フォーラムで同様の質問があったことは知っていますが、どれも私の問題に完全に対処していないようです。今、私はJava 8に慣れていないので、ご容赦ください。次のような製品のリストがあります。
Input:
name category type cost
prod1 cat2 t1 100.23
prod2 cat1 t2 50.23
prod1 cat1 t3 200.23
prod3 cat2 t1 150.23
prod1 cat2 t1 100.23
Output:
Single line (name, category, type) summing the cost and count of products.
Product {
public String name;
public String category;
public String type;
public int id;
public double cost;
}
これを名前、カテゴリ、タイプでグループ化し、このデータを要約して各製品の総コストと数を生成する単一の結果を生成する必要があります。ほとんどの例は、2つのフィールドによるグループ化と、単一の基準を使用した集計を示しています。
フォーラムでの提案に続いて、私はグループ化のためにこれを思いついた:
public class ObjectKeys {
ArrayList<Object> keys;
public ObjectKeys(Object...searchKeys) {
keys = new ArrayList<Object>();
for (int i = 0; i < searchKeys.length; i++) {
keys.add( searchKeys[i] );
}
}
}
次に、次のように使用しました。
Map<String, Map<String, Map<String, List<Product>>>> productsByNameCategoryType =
products.stream().collect(groupingBy(new ObjectKeys(l.name(), l.category(),l.type())))
しかし、カウントと合計を上記のコードにチェーンするにはどうすればよいですか?特に2つ以上のフィールドを持つgroupbyの場合。これを行うためのより良い方法はありますか?
私が言ったように、私のJava8はそれほど良くありません、助けてください。
class Product {
public String name;
public String category;
public String type;
public int id;
//todo:implement equals(), toString() and hashCode()
}
class Item{
public Product product;
public double cost;
}
Collectors#groupingBy & Collectors#summarizingDouble を使用して、商品ごとにグループ化されたアイテムを要約できます。
List<Item> items = ...;
Map<Product, DoubleSummaryStatistics> stat = items.stream().collect(groupingBy(
it -> it.product,
Collectors.summarizingDouble(it -> it.cost)
));
// get some product summarizing
long count = stat.get(product).getCount();
double sum = stat.get(product).getSum();
//list all product summarizing
stat.entrySet().forEach(it ->
System.out.println(String.format("%s - count: %d, total cost: %.2f"
, it.getKey(),it.getValue().getCount(), it.getValue().getSum()));
);
まず、qty
クラスにItem
フィールドを追加する必要があります。
class Item{
public int qty;
//other fields will be omitted
public Item add(Item that) {
if (!Objects.equals(this.product, that.product)) {
throw new IllegalArgumentException("Can't be added items"
+" with diff products!");
}
return from(product, this.cost + that.cost, this.qty + that.qty);
}
private static Item from(Product product, double cost, int qty) {
Item it = new Item();
it.product = product;
it.cost = cost;
it.qty = qty;
return it;
}
}
次に、 Collectors#toMap を使用して、アイテムを同じ商品とマージできます。
Collection<Item> summarized = items.stream().collect(Collectors.toMap(
it -> it.product,
Function.identity(),
Item::add
)).values();
両方の方法で同じことを行うことがわかりますが、2番目のアプローチはストリームでの操作が簡単です。 githubでチェックインした2つの方法のテストでは、クリックして詳細を確認できます。 アイテムの要約 & アイテムのマージ 方法。
迅速で汚い解決策は次のとおりです。
_ Map<String, String> productsByNameCategoryType = products.stream()
.collect(Collectors.groupingBy(p
-> p.getName() + '-' + p.getCategory() + '-' + p.getType(),
Collectors.collectingAndThen(
Collectors.summarizingDouble(Product::getCost),
dss -> String.format("%7.2f%3d",
dss.getSum(), dss.getCount()))));
_
結果マップのキーと値の両方に対して独自のクラスを作成することをお勧めします。いずれにせよ、データと上記のコードを使用すると、マップには4つのエントリが含まれます。
_prod1-cat1-t3: 200,23 1
prod1-cat2-t1: 200,46 2
prod3-cat2-t1: 150,23 1
prod2-cat1-t2: 50,23 1
_
私のコンピューターにはデンマーク語のロケールがあるため、合計は小数点としてコンマで出力されます(必要に応じて、ロケールをString.format()
に渡してロケールを制御できます)。
あなたの友達はCollectors.collectingAndThen()
とCollectors.summarizingDouble()
の組み合わせです。私はそれを この答え から取った。