2015-09-14 689 views
8

我一直在为这个问题奋斗了几天。我正在尝试使用Java Streams创建Pivot功能。我只需要执行 SUM,COUNT,MAX,MIN和AVERAGE。对于输入,我给出了一个数据透视列索引,一个数据透视表索引,以及要计算的值。使用流实现Java数据透视表

捕获的数据是在列表中<列表< Object >>,其中Object是String,Integer或Double。但直到运行时才会知道。我必须返回我的结果列表<列表<对象>>。

我有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(',')); 
    } 

} 
+0

这是一个巨大的问题。您可能想要阅读有关提问*最小*。请参阅http://stackoverflow.com/help/mcve ---我没有走得太远,但是你说你有一个'List'对象,可以是'String','Integer'或'Double',直到运行时才会知道,但随后您将继续显示一个定义良好的完全键入的“Row”类。那么,你知道与否? – Andreas

+0

任何特殊原因,它*有*为流? – Andreas

+0

不,我完全接受其他不涉及流的解决方案。 –

回答

3

由于所有可能的值(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}} 
0

随着输入是行的List中,每行是一个List列和列作为一个StringIntegerDouble,并且不知道要分组的列数和列数,也不知道要合并哪些列和哪种类型的列,我建议实施您自己的聚合器。

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

你想要什么就基本的Java实现一个SQL组按声明:

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()作为组合等于基的由列/哈希码:列1,列2,...

第二类是Aggregator,这实际上是实施两个类一个共同的界面,一个类聚合Integer和另一个聚合Double。聚合器将被赋予一个值(Object)并累计总和/最小值/最大值/计数值。

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

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实现可以按列和聚合列处理任意数量的组,并且使用它非常直观。