Java 8 streamingingBy:求和一个attributeValue

时间:2016-01-25 16:31:05

标签: java java-8 grouping java-stream

我有一个简单的类Foo:

class Foo {
   String type; 
   Integer quantity;
   String code;
   //...
}

以及该类型的对象列表:

{ type=11, quantity=1, code=A },
{ type=11, quantity=2, code=A },
{ type=11, quantity=1, code=B },
{ type=11, quantity=3, code=C },
{ type=12, quantity=2, code=A },
{ type=12, quantity=1, code=B },
{ type=11, quantity=1, code=B },
{ type=13, quantity=1, code=C },
{ type=13, quantity=3, code=C }

我需要先按类别分组,我可以这样做:

Map<String, List<Foo>> typeGroups = mylist.stream().collect(Collectors.groupingBy(Foo::getType));

下一步是将Map转换为List<Foo>,如下所示:

{ type=11, quantity=3, code=A },
{ type=11, quantity=2, code=B },
{ type=11, quantity=3, code=C },
{ type=12, quantity=2, code=A },
{ type=12, quantity=1, code=B },
{ type=13, quantity=4, code=C }

在SQL中,我有这样的查询:

SELECT type, code, SUM(quantity) FROM Foo GROUP BY type, code

让我用简单的英语解释。

我需要最终得到一个Foos列表。每个代码在其组中必须是唯一的,数量必须是其组中具有相同代码的Foos的总和。

我尝试使用groupingBy()summingInt()进行此操作,但无法获得所需的结果。

3 个答案:

答案 0 :(得分:3)

您需要使用groupingBy收藏家,该收藏家根据typecode制作的列表进行分组。这是有效的,因为当所有元素的所有元素相同且顺序相同时,两个列表相等。 (请注意,此处的列表仅用作值的容器,其中相等性定义为包含的所有值的相等性且顺序相同;自定义Tuple类也适用于此处。)

以下代码首先创建一个中间映射Map<List<String>, Integer>,其中键对应于类型和代码列表,值对应于这些类型和代码的所有数量的总和。然后对该地图进行后处理:每个条目都映射到Foo,其中type是密钥的第一个值,quantity是值,code是键的第二个值。

Map<List<String>, Integer> map = 
    myList.stream()
          .collect(Collectors.groupingBy(
             f -> Arrays.asList(f.getType(), f.getCode()),
             Collectors.summingInt(Foo::getQuantity)
          ));

List<Foo> result = 
    map.entrySet()
       .stream()
       .map(e -> new Foo(e.getKey().get(0), e.getValue(), e.getKey().get(1)))
       .collect(Collectors.toList());

请注意,上述解决方案使用了2个不同的Stream管道。这可以使用单个Stream管道完成,但它需要一个能够收集到2个不同收集器的收集器。在这种情况下,第一个收集器将保留第一个分组值,第二个收集器将总和数量。不幸的是,Stream API本身没有这样的收集器。这可以使用包含此类收集器的StreamEx库来完成。

在以下代码中,输入列表仍然按照Foo的类型和代码形成的列表进行分组。收集器有什么变化:我们使用pairing(c1, c2, finisher)将两个收集器配对,并对收集器的结果应用给定的修整器操作。

  • 这里的第一个收集器只是first(),它返回第一个分组Foo(我们不需要其他收集器,因为我们知道它们将具有相同的类型和代码)。它通过调用collectingAndThen来包装,以从Optional收集器中提取first()值(first()返回Optional来处理流的情况是空的,会收集到一个空的Optional;这是不可能的,所以我们可以安全地调用get()
  • 第二个收集器只是将所有数量与summingInt相加。
  • 此处的修整机操作将第一个收集器返回的Foo和总和的结果合并为一个新的Foo新数量。

最后,我们只需要保留结果地图的值。由于Map.values()会返回Collection,因此会将其包装到ArrayList中以获得最终结果。

List<Foo> result = new ArrayList<>(
    myList.stream()
          .collect(Collectors.groupingBy(
              f -> Arrays.<String>asList(f.getType(), f.getCode()),
              MoreCollectors.pairing(
                 Collectors.collectingAndThen(MoreCollectors.first(), Optional::get),
                 Collectors.summingInt(Foo::getQuantity),
                 (f, s) -> new Foo(f.getType(), s, f.getCode())
              )
          )).values());

答案 1 :(得分:1)

我认为这可以一次完成,假设

public class Foo {
    private String type;
    private int quantity;
    private String code;

    public Foo(String type, int quantity, String code) {
        this.type = type;
        this.code = code;
        this.quantity = quantity;
    }

    public String getType() {return type;}
    public int getQuantity() {return quantity;}
    public String getCode() {return code;}
}

List<Foo> foos = new ArrayList<>(); foos.add(new Foo("11", 1, "A")); foos.add(new Foo("11", 2, "A")); foos.add(new Foo("11", 1, "B")); foos.add(new Foo("11", 3, "C")); foos.add(new Foo("12", 2, "A")); foos.add(new Foo("12", 1, "B")); foos.add(new Foo("11", 1, "B")); foos.add(new Foo("13", 1, "C")); foos.add(new Foo("13", 3, "C"));

以下

    foos.stream().collect(
                    toMap(
                            f -> f.getType() + f.getCode(),
                            Function.identity(),
                            (s, a) -> new Foo(
                                        s.getType(), 
                                        s.getQuantity() + a.getQuantity(),
                                        s.getCode()))
                          )
                    .values();        

产生

Foo[type='12'|quantity=2|code='A'] 
Foo[type='13'|quantity=4|code='C'] 
Foo[type='12'|quantity=1|code='B'] 
Foo[type='11'|quantity=3|code='A'] 
Foo[type='11'|quantity=2|code='B'] 
Foo[type='11'|quantity=3|code='C']                       

答案 2 :(得分:0)

使用rxjava帮助:groupBy + flatMap + reduce

    Observable.from(()->fooList.stream().iterator())
    .groupBy(f->Arrays.asList(f.type,f.code))
    .flatMap((Observable<Foo> o)->o
            .reduce((a,b)->new Foo(a.type,a.code,a.quantity+b.quantity))
    )
    .forEach((Foo o) ->
        System.out.println("type:" + o.type + " code:" + o.code +" qty:"+ o.quantity)
    )  ;