【问题标题】:Nesting Values with Java 8 stream and collector使用 Java 8 流和收集器嵌套值
【发布时间】:2018-11-14 04:03:48
【问题描述】:

我有以下数据集来表示销售记录:

 sn|    Channel    | Category   | Brand             |qty    |  gross |          
 1 |"Mini Market" | "Large MM" | "ARIEL"            |3      | 100 |
 2 |"Mini Market" | "Large MM" | "ARIEL"            |6      | 200| 
 3 |"Mini Market" | "Large MM" | "GILLETTE"         |12     | 103.88| 
 4 |"Mini Market" | "Large MM" | "OLAY"             |2      | 50  | 
 5 |"Mini Market" | "Large MM" | "OLAY"             |6      | 10| 
 6 |"Mini Market" | "Small MM" | "GILLETTE"         |5      | 20 |
 7 |"Mini Market" | "Small MM" | "GILLETTE"         |3      | 30| 
 8 |"Mini Market" | "Small MM" | "OLAY"             |3      | 80.3  |
 9 |"Mini Market" | "Small MM" | "ORAL B"           |6      | 100 |
 10|"Mini Market" | "Small MM" | "ORAL B"           |7      | 150 |

POJO 类:

class SalesRecord{
    private String channel;
    private String category;
    private String brand;
    private int qty;
    private double gross;

    //getters and setters


}

class PivotTable {
    Map<Integer,Set<String>> uniqueAttirbuteMap;
    List<Pivot> pivot;
    //other fields and methods

}

class Pivot {
    public String attribute;
    Map<String, Double> aggregates;
    List<Pivot> pivotList;
    //other fields and methods

}

自定义收集器类:

public class SalesCollector implements Collector<SalesRecord,
    SalesCollector.Accumulator, PivotTable> {
    private static final String GROSS_SUM = "sum_gross";
    private static final String QTY_SUM = "sum_qty"

    public List<Double> prices = Lists.newArrayList();
    public List<Double> qtys = Lists.newArrayList();

    public double totalSales = 0;
    public double totalQty = 0;

     public SalesCollector(final List<GroupBy> groups) {
        this.groups = groups;
    }


    @Override
    public Supplier<Accumulator> supplier() {
        return () -> new Accumulator(this.groups, this);
    }

    @Override
    public BiConsumer<Accumulator, SalesRecord> accumulator() {
        return Accumulator::accumulate;
    }

    @Override
    public BinaryOperator<Accumulator> combiner() {
        return Accumulator::combine;
    }

    @Override
    public Function<Accumulator, PivotTable> finisher() {
        return Accumulator::toSummary;
    }

    static class Accumulator {

        private final List<GroupBy> groups;
        private final SalesCollector collector;
        PivotTable pivotTable = new PivotTable();

        Accumulator(final List<GroupBy> groups, final SalesCollector collector) {
            this.groups = groups;
            this.collector = collector;
        }

         void accumulate(SalesRecord elem) {
          double sum = pivotTable.getAggregates().getOrDefault(GROSS_SUM, 0D) + elem.getGross();
            pivotTable.getAggregates().put(GROSS_SUM, sum);

             double qtySum = pivotTable.getAggregates().getOrDefault(QTY_SUM, 0D) + elem.getQty();
            pivotTable.getAggregates().put(QTY_SUM, qtySum);
        }

         Accumulator combine(Accumulator other) {

            final PivotTable summary = other.toSummary();

            double sum_qty =
                pivotTable.getAggregates().get(QTY_SUM) + summary.getAggregates().get(QTY_SUM);
            pivotTable.getAggregates().put(QTY_SUM, sum_qty);

            double sum_gross =
                pivotTable.getAggregates().get(GROSS_SUM) + summary.getAggregates().get(GROSS_SUM);
            pivotTable.getAggregates().put(GROSS_SUM, sum_gross);

            return this;
            }

            PivotTable toSummary() {
            double sumGross = pivotTable.getAggregates().get(GROSS_SUM);
            collector.prices.add(sumGross);
            collector.totalSales += sumGross;


            double sumQty = pivotTable.getAggregates().get(QTY_SUM);
            collector.qtys.add(sumQty);
            collector.totalQty += sumQty;

            return pivotTable;
        }
    }
}

目前我正在使用流和 gropuing 并有一个自定义收集器来计算 qty 和 Gross 的值,如下所示:

SalesCollector collector = new SalesCollector(groups);
        final Map<String, Map<String, Map<String, PivotTable>>> results = salesRecords.stream()
            .collect(groupingBy(SalesRecord::getChannel(), TreeMap::new,
                groupingBy(SalesRecord::getCategoryName(), TreeMap::new,
                    groupingBy(SalesRecord::getBrand(), TreeMap::new, collector))));

List<PivotTable> myList = results.values().stream()
            .map(Map::values)
            .flatMap(Collection::stream)
            .map(Map::values)
            .flatMap(Collection::stream)
            .collect(Collectors.toList());

我目前的结果如下:

PivotTable(pivot:[Pivot(attribute:Mini Market), Pivot(attribute:Large MM), Pivot(attribute:ARIEL, aggregates:{ sum_qty=9, sum_gross=300 })])
PivotTable(pivot:[Pivot(attribute:Mini Market), Pivot(attribute:Large MM), Pivot(attribute:GILLETTE, aggregates:{ sum_qty = 12, sum_gross= 103.88})])
PivotTable(pivot:[Pivot(attribute:Mini Market), Pivot(attribute:Large MM), Pivot(attribute:OLAY, aggregates:{ sum_qty = 8, sum_gross= 60})])
PivotTable(pivot:[Pivot(attribute:Mini Market), Pivot(attribute:Small MM), Pivot(attribute:OLAY, aggregates:{ sum_qty = 3, sum_gross= 80.3})])
PivotTable(pivot:[Pivot(attribute:Mini Market), Pivot(attribute:Small MM), Pivot(attribute:GILLETTE, aggregates:{ sum_qty = 8, sum_gross= 50})])
PivotTable(pivot:[Pivot(attribute:Mini Market), Pivot(attribute:Small MM), Pivot(attribute:ORAL B, aggregates:{ sum_qty = 13, sum_gross= 250})])

我想要达到的目标如下:

PivotTable(pivot:[Pivot(attribute:Mini Market), Pivot(attribute:Large MM), pivotList:[Pivot(attribute:ARIEL, aggregates:{ sum_qty=9, sum_gross=300 }),
Pivot(attribute:GILLETTE, aggregates:{ sum_qty = 12, sum_gross= 103.88}),Pivot(attribute:OLAY, aggregates:{ sum_qty = 8, sum_gross= 60})])
PivotTable(pivot:[Pivot(attribute:Mini Market), Pivot(attribute:Small MM), Pivot(attribute:GILLETTE, aggregates:{ sum_qty = 8, sum_gross= 50})]),
Pivot(attribute:OLAY, aggregates:{ sum_qty = 3, sum_gross= 80.3}),Pivot(attribute:ORAL B, aggregates:{ sum_qty = 13, sum_gross= 250})])

简单来说,我想嵌套如下:

Mini Market:
    Large MM:
        ARIEL: {sum_qty = 9, sum_gross= 300}
        GILLETTE: {sum_qty = 12, sum_gross= 103.88}
        OLAY: {sum_qty = 8, sum_gross= 60}

Mini Market:
    Small MM:
        GILLETTE: {sum_qty = 8, sum_gross= 50}
        OLAY: {sum_qty = 3, sum_gross= 80.3}
        ORAL B: {sum_qty = 13, sum_gross= 250}

是否可以通过相同分组的电流收集器结果本身来实现这一点?实现这一目标的最佳方法是什么?

【问题讨论】:

  • 您能分享一下PivotTableSalesRecord POJO 吗?另外,您现在期望的结果的数据类型是什么?
  • @nullpointer 我已经添加了 POJO。结果预期id列表的数据类型

标签: java java-stream collectors


【解决方案1】:

我通过缺少的collector 扩展了您的信息流。这是一个专用的Collector,它将每个SalesRecord 映射到Map&lt;String, Double&gt;。由于Map 有一种值类型,我决定使用Double

    Supplier<Map<String, Double>> supplier = TreeMap::new;
    BiConsumer<Map<String, Double>, SalesRecord> biConsumer = (map, sr) -> {
        map.merge("sum_qty", Double.valueOf(sr.getQty()), (qtySum, qty) -> qtySum + qty);
        map.merge("sum_gross", sr.getGross(), (grossSum, gross) -> grossSum + gross);
    };
    BinaryOperator<Map<String, Double>> binaryOperator = (l, r) -> {
        l.compute("sum_qty", (k, v) -> v + r.get("sum_qty"));
        l.compute("sum_gross", (k, v) -> v + r.get("sum_gross"));
        return l;
    };
    Collector<SalesRecord, Map<String, Double>, Map<String, Double>> collector = Collector.of(supplier, biConsumer, binaryOperator);

现在作为最后一个下游收集器添加到流中:

Map<String, Map<String, Map<String, Map<String, Double>>>> grouped = salesRecords.stream().collect(Collectors.groupingBy(
        SalesRecord::getChannel, TreeMap::new,
        Collectors.groupingBy(SalesRecord::getCategory, TreeMap::new,
                Collectors.groupingBy(SalesRecord::getBrand, collector))));

结果是Map&lt;String, Map&lt;String, Map&lt;String, Map&lt;String, Double&gt;&gt;&gt;&gt; grouped,看起来像这样(添加换行符以提高可读性):

{Mini Market=   
               {Large MM={
                            OLAY={sum_gross=60.0, sum_qty=8.0}, 
                            ARIEL={sum_gross=300.0, sum_qty=9.0},
                            GILLETTE={sum_gross=103.88, sum_qty=12.0}},
                Small MM={
                            OLAY={sum_gross=80.3, sum_qty=3.0},
                            ORAL B={sum_gross=250.0, sum_qty=13.0},
                            GILLETTE={sum_gross=50.0, sum_qty=8.0}}
                }
}

【讨论】:

  • 据我所知SalesCollector / Accumulator 建立总和。你为什么要在SalesCollectorAccumulator 之间分离代码?他们是tightly coupled
  • 您问“是否有可能通过相同分组的电流收集器结果本身来实现这一目标?”我做到了。并且:“实现这一目标的最佳方法是什么?”我不想说,我的回答显示了最佳方式,但我认为显示的代码比SalesCollector / Accumulator 的开销更少。
  • 所以我现在的问题是如何将输出作为 List 而不是 Map>>> where ' Pivot 的属性'是渠道/类别/品牌的值,聚合是总和值?
  • 我不确定我是否理解你的问题。变量grouped 对应于Pivot 的字段aggregates。但是不知道Pivot.attribute的目的是什么。
  • 这是因为分组可以分为多个级别,而不仅仅是三个。如果我要简单地将结果作为 Map 返回,那么我需要根据分组级别将结果设置为多个 Map 变量。例如,对于 2 lvl 分组 Map> 对于 3 将是 Map>>>。相反,我想要实现的是将结果作为 list 返回,而不管分组级别如何。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-03-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-09-15
相关资源
最近更新 更多