2015-09-14 691 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實現可以按列和聚合列處理任意數量的組,並且使用它非常直觀。