Java 流分组按多个字段求和
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/52058261/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Java stream group by and sum multiple fields
提问by MatMat
I have a List fooList
我有一个列表 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.
关键是 Foo 持有汇总的金额和价格,列表作为具有相同类别的所有对象的值。
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.
现在我只需要用一个包含汇总金额和价格的 Foo 对象替换 String 键。这是我被困的地方。我似乎找不到任何方法来做到这一点。
采纳答案by Sweeper
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));
回答by dehasi
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)));
回答by Dakshinamurthy Karra
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);
}
回答by Hulk
My variation of Sweepers answeruses a reducing Collector instead of streaming twice to sum the individual fields:
我的Sweepers 答案变体使用了一个减少的收集器而不是流两次来对各个字段求和:
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 betterthough, as it creates a lot of short-lived Foos
.
但它并不是真的更好,因为它会产生很多短暂的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.
但是请注意,Foo
需要提供仅考虑结果才能正常工作的hashCode
- 和 -equals
实现。一般来说,这可能不是您想要的s 。我更喜欢定义一个单独的类来包含聚合数据。category
map
Foo
FooSummary
回答by Federico Peralta Schaffner
If you have a special, dedicated constructor and hashCode
and equals
methods consistently implemented in Foo
as follows:
如果你有一个特殊的、专用的构造函数hashCode
和equals
方法Foo
,如下所示:
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).
上面的hashCode
和equals
实现允许您Foo
在地图中用作有意义的键(否则您的地图将被破坏)。
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:
现在,借助一种Foo
同时执行amount
和price
属性聚合的新方法,您可以分两步完成您想要的操作。先说方法:
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.
一方面,该解决方案迫使你使用的实现hashCode
,并equals
认为考虑两种不同的Foo
情况下,作为平等的,如果他们属于同一个category
。也许这不是我们想要的,或者您已经有一个考虑更多或其他属性的实现。
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.
另一方面,Foo
用作映射的键,用于按其一个属性对实例进行分组是一个非常不常见的用例。我认为最好只使用该category
属性按类别分组并有两个映射:Map<String, List<Foo>>
保留组并Map<String, Foo>
保留聚合的price
and amount
,关键是category
在这两种情况下。
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.
除此之外,此解决方案在将条目放入地图后改变地图的键。这是危险的,因为这可能会破坏地图。然而,在这里我只是改变Foo
那些既不参与hashCode
也不参与的属性equals
Foo
。我认为这种风险在这种情况下是可以接受的,因为要求是不寻常的。