web-dev-qa-db-ja.com

Java lambdas?

これらのフィールドを持つクラスFooがあります:

id:int/name; String/targetCost:BigDecimal/actualCost:BigDecimal

このクラスのオブジェクトの配列リストを取得します。例えば。:

new Foo(1, "P1", 300, 400), 
new Foo(2, "P2", 600, 400),
new Foo(3, "P3", 30, 20),
new Foo(3, "P3", 70, 20),
new Foo(1, "P1", 360, 40),
new Foo(4, "P4", 320, 200),
new Foo(4, "P4", 500, 900)

「targetCost」と「actualCost」の合計を作成し、「行」をグループ化してこれらの値を変換したい.

new Foo(1, "P1", 660, 440),
new Foo(2, "P2", 600, 400),
new Foo(3, "P3", 100, 40),
new Foo(4, "P4", 820, 1100)

今までに書いたこと:

data.stream()
       .???
       .collect(Collectors.groupingBy(PlannedProjectPOJO::getId));

どうやってやるの?

37
haisi

Collectors.groupingByを使用するのが正しい方法ですが、各グループのすべての項目のリストを作成する単一引数バージョンを使用する代わりに、 2つの引数バージョン を使用して別のCollector各グループの要素を集約する方法を決定します。

これは、要素の単一のプロパティを集約する場合、またはグループごとに要素の数をカウントする場合に特にスムーズです。

  • カウント:

    list.stream()
      .collect(Collectors.groupingBy(foo -> foo.id, Collectors.counting()))
      .forEach((id,count)->System.out.println(id+"\t"+count));
    
  • 1つのプロパティをまとめる:

    list.stream()
      .collect(Collectors.groupingBy(foo -> foo.id,
                                        Collectors.summingInt(foo->foo.targetCost)))
      .forEach((id,sumTargetCost)->System.out.println(id+"\t"+sumTargetCost));
    

あなたのケースでは、カスタムリダクション操作を指定する複数のプロパティを集約する場合 この回答で提案されているように が正しいアプローチですが、グループ化操作中にリダクションを実行できるため、削減を実行する前に、データ全体をMap<…,List>に収集する必要があります。

(今、import static Java.util.stream.Collectors.*;を使用していると思います…)

list.stream().collect(groupingBy(foo -> foo.id, collectingAndThen(reducing(
  (a,b)-> new Foo(a.id, a.ref, a.targetCost+b.targetCost, a.actualCost+b.actualCost)),
      Optional::get)))
  .forEach((id,foo)->System.out.println(foo));

完全を期すために、ここでは質問の範囲を超える問題の解決策を示します。複数の列/プロパティをGROUP BYしたい場合はどうすればよいですか?

プログラマーの心に飛び込む最初のことは、groupingByを使用して、ストリームの要素のプロパティを抽出し、新しいキーオブジェクトを作成/返すことです。ただし、これにはキープロパティに適切なホルダークラスが必要です(およびJavaには汎用のTupleクラスはありません)。

しかし、代替手段があります。 引数形式のgroupingBy を使用することにより、キーの等価性を決定する実際のMap実装のサプライヤを指定できます。複数のプロパティを比較するコンパレータで並べ替えられたマップを使用することにより、追加のクラスを必要とせずに目的の動作が得られます。コンパレータが無視したキーインスタンスのプロパティを使用しないように注意する必要があります。これらは、任意の値を持っているだけです。

list.stream().collect(groupingBy(Function.identity(),
  ()->new TreeMap<>(
    // we are effectively grouping by [id, actualCost]
    Comparator.<Foo,Integer>comparing(foo->foo.id).thenComparing(foo->foo.actualCost)
  ), // and aggregating/ summing targetCost
  Collectors.summingInt(foo->foo.targetCost)))
.forEach((group,targetCostSum) ->
    // take the id and actualCost from the group and actualCost from aggregation
    System.out.println(group.id+"\t"+group.actualCost+"\t"+targetCostSum));
66
Holger

考えられるアプローチの1つを次に示します。

public class Test {
    private static class Foo {
        public int id, targetCost, actualCost;
        public String ref;

        public Foo(int id, String ref, int targetCost, int actualCost) {
            this.id = id;
            this.targetCost = targetCost;
            this.actualCost = actualCost;
            this.ref = ref;
        }

        @Override
        public String toString() {
            return String.format("Foo(%d,%s,%d,%d)",id,ref,targetCost,actualCost);
        }
    }

    public static void main(String[] args) {
        List<Foo> list = Arrays.asList(
            new Foo(1, "P1", 300, 400), 
            new Foo(2, "P2", 600, 400),
            new Foo(3, "P3", 30, 20),
            new Foo(3, "P3", 70, 20),
            new Foo(1, "P1", 360, 40),
            new Foo(4, "P4", 320, 200),
            new Foo(4, "P4", 500, 900));

        List<Foo> transform = list.stream()
            .collect(Collectors.groupingBy(foo -> foo.id))
            .entrySet().stream()
            .map(e -> e.getValue().stream()
                .reduce((f1,f2) -> new Foo(f1.id,f1.ref,f1.targetCost + f2.targetCost,f1.actualCost + f2.actualCost)))
                .map(f -> f.get())
                .collect(Collectors.toList());
        System.out.println(transform);
    }
}

出力:

[Foo(1,P1,660,440), Foo(2,P2,600,400), Foo(3,P3,100,40), Foo(4,P4,820,1100)]
15
Dici
data.stream().collect(toMap(foo -> foo.id,
                       Function.identity(),
                       (a, b) -> new Foo(a.getId(),
                               a.getNum() + b.getNum(),
                               a.getXXX(),
                               a.getYYY()))).values();

toMap()を使用するだけで、非常に簡単です

4
user1241671

JDKのStream AP​​Iのみでこれを行うのは、他の答えが示しているように、本当に簡単ではありません。 この記事では、GROUP BY in Java 8 (標準の集約関数を使用)および jOOλ を使用して、これらのユースケースにStreamを拡張するライブラリ。

書きます:

import static org.jooq.lambda.Tuple.Tuple.Tuple;

import Java.util.List;
import Java.util.stream.Collectors;

import org.jooq.lambda.Seq;
import org.jooq.lambda.Tuple.Tuple;
// ...

List<Foo> list =

// FROM Foo
Seq.of(
    new Foo(1, "P1", 300, 400),
    new Foo(2, "P2", 600, 400),
    new Foo(3, "P3", 30, 20),
    new Foo(3, "P3", 70, 20),
    new Foo(1, "P1", 360, 40),
    new Foo(4, "P4", 320, 200),
    new Foo(4, "P4", 500, 900))

// GROUP BY f1, f2
.groupBy(
    x -> Tuple(x.f1, x.f2),

// SELECT SUM(f3), SUM(f4)
    Tuple.collectors(
        Collectors.summingInt(x -> x.f3),
        Collectors.summingInt(x -> x.f4)
    )
)

// Transform the Map<Tuple2<Integer, String>, Tuple2<Integer, Integer>> type to List<Foo>
.entrySet()
.stream()
.map(e -> new Foo(e.getKey().v1, e.getKey().v2, e.getValue().v1, e.getValue().v2))
.collect(Collectors.toList());

呼び出し中

System.out.println(list);

その後、降伏します

[Foo [f1=1, f2=P1, f3=660, f4=440],
 Foo [f1=2, f2=P2, f3=600, f4=400], 
 Foo [f1=3, f2=P3, f3=100, f4=40], 
 Foo [f1=4, f2=P4, f3=820, f4=1100]]
2
Lukas Eder
public  <T, K> Collector<T, ?, Map<K, Integer>> groupSummingInt(Function<? super T, ? extends K>  identity, ToIntFunction<? super T> val) {
    return Collectors.groupingBy(identity, Collectors.summingInt(val));
}
0
Shylock.Gou