Java stream group by and sum multiple fields
Clash Royale CLAN TAG#URR8PPP
up vote
7
down vote
favorite
I have a List fooList
class Foo
private String category;
private int amount;
private int price;
... constructor, getters & setters
I would like to group by category and then sum amount aswell as price.
The result will be stored in a map:
Map<Foo, List<Foo>> map = new HashMap<>();
The key is the Foo holding the summarized amount and price, with a list as value for all the objects with the same category.
So far I've tried the following:
Map<String, List<Foo>> map = fooList.stream().collect(groupingBy(Foo::getCategory()));
Now I only need to replace the String key with a Foo object holding the summarized amount and price. Here is where I'm stuck. I can't seem to find any way of doing this.
java java-8 java-stream grouping
add a comment |Â
up vote
7
down vote
favorite
I have a List fooList
class Foo
private String category;
private int amount;
private int price;
... constructor, getters & setters
I would like to group by category and then sum amount aswell as price.
The result will be stored in a map:
Map<Foo, List<Foo>> map = new HashMap<>();
The key is the Foo holding the summarized amount and price, with a list as value for all the objects with the same category.
So far I've tried the following:
Map<String, List<Foo>> map = fooList.stream().collect(groupingBy(Foo::getCategory()));
Now I only need to replace the String key with a Foo object holding the summarized amount and price. Here is where I'm stuck. I can't seem to find any way of doing this.
java java-8 java-stream grouping
have you looked atCollectors.summarizingLong
?
– dehasi
Aug 28 at 12:49
Yes but how do you summarize on multiple fields?
– MatMat
Aug 28 at 12:52
1
well only recently I've answered something very close to this... stackoverflow.com/a/52041895/1059372 in jdk-12 seems like there will be aBiCollector
that will make your life easier
– Eugene
Aug 28 at 12:58
3
Do I understand you correctly, that the KEY shall hold the sums? And that the List<Foo> shall hold all original instances?
– jokster
Aug 28 at 12:59
@jokster yes, that is correct!
– MatMat
Aug 28 at 13:01
add a comment |Â
up vote
7
down vote
favorite
up vote
7
down vote
favorite
I have a List fooList
class Foo
private String category;
private int amount;
private int price;
... constructor, getters & setters
I would like to group by category and then sum amount aswell as price.
The result will be stored in a map:
Map<Foo, List<Foo>> map = new HashMap<>();
The key is the Foo holding the summarized amount and price, with a list as value for all the objects with the same category.
So far I've tried the following:
Map<String, List<Foo>> map = fooList.stream().collect(groupingBy(Foo::getCategory()));
Now I only need to replace the String key with a Foo object holding the summarized amount and price. Here is where I'm stuck. I can't seem to find any way of doing this.
java java-8 java-stream grouping
I have a List fooList
class Foo
private String category;
private int amount;
private int price;
... constructor, getters & setters
I would like to group by category and then sum amount aswell as price.
The result will be stored in a map:
Map<Foo, List<Foo>> map = new HashMap<>();
The key is the Foo holding the summarized amount and price, with a list as value for all the objects with the same category.
So far I've tried the following:
Map<String, List<Foo>> map = fooList.stream().collect(groupingBy(Foo::getCategory()));
Now I only need to replace the String key with a Foo object holding the summarized amount and price. Here is where I'm stuck. I can't seem to find any way of doing this.
java java-8 java-stream grouping
edited Aug 28 at 12:52


YCF_L
31.5k103273
31.5k103273
asked Aug 28 at 12:44
MatMat
299214
299214
have you looked atCollectors.summarizingLong
?
– dehasi
Aug 28 at 12:49
Yes but how do you summarize on multiple fields?
– MatMat
Aug 28 at 12:52
1
well only recently I've answered something very close to this... stackoverflow.com/a/52041895/1059372 in jdk-12 seems like there will be aBiCollector
that will make your life easier
– Eugene
Aug 28 at 12:58
3
Do I understand you correctly, that the KEY shall hold the sums? And that the List<Foo> shall hold all original instances?
– jokster
Aug 28 at 12:59
@jokster yes, that is correct!
– MatMat
Aug 28 at 13:01
add a comment |Â
have you looked atCollectors.summarizingLong
?
– dehasi
Aug 28 at 12:49
Yes but how do you summarize on multiple fields?
– MatMat
Aug 28 at 12:52
1
well only recently I've answered something very close to this... stackoverflow.com/a/52041895/1059372 in jdk-12 seems like there will be aBiCollector
that will make your life easier
– Eugene
Aug 28 at 12:58
3
Do I understand you correctly, that the KEY shall hold the sums? And that the List<Foo> shall hold all original instances?
– jokster
Aug 28 at 12:59
@jokster yes, that is correct!
– MatMat
Aug 28 at 13:01
have you looked at
Collectors.summarizingLong
?– dehasi
Aug 28 at 12:49
have you looked at
Collectors.summarizingLong
?– dehasi
Aug 28 at 12:49
Yes but how do you summarize on multiple fields?
– MatMat
Aug 28 at 12:52
Yes but how do you summarize on multiple fields?
– MatMat
Aug 28 at 12:52
1
1
well only recently I've answered something very close to this... stackoverflow.com/a/52041895/1059372 in jdk-12 seems like there will be a
BiCollector
that will make your life easier– Eugene
Aug 28 at 12:58
well only recently I've answered something very close to this... stackoverflow.com/a/52041895/1059372 in jdk-12 seems like there will be a
BiCollector
that will make your life easier– Eugene
Aug 28 at 12:58
3
3
Do I understand you correctly, that the KEY shall hold the sums? And that the List<Foo> shall hold all original instances?
– jokster
Aug 28 at 12:59
Do I understand you correctly, that the KEY shall hold the sums? And that the List<Foo> shall hold all original instances?
– jokster
Aug 28 at 12:59
@jokster yes, that is correct!
– MatMat
Aug 28 at 13:01
@jokster yes, that is correct!
– MatMat
Aug 28 at 13:01
add a comment |Â
5 Answers
5
active
oldest
votes
up vote
9
down vote
accepted
A bit ugly, but it should work:
list.stream().collect(Collectors.groupingBy(Foo::getCategory))
.entrySet().stream()
.collect(Collectors.toMap(x ->
int sumAmount = x.getValue().stream().mapToInt(Foo::getAmount).sum();
int sumPrice= x.getValue().stream().mapToInt(Foo::getPrice).sum();
return new Foo(x.getKey(), sumAmount, sumPrice);
, Map.Entry::getValue));
add a comment |Â
up vote
4
down vote
My variation of Sweepers answer uses a reducing Collector instead of streaming twice to sum the individual fields:
Map<Foo, List<Foo>> map = fooList.stream()
.collect(Collectors.groupingBy(Foo::getCategory))
.entrySet().stream()
.collect(Collectors.toMap(e -> e.getValue().stream().collect(
Collectors.reducing((l, r) -> new Foo(l.getCategory(),
l.getAmount() + r.getAmount(),
l.getPrice() + r.getPrice())))
.get(),
e -> e.getValue()));
It is not really better though, as it creates a lot of short-lived Foos
.
Note however that Foo
is required to provide hashCode
- and equals
-implementations that take only category
into account for the resulting map
to work correctly. This would probably not be what you want for Foo
s in general. I would prefer defining a separate FooSummary
class to contain the aggregated data.
add a comment |Â
up vote
3
down vote
If you have a special, dedicated constructor and hashCode
and equals
methods consistently implemented in Foo
as follows:
public Foo(Foo that) // not a copy constructor!!!
this.category = that.category;
this.amount = 0;
this.price = 0;
public int hashCode()
return Objects.hashCode(category);
public boolean equals(Object another)
if (another == this) return true;
if (!(another instanceof Foo)) return false;
Foo that = (Foo) another;
return Objects.equals(this.category, that.category);
The hashCode
and equals
implementations above allow you to use Foo
as a meaningful key in the map (otherwise your map would be broken).
Now, with the help of a new method in Foo
that performs the aggregation of the amount
and price
attributes at the same time, you can do what you want in 2 steps. First the method:
public void aggregate(Foo that)
this.amount += that.amount;
this.price += that.price;
Now the final solution:
Map<Foo, List<Foo>> result = fooList.stream().collect(
Collectors.collectingAndThen(
Collectors.groupingBy(Foo::new), // works: special ctor, hashCode & equals
m -> m.forEach((k, v) -> v.forEach(k::aggregate)); return m; ));
EDIT: a few observations were missing...
On one hand, this solution forces you to use an implementation of hashCode
and equals
that considers two different Foo
instances as equal if they belong to the same category
. Maybe this is not desired, or you already have an implementation that takes more or other attributes into account.
On the other hand, using Foo
as the key of a map which is used to group instances by one of its attributes is quite an uncommon use case. I think it would be better to just use the category
attribute to group by category and have two maps: Map<String, List<Foo>>
to keep the groups and Map<String, Foo>
to keep the aggregated price
and amount
, with the key being the category
in both cases.
Besides this, this solution mutates the keys of the map after the entries are put into it. This is dangerous, because this could break the map. However, here I'm only mutating attributes of Foo
that don't participate in neither hashCode
nor equals
Foo
's implementation. I think that this risk is acceptable in this case, due to the unusuality of the requirement.
add a comment |Â
up vote
0
down vote
I suggest you create a helper class, which will hold amount and price
final class Pair
final int amount;
final int price;
Pair(int amount, int price)
this.amount = amount;
this.price = price;
And then just collect list to map:
List<Foo> list =//....;
Map<Foo, Pair> categotyPrise = list.stream().collect(Collectors.toMap(foo -> foo,
foo -> new Pair(foo.getAmount(), foo.getPrice()),
(o, n) -> new Pair(o.amount + n.amount, o.price + n.price)));
that is a good start, but not what the OP needs... read the comments under the question
– Eugene
Aug 28 at 13:06
add a comment |Â
up vote
0
down vote
My take on a solution :)
public static void main(String args)
List<Foo> foos = new ArrayList<>();
foos.add(new Foo("A", 1, 10));
foos.add(new Foo("A", 2, 10));
foos.add(new Foo("A", 3, 10));
foos.add(new Foo("B", 1, 10));
foos.add(new Foo("C", 1, 10));
foos.add(new Foo("C", 5, 10));
List<Foo> summarized = new ArrayList<>();
Map<Foo, List<Foo>> collect = foos.stream().collect(Collectors.groupingBy(new Function<Foo, Foo>()
@Override
public Foo apply(Foo t)
Optional<Foo> fOpt = summarized.stream().filter(e -> e.getCategory().equals(t.getCategory())).findFirst();
Foo f;
if (!fOpt.isPresent())
f = new Foo(t.getCategory(), 0, 0);
summarized.add(f);
else
f = fOpt.get();
f.setAmount(f.getAmount() + t.getAmount());
f.setPrice(f.getPrice() + t.getPrice());
return f;
));
System.out.println(collect);
add a comment |Â
5 Answers
5
active
oldest
votes
5 Answers
5
active
oldest
votes
active
oldest
votes
active
oldest
votes
up vote
9
down vote
accepted
A bit ugly, but it should work:
list.stream().collect(Collectors.groupingBy(Foo::getCategory))
.entrySet().stream()
.collect(Collectors.toMap(x ->
int sumAmount = x.getValue().stream().mapToInt(Foo::getAmount).sum();
int sumPrice= x.getValue().stream().mapToInt(Foo::getPrice).sum();
return new Foo(x.getKey(), sumAmount, sumPrice);
, Map.Entry::getValue));
add a comment |Â
up vote
9
down vote
accepted
A bit ugly, but it should work:
list.stream().collect(Collectors.groupingBy(Foo::getCategory))
.entrySet().stream()
.collect(Collectors.toMap(x ->
int sumAmount = x.getValue().stream().mapToInt(Foo::getAmount).sum();
int sumPrice= x.getValue().stream().mapToInt(Foo::getPrice).sum();
return new Foo(x.getKey(), sumAmount, sumPrice);
, Map.Entry::getValue));
add a comment |Â
up vote
9
down vote
accepted
up vote
9
down vote
accepted
A bit ugly, but it should work:
list.stream().collect(Collectors.groupingBy(Foo::getCategory))
.entrySet().stream()
.collect(Collectors.toMap(x ->
int sumAmount = x.getValue().stream().mapToInt(Foo::getAmount).sum();
int sumPrice= x.getValue().stream().mapToInt(Foo::getPrice).sum();
return new Foo(x.getKey(), sumAmount, sumPrice);
, Map.Entry::getValue));
A bit ugly, but it should work:
list.stream().collect(Collectors.groupingBy(Foo::getCategory))
.entrySet().stream()
.collect(Collectors.toMap(x ->
int sumAmount = x.getValue().stream().mapToInt(Foo::getAmount).sum();
int sumPrice= x.getValue().stream().mapToInt(Foo::getPrice).sum();
return new Foo(x.getKey(), sumAmount, sumPrice);
, Map.Entry::getValue));
answered Aug 28 at 13:21


Sweeper
54.7k960117
54.7k960117
add a comment |Â
add a comment |Â
up vote
4
down vote
My variation of Sweepers answer uses a reducing Collector instead of streaming twice to sum the individual fields:
Map<Foo, List<Foo>> map = fooList.stream()
.collect(Collectors.groupingBy(Foo::getCategory))
.entrySet().stream()
.collect(Collectors.toMap(e -> e.getValue().stream().collect(
Collectors.reducing((l, r) -> new Foo(l.getCategory(),
l.getAmount() + r.getAmount(),
l.getPrice() + r.getPrice())))
.get(),
e -> e.getValue()));
It is not really better though, as it creates a lot of short-lived Foos
.
Note however that Foo
is required to provide hashCode
- and equals
-implementations that take only category
into account for the resulting map
to work correctly. This would probably not be what you want for Foo
s in general. I would prefer defining a separate FooSummary
class to contain the aggregated data.
add a comment |Â
up vote
4
down vote
My variation of Sweepers answer uses a reducing Collector instead of streaming twice to sum the individual fields:
Map<Foo, List<Foo>> map = fooList.stream()
.collect(Collectors.groupingBy(Foo::getCategory))
.entrySet().stream()
.collect(Collectors.toMap(e -> e.getValue().stream().collect(
Collectors.reducing((l, r) -> new Foo(l.getCategory(),
l.getAmount() + r.getAmount(),
l.getPrice() + r.getPrice())))
.get(),
e -> e.getValue()));
It is not really better though, as it creates a lot of short-lived Foos
.
Note however that Foo
is required to provide hashCode
- and equals
-implementations that take only category
into account for the resulting map
to work correctly. This would probably not be what you want for Foo
s in general. I would prefer defining a separate FooSummary
class to contain the aggregated data.
add a comment |Â
up vote
4
down vote
up vote
4
down vote
My variation of Sweepers answer uses a reducing Collector instead of streaming twice to sum the individual fields:
Map<Foo, List<Foo>> map = fooList.stream()
.collect(Collectors.groupingBy(Foo::getCategory))
.entrySet().stream()
.collect(Collectors.toMap(e -> e.getValue().stream().collect(
Collectors.reducing((l, r) -> new Foo(l.getCategory(),
l.getAmount() + r.getAmount(),
l.getPrice() + r.getPrice())))
.get(),
e -> e.getValue()));
It is not really better though, as it creates a lot of short-lived Foos
.
Note however that Foo
is required to provide hashCode
- and equals
-implementations that take only category
into account for the resulting map
to work correctly. This would probably not be what you want for Foo
s in general. I would prefer defining a separate FooSummary
class to contain the aggregated data.
My variation of Sweepers answer uses a reducing Collector instead of streaming twice to sum the individual fields:
Map<Foo, List<Foo>> map = fooList.stream()
.collect(Collectors.groupingBy(Foo::getCategory))
.entrySet().stream()
.collect(Collectors.toMap(e -> e.getValue().stream().collect(
Collectors.reducing((l, r) -> new Foo(l.getCategory(),
l.getAmount() + r.getAmount(),
l.getPrice() + r.getPrice())))
.get(),
e -> e.getValue()));
It is not really better though, as it creates a lot of short-lived Foos
.
Note however that Foo
is required to provide hashCode
- and equals
-implementations that take only category
into account for the resulting map
to work correctly. This would probably not be what you want for Foo
s in general. I would prefer defining a separate FooSummary
class to contain the aggregated data.
edited Aug 29 at 6:37
answered Aug 28 at 13:45
Hulk
2,13411431
2,13411431
add a comment |Â
add a comment |Â
up vote
3
down vote
If you have a special, dedicated constructor and hashCode
and equals
methods consistently implemented in Foo
as follows:
public Foo(Foo that) // not a copy constructor!!!
this.category = that.category;
this.amount = 0;
this.price = 0;
public int hashCode()
return Objects.hashCode(category);
public boolean equals(Object another)
if (another == this) return true;
if (!(another instanceof Foo)) return false;
Foo that = (Foo) another;
return Objects.equals(this.category, that.category);
The hashCode
and equals
implementations above allow you to use Foo
as a meaningful key in the map (otherwise your map would be broken).
Now, with the help of a new method in Foo
that performs the aggregation of the amount
and price
attributes at the same time, you can do what you want in 2 steps. First the method:
public void aggregate(Foo that)
this.amount += that.amount;
this.price += that.price;
Now the final solution:
Map<Foo, List<Foo>> result = fooList.stream().collect(
Collectors.collectingAndThen(
Collectors.groupingBy(Foo::new), // works: special ctor, hashCode & equals
m -> m.forEach((k, v) -> v.forEach(k::aggregate)); return m; ));
EDIT: a few observations were missing...
On one hand, this solution forces you to use an implementation of hashCode
and equals
that considers two different Foo
instances as equal if they belong to the same category
. Maybe this is not desired, or you already have an implementation that takes more or other attributes into account.
On the other hand, using Foo
as the key of a map which is used to group instances by one of its attributes is quite an uncommon use case. I think it would be better to just use the category
attribute to group by category and have two maps: Map<String, List<Foo>>
to keep the groups and Map<String, Foo>
to keep the aggregated price
and amount
, with the key being the category
in both cases.
Besides this, this solution mutates the keys of the map after the entries are put into it. This is dangerous, because this could break the map. However, here I'm only mutating attributes of Foo
that don't participate in neither hashCode
nor equals
Foo
's implementation. I think that this risk is acceptable in this case, due to the unusuality of the requirement.
add a comment |Â
up vote
3
down vote
If you have a special, dedicated constructor and hashCode
and equals
methods consistently implemented in Foo
as follows:
public Foo(Foo that) // not a copy constructor!!!
this.category = that.category;
this.amount = 0;
this.price = 0;
public int hashCode()
return Objects.hashCode(category);
public boolean equals(Object another)
if (another == this) return true;
if (!(another instanceof Foo)) return false;
Foo that = (Foo) another;
return Objects.equals(this.category, that.category);
The hashCode
and equals
implementations above allow you to use Foo
as a meaningful key in the map (otherwise your map would be broken).
Now, with the help of a new method in Foo
that performs the aggregation of the amount
and price
attributes at the same time, you can do what you want in 2 steps. First the method:
public void aggregate(Foo that)
this.amount += that.amount;
this.price += that.price;
Now the final solution:
Map<Foo, List<Foo>> result = fooList.stream().collect(
Collectors.collectingAndThen(
Collectors.groupingBy(Foo::new), // works: special ctor, hashCode & equals
m -> m.forEach((k, v) -> v.forEach(k::aggregate)); return m; ));
EDIT: a few observations were missing...
On one hand, this solution forces you to use an implementation of hashCode
and equals
that considers two different Foo
instances as equal if they belong to the same category
. Maybe this is not desired, or you already have an implementation that takes more or other attributes into account.
On the other hand, using Foo
as the key of a map which is used to group instances by one of its attributes is quite an uncommon use case. I think it would be better to just use the category
attribute to group by category and have two maps: Map<String, List<Foo>>
to keep the groups and Map<String, Foo>
to keep the aggregated price
and amount
, with the key being the category
in both cases.
Besides this, this solution mutates the keys of the map after the entries are put into it. This is dangerous, because this could break the map. However, here I'm only mutating attributes of Foo
that don't participate in neither hashCode
nor equals
Foo
's implementation. I think that this risk is acceptable in this case, due to the unusuality of the requirement.
add a comment |Â
up vote
3
down vote
up vote
3
down vote
If you have a special, dedicated constructor and hashCode
and equals
methods consistently implemented in Foo
as follows:
public Foo(Foo that) // not a copy constructor!!!
this.category = that.category;
this.amount = 0;
this.price = 0;
public int hashCode()
return Objects.hashCode(category);
public boolean equals(Object another)
if (another == this) return true;
if (!(another instanceof Foo)) return false;
Foo that = (Foo) another;
return Objects.equals(this.category, that.category);
The hashCode
and equals
implementations above allow you to use Foo
as a meaningful key in the map (otherwise your map would be broken).
Now, with the help of a new method in Foo
that performs the aggregation of the amount
and price
attributes at the same time, you can do what you want in 2 steps. First the method:
public void aggregate(Foo that)
this.amount += that.amount;
this.price += that.price;
Now the final solution:
Map<Foo, List<Foo>> result = fooList.stream().collect(
Collectors.collectingAndThen(
Collectors.groupingBy(Foo::new), // works: special ctor, hashCode & equals
m -> m.forEach((k, v) -> v.forEach(k::aggregate)); return m; ));
EDIT: a few observations were missing...
On one hand, this solution forces you to use an implementation of hashCode
and equals
that considers two different Foo
instances as equal if they belong to the same category
. Maybe this is not desired, or you already have an implementation that takes more or other attributes into account.
On the other hand, using Foo
as the key of a map which is used to group instances by one of its attributes is quite an uncommon use case. I think it would be better to just use the category
attribute to group by category and have two maps: Map<String, List<Foo>>
to keep the groups and Map<String, Foo>
to keep the aggregated price
and amount
, with the key being the category
in both cases.
Besides this, this solution mutates the keys of the map after the entries are put into it. This is dangerous, because this could break the map. However, here I'm only mutating attributes of Foo
that don't participate in neither hashCode
nor equals
Foo
's implementation. I think that this risk is acceptable in this case, due to the unusuality of the requirement.
If you have a special, dedicated constructor and hashCode
and equals
methods consistently implemented in Foo
as follows:
public Foo(Foo that) // not a copy constructor!!!
this.category = that.category;
this.amount = 0;
this.price = 0;
public int hashCode()
return Objects.hashCode(category);
public boolean equals(Object another)
if (another == this) return true;
if (!(another instanceof Foo)) return false;
Foo that = (Foo) another;
return Objects.equals(this.category, that.category);
The hashCode
and equals
implementations above allow you to use Foo
as a meaningful key in the map (otherwise your map would be broken).
Now, with the help of a new method in Foo
that performs the aggregation of the amount
and price
attributes at the same time, you can do what you want in 2 steps. First the method:
public void aggregate(Foo that)
this.amount += that.amount;
this.price += that.price;
Now the final solution:
Map<Foo, List<Foo>> result = fooList.stream().collect(
Collectors.collectingAndThen(
Collectors.groupingBy(Foo::new), // works: special ctor, hashCode & equals
m -> m.forEach((k, v) -> v.forEach(k::aggregate)); return m; ));
EDIT: a few observations were missing...
On one hand, this solution forces you to use an implementation of hashCode
and equals
that considers two different Foo
instances as equal if they belong to the same category
. Maybe this is not desired, or you already have an implementation that takes more or other attributes into account.
On the other hand, using Foo
as the key of a map which is used to group instances by one of its attributes is quite an uncommon use case. I think it would be better to just use the category
attribute to group by category and have two maps: Map<String, List<Foo>>
to keep the groups and Map<String, Foo>
to keep the aggregated price
and amount
, with the key being the category
in both cases.
Besides this, this solution mutates the keys of the map after the entries are put into it. This is dangerous, because this could break the map. However, here I'm only mutating attributes of Foo
that don't participate in neither hashCode
nor equals
Foo
's implementation. I think that this risk is acceptable in this case, due to the unusuality of the requirement.
edited Aug 29 at 13:35
answered Aug 28 at 15:29


Federico Peralta Schaffner
18.5k32862
18.5k32862
add a comment |Â
add a comment |Â
up vote
0
down vote
I suggest you create a helper class, which will hold amount and price
final class Pair
final int amount;
final int price;
Pair(int amount, int price)
this.amount = amount;
this.price = price;
And then just collect list to map:
List<Foo> list =//....;
Map<Foo, Pair> categotyPrise = list.stream().collect(Collectors.toMap(foo -> foo,
foo -> new Pair(foo.getAmount(), foo.getPrice()),
(o, n) -> new Pair(o.amount + n.amount, o.price + n.price)));
that is a good start, but not what the OP needs... read the comments under the question
– Eugene
Aug 28 at 13:06
add a comment |Â
up vote
0
down vote
I suggest you create a helper class, which will hold amount and price
final class Pair
final int amount;
final int price;
Pair(int amount, int price)
this.amount = amount;
this.price = price;
And then just collect list to map:
List<Foo> list =//....;
Map<Foo, Pair> categotyPrise = list.stream().collect(Collectors.toMap(foo -> foo,
foo -> new Pair(foo.getAmount(), foo.getPrice()),
(o, n) -> new Pair(o.amount + n.amount, o.price + n.price)));
that is a good start, but not what the OP needs... read the comments under the question
– Eugene
Aug 28 at 13:06
add a comment |Â
up vote
0
down vote
up vote
0
down vote
I suggest you create a helper class, which will hold amount and price
final class Pair
final int amount;
final int price;
Pair(int amount, int price)
this.amount = amount;
this.price = price;
And then just collect list to map:
List<Foo> list =//....;
Map<Foo, Pair> categotyPrise = list.stream().collect(Collectors.toMap(foo -> foo,
foo -> new Pair(foo.getAmount(), foo.getPrice()),
(o, n) -> new Pair(o.amount + n.amount, o.price + n.price)));
I suggest you create a helper class, which will hold amount and price
final class Pair
final int amount;
final int price;
Pair(int amount, int price)
this.amount = amount;
this.price = price;
And then just collect list to map:
List<Foo> list =//....;
Map<Foo, Pair> categotyPrise = list.stream().collect(Collectors.toMap(foo -> foo,
foo -> new Pair(foo.getAmount(), foo.getPrice()),
(o, n) -> new Pair(o.amount + n.amount, o.price + n.price)));
answered Aug 28 at 13:05


dehasi
539615
539615
that is a good start, but not what the OP needs... read the comments under the question
– Eugene
Aug 28 at 13:06
add a comment |Â
that is a good start, but not what the OP needs... read the comments under the question
– Eugene
Aug 28 at 13:06
that is a good start, but not what the OP needs... read the comments under the question
– Eugene
Aug 28 at 13:06
that is a good start, but not what the OP needs... read the comments under the question
– Eugene
Aug 28 at 13:06
add a comment |Â
up vote
0
down vote
My take on a solution :)
public static void main(String args)
List<Foo> foos = new ArrayList<>();
foos.add(new Foo("A", 1, 10));
foos.add(new Foo("A", 2, 10));
foos.add(new Foo("A", 3, 10));
foos.add(new Foo("B", 1, 10));
foos.add(new Foo("C", 1, 10));
foos.add(new Foo("C", 5, 10));
List<Foo> summarized = new ArrayList<>();
Map<Foo, List<Foo>> collect = foos.stream().collect(Collectors.groupingBy(new Function<Foo, Foo>()
@Override
public Foo apply(Foo t)
Optional<Foo> fOpt = summarized.stream().filter(e -> e.getCategory().equals(t.getCategory())).findFirst();
Foo f;
if (!fOpt.isPresent())
f = new Foo(t.getCategory(), 0, 0);
summarized.add(f);
else
f = fOpt.get();
f.setAmount(f.getAmount() + t.getAmount());
f.setPrice(f.getPrice() + t.getPrice());
return f;
));
System.out.println(collect);
add a comment |Â
up vote
0
down vote
My take on a solution :)
public static void main(String args)
List<Foo> foos = new ArrayList<>();
foos.add(new Foo("A", 1, 10));
foos.add(new Foo("A", 2, 10));
foos.add(new Foo("A", 3, 10));
foos.add(new Foo("B", 1, 10));
foos.add(new Foo("C", 1, 10));
foos.add(new Foo("C", 5, 10));
List<Foo> summarized = new ArrayList<>();
Map<Foo, List<Foo>> collect = foos.stream().collect(Collectors.groupingBy(new Function<Foo, Foo>()
@Override
public Foo apply(Foo t)
Optional<Foo> fOpt = summarized.stream().filter(e -> e.getCategory().equals(t.getCategory())).findFirst();
Foo f;
if (!fOpt.isPresent())
f = new Foo(t.getCategory(), 0, 0);
summarized.add(f);
else
f = fOpt.get();
f.setAmount(f.getAmount() + t.getAmount());
f.setPrice(f.getPrice() + t.getPrice());
return f;
));
System.out.println(collect);
add a comment |Â
up vote
0
down vote
up vote
0
down vote
My take on a solution :)
public static void main(String args)
List<Foo> foos = new ArrayList<>();
foos.add(new Foo("A", 1, 10));
foos.add(new Foo("A", 2, 10));
foos.add(new Foo("A", 3, 10));
foos.add(new Foo("B", 1, 10));
foos.add(new Foo("C", 1, 10));
foos.add(new Foo("C", 5, 10));
List<Foo> summarized = new ArrayList<>();
Map<Foo, List<Foo>> collect = foos.stream().collect(Collectors.groupingBy(new Function<Foo, Foo>()
@Override
public Foo apply(Foo t)
Optional<Foo> fOpt = summarized.stream().filter(e -> e.getCategory().equals(t.getCategory())).findFirst();
Foo f;
if (!fOpt.isPresent())
f = new Foo(t.getCategory(), 0, 0);
summarized.add(f);
else
f = fOpt.get();
f.setAmount(f.getAmount() + t.getAmount());
f.setPrice(f.getPrice() + t.getPrice());
return f;
));
System.out.println(collect);
My take on a solution :)
public static void main(String args)
List<Foo> foos = new ArrayList<>();
foos.add(new Foo("A", 1, 10));
foos.add(new Foo("A", 2, 10));
foos.add(new Foo("A", 3, 10));
foos.add(new Foo("B", 1, 10));
foos.add(new Foo("C", 1, 10));
foos.add(new Foo("C", 5, 10));
List<Foo> summarized = new ArrayList<>();
Map<Foo, List<Foo>> collect = foos.stream().collect(Collectors.groupingBy(new Function<Foo, Foo>()
@Override
public Foo apply(Foo t)
Optional<Foo> fOpt = summarized.stream().filter(e -> e.getCategory().equals(t.getCategory())).findFirst();
Foo f;
if (!fOpt.isPresent())
f = new Foo(t.getCategory(), 0, 0);
summarized.add(f);
else
f = fOpt.get();
f.setAmount(f.getAmount() + t.getAmount());
f.setPrice(f.getPrice() + t.getPrice());
return f;
));
System.out.println(collect);
edited Aug 28 at 13:19
answered Aug 28 at 13:14
KDM
3,7011921
3,7011921
add a comment |Â
add a comment |Â
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f52058261%2fjava-stream-group-by-and-sum-multiple-fields%23new-answer', 'question_page');
);
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
have you looked at
Collectors.summarizingLong
?– dehasi
Aug 28 at 12:49
Yes but how do you summarize on multiple fields?
– MatMat
Aug 28 at 12:52
1
well only recently I've answered something very close to this... stackoverflow.com/a/52041895/1059372 in jdk-12 seems like there will be a
BiCollector
that will make your life easier– Eugene
Aug 28 at 12:58
3
Do I understand you correctly, that the KEY shall hold the sums? And that the List<Foo> shall hold all original instances?
– jokster
Aug 28 at 12:59
@jokster yes, that is correct!
– MatMat
Aug 28 at 13:01