使用STREAMS实现Java透视表

2022-07-25 00:00:00 pivot java-8 java java-stream collectors

我已经在这个问题上挣扎了几天了。我正在尝试使用Java Streams创建透视功能。我只需要执行 总和、计数、最大值、最小值和平均值。对于输入,我得到一个透视表列索引、一个透视表行索引数组和要计算的值。

问题是数据在列表中,其中对象可以是字符串、整数或双精度。但我要到运行时才能知道。我必须以List<;List<;Object>的形式返回我的结果。

我遇到了MAX/MIN问题(我假设平均值将类似于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("

Streams PIVOT with index values inside a List
");
            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(','));
    }

}

解决方案

由于已知所有可能的值(StringIntegerDouble)都是Comparable,因此您可以对Comparable接口执行未经检查的强制转换。此外,不要忘记打开可选的包装。最后,如果我理解正确的话,结果应该是Map<Object, Map<Object, Object>> myList,而不是Map<Object, Map<Object, Integer>> 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}}

相关文章