Reputation: 31
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
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
Reputation: 424983
Here's an implementation that:
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
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