【问题标题】:Implementing Java Pivot table using Streams使用 Streams 实现 Java Pivot 表
【发布时间】:2022-01-24 03:39:01
【问题描述】:

这几天我一直在努力解决这个问题。我正在尝试使用 Java Streams 创建 Pivot 功能。我只需要执行 总和、计数、最大值、最小值和平均值。对于输入,我得到一个枢轴列索引、一个枢轴行索引数组以及要计算的值。

关键是数据在 List > 中,其中 Object 可以是 String、Integer 或 Double。但直到运行时我才会知道。我必须将结果返回为 List >。

我在使用 MAX/MIN 时遇到问题(我假设 AVERAGE 类似于 MAX 和 MIN)

为了以多个表值为中心,我创建了一个类来使用我的第二个 groupingBy

这不会编译,我不确定要比较什么,在哪里将对象转换为 int 或者我什至需要。我想用一个流来完成这一切,但我不确定这是否可能。我做错了什么,或者我可以做不同的事情。提前致谢。

package pivot.test;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;

public class PivotTest {

    List<List<Object>> rows = new ArrayList<List<Object>>();

    public PivotTest() throws Exception {

        rows.add(Arrays.asList(new Object[]{ "East", "Boy", "Tee", 10, 12.00}));
        rows.add(Arrays.asList(new Object[]{ "East", "Boy", "Golf", 15, 20.00}));
        rows.add(Arrays.asList(new Object[]{ "East", "Girl", "Tee", 8, 14.00}));
        rows.add(Arrays.asList(new Object[]{ "East", "Girl", "Golf", 20, 24.00}));
        rows.add(Arrays.asList(new Object[]{ "West", "Boy", "Tee", 5, 12.00}));
        rows.add(Arrays.asList(new Object[]{ "West", "Boy", "Golf", 12, 20.00}));
        rows.add(Arrays.asList(new Object[]{ "West", "Girl", "Tee", 15, 14.00}));
        rows.add(Arrays.asList(new Object[]{ "West", "Girl", "Golf", 10, 24.00}));

    }

    // Dynamic Max based upon Column, Value to sum, and an array of pivot rows
    public void MaxTable(int colIdx, int valueIdx, int... rowIdx) {

         Map<Object, Map<Object, Integer>> myList = newRows.stream().collect(
         Collectors.groupingBy(r -> ((List<Object>) r).get(colIdx),
         Collectors.groupingBy( r -> new PivotColumns(r, rowIdx),
         Collectors.collectingAndThen( Collectors.maxBy(Comparator.comparingInt(???)),
                r -> ((List<Object>) r).get(valueIdx)))));

         System.out.println("Dynamic MAX PIVOT"); System.out.println(myList);

    }

    public static void main(String[] args) {

        try {
            PivotTest p = new PivotTest();
            System.out.println("\n\nStreams PIVOT with index values inside a List\n");
            p.MaxTable(0, 3, new int[] { 2 });
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

}

class PivotColumns {

    ArrayList<Object> columns;

    public PivotColumns(
        List<Object> objs, int... pRows) {
        columns = new ArrayList<Object>();

        for (int i = 0; i < pRows.length; i++) {
            columns.add(objs.get(pRows[i]));
        }

    }

    public void addObject(Object obj) {
        columns.add(obj);
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((columns == null) ? 0 : columns.hashCode());
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        PivotColumns other = (PivotColumns) obj;
        if (columns == null) {
            if (other.columns != null)
                return false;
        } else if (!columns.equals(other.columns))
            return false;
        return true;
    }

    public String toString() {
        String s = "";
        for (Object obj : columns) {
            s += obj + ",";
        }

        return s.substring(0, s.lastIndexOf(','));
    }

}

【问题讨论】:

  • 这是一个巨大的问题。您可能想阅读有关提出问题 Minimal 的内容。见stackoverflow.com/help/mcve——我没走多远,但你说你有一个List对象,要么是String,要么是Integer,要么是Double,直到运行时你才会知道,但接着你会继续展示一个定义明确、完全类型化的Row 类。那么它是什么,你知道吗?
  • 必须成为流媒体有什么特别的原因吗?
  • 没有。我对其他不涉及流的解决方案完全开放。

标签: java java-8 pivot java-stream collectors


【解决方案1】:

输入是行的List,每行是列的List,列是StringIntegerDouble,并且不知道要分组哪些列和多少列通过,并且不知道要聚合哪个和什么类型的列,我建议您实现自己的聚合器。

假设所有行都有相同数量的列,并且某一列的所有值将始终是相同类型(或null)。

你想要的基本上是 SQL group-by 语句的 Java 实现:

SELECT Column1, Column2, ...
     , SUM(Column5), MIN(Column5), MAX(Column5), COUNT(Column5)
     , SUM(Column6), MIN(Column6), MAX(Column6), COUNT(Column6)
     , ...
  FROM List<List<Object>>
 GROUP BY Column1, Column2, ...

您需要 3 个课程。第一个是GroupBy 类,它必须实现equals()hashCode() 作为group-by 列的组合equals/hashcode:Column1, Column2, ...

第二个类是Aggregator,它实际上是两个实现一个公共接口的类,一个用于聚合Integer,另一个用于聚合Double。聚合器将被赋予一个值 (Object) 并将累加 sum/min/max/count 值。

第三个类是主类,你称之为Pivot 类。应该告知所需的分组列(带类型)和所需的聚合列(带类型),最好使用builder pattern。然后可以给它数据,并将该数据收集到 HashMap&lt;GroupBy, Aggregator&gt; 中,然后将该结果转换回返回值所需的格式。

如何调用 Pivot 类的示例:

List<List<Object>> input = /*constructed elsewhere*/;

List<List<Object>> output = new Pivot()
     .addGroupByString(0) // Column1
     .addGroupByString(1) // Column2
     .addGroupByInteger(2) // Column3 a group by column can be be a number
     .addIntegerAggregation(4) // Column5
     .addDoubleAggregation(5) // Column6
     .process(input);

或者,如果您并不总是想要所有聚合,则可能是:

     .addIntegerSum(4) // SUM(Column5)
     .addDoubleMin(5) // MIN(Column6)
     .addDoubleMax(5) // MAX(Column6)

这样,Pivot的实现就可以处理任意数量的分组列和聚合列,使用起来非常直观。

【讨论】:

    【解决方案2】:

    由于已知所有可能的值(StringIntegerDouble)都是Comparable,因此您可以对Comparable 接口执行未经检查的强制转换。也不要忘记打开可选的包装。最后,如果我理解正确,结果应该是Map&lt;Object, Map&lt;Object, Object&gt;&gt; myList,而不是Map&lt;Object, Map&lt;Object, Integer&gt;&gt; myList,因为您的列可能包含非整数值:

    public void MaxTable(int colIdx, int valueIdx, int... rowIdx) {
         Map<Object, Map<Object, Object>> myList = newRows.stream().collect(
         Collectors.groupingBy(r -> r.get(colIdx),
         Collectors.groupingBy( r -> new PivotColumns(r, rowIdx),
         Collectors.collectingAndThen( Collectors.maxBy(
             Comparator.comparing(r -> (Comparable<Object>)(((List<Object>) r).get(valueIdx)))),
             r -> r.get().get(valueIdx)))));
    
         System.out.println("Dynamic MAX PIVOT"); System.out.println(myList);
    }
    

    结果:

    > p.MaxTable(0, 3, new int[] { 1 });
    {West={Girl=15, Boy=12}, East={Girl=20, Boy=15}}
    
    > p.MaxTable(0, 4, new int[] { 1 });
    {West={Girl=24.0, Boy=20.0}, East={Girl=24.0, Boy=20.0}}
    

    如您所见,您可以同时处理 IntegerDouble 列。甚至String 也可以处理(将选择字典顺序的最大值)。

    对于平均值,您可以假设您的列值是数字(Number 类,IntegerDouble)并收集到 Double(整数的平均值也可以是非整数):

    public void AverageTable(int colIdx, int valueIdx, int... rowIdx) {
        Map<Object, Map<Object, Double>> myList = newRows.stream().collect(
                Collectors.groupingBy(r -> r.get(colIdx), Collectors
                        .groupingBy(r -> new PivotColumns(r, rowIdx),
                                Collectors.averagingDouble(r -> ((Number) (r
                                        .get(valueIdx))).doubleValue()))));
    
        System.out.println("Dynamic AVG PIVOT"); System.out.println(myList);
    }
    

    输出:

    > p.AverageTable(0, 3, new int[] { 1 });
    {West={Girl=12.5, Boy=8.5}, East={Girl=14.0, Boy=12.5}}
    
    > p.AverageTable(0, 4, new int[] { 1 });
    {West={Girl=19.0, Boy=16.0}, East={Girl=19.0, Boy=16.0}}
    

    【讨论】:

      猜你喜欢
      • 2022-01-22
      • 1970-01-01
      • 2022-01-16
      • 1970-01-01
      • 2018-06-21
      • 1970-01-01
      • 2017-01-09
      • 1970-01-01
      相关资源
      最近更新 更多