rainie1636
rainie1636

Reputation: 31

java 8 stream how to map 2 column into the map

I have a table:

name | data | num
item1 | 16 | 2
item2 | 17 | 3
item1 | 16 | 5

I would like to transform it to:

{ item1: {16+16, 2+5}, item2: {17, 3}}

However I only succeed to produce the following result:

{ item1: 16+16, item2: 17}

with the following code: The Stats class store both fields, but I don't know how to add both fields into the data

class Stats {
    public Integer data, num;
    public Stats(Integer td, Integer nf) {
        data = td;
        num = nf;
    }
    public Integer getdata(){
        return data;
    }
    public Integer getnum(){
        return num;
    }
}
Map<String, Stats> map = new HashMap<>();
Stream<String> lines = Files.lines(Paths.get(table));
map = lines.map(x -> 
             .collect(Collectors.toMap(
                z -> z[0],
                z -> Integer.parseInt(z[1]),
                (a, b) -> a+b));

The above code only works for Map<String, Int> since I'm not sure how to make Map<String, Stats> works. Any idea how to get the item on the second column into the map while and using 1 pipeline only?

Upvotes: 1

Views: 4718

Answers (3)

Tagir Valeev
Tagir Valeev

Reputation: 100169

First you can make your Stats class a little more reduce-friendly adding merge method (I also prettified it a little):

class Stats {
    final int data, num;

    public Stats() {
        data = num = 0;
    }

    public Stats(int data, int num) {
        this.data = data;
        this.num = num;
    }

    public Stats merge(Stats stats) {
        return new Stats(this.data+stats.data, this.num+stats.num);
    }

    public int getData(){
        return data;
    }
    public int getNum(){
        return num;
    }

    @Override
    public String toString() {
        return "{" + data + "," + num + "}";
    }
}

Now you can solve your task using the chain of Collectors. Suppose your input comes from String stream like in Bohemian answer:

Stream<String> lines = Stream.of("item1,16,2",
          "item2,17,3",
          "item1,16,5"); // sample input

Now the resulting map can be built in this manner:

Map<String, Stats> map = lines.map(s -> s.split(",")).collect(
    Collectors.groupingBy(a -> a[0], 
    Collectors.mapping(a -> new Stats(Integer.parseInt(a[1]), Integer.parseInt(a[2])), 
    Collectors.reducing(new Stats(), Stats::merge))));
System.out.println(map);

Live demo is here.

Upvotes: 0

Bohemian
Bohemian

Reputation: 424983

Here's an implementation that:

  • doesn't need any extra classes
  • works for any length of numeric values (not just 2)
  • is (technically) just one line


Stream<String> lines = Stream.of("item1,16,2",
      "item2,17,3",
      "item1,16,5"); // sample input

Map<String, List<Integer>> map = lines.map(s -> s.split(","))
  .collect(Collectors.toMap(a -> a[0],
      a -> Arrays.asList(a).stream()
       .skip(1).map(Integer::parseInt)
        .collect(Collectors.toList()),
      (a, b) -> {for (int i = 0; i < a.size(); i++)
          a.set(i, a.get(i) + b.get(i)); return a;}));

Printing the map after running this outputs:

{item2=[17, 3], item1=[32, 7]}

See live demo of this code.

Upvotes: 0

JB Nizet
JB Nizet

Reputation: 691655

You first need to create a class containing two integers:

public final class TwoInts {
    private final int data;
    private final int num;

    public TwoInts(int data, int num) { 
        this.data = data;
        this.num = num;
    }

    // getters omitted for brevity
}

Then you can reuse the logic you already have, except instead of using integers, you'll use TwoInts instances. So you'll need to be able to add two instances of TwoInts together to produce another TwoInts instance (just like you're producing a new integer by adding two integers):

public final class TwoInts {
    private final int data;
    private final int num;

    public TwoInts(int data, int num) { 
        this.data = data;
        this.num = num;
    }

    public static TwoInts sum(TwoInts first, TwoInts second) {
        return new TwoInts(first.data + second.data, 
                           first.num + second.num);
    }

    // getters omitted for brevity
}

And there you go:

Stream<String> lines = Files.lines(Paths.get(table));
Map<String, TwoInts> map = 
    lines.map(line -> toArrayOfStrings(line))
         .collect(Collectors.toMap(
              row -> row[0],
              row -> new TwoInts(Integer.parseInt(row[1]), Integer.parseInt(row[2])),
              (a, b) -> TwoInts.sum(a, b));

Upvotes: 1

Related Questions