2015-09-14 26 views
8

Sono stato alle prese con questo problema per alcuni giorni. Sto cercando di creare la funzionalità di Pivot utilizzando i flussi Java. Devo solo implementare SUM, COUNT, MAX, MIN e MEDIA. Per l'input mi viene fornito un indice di colonna pivot, una matrice di indici di riga pivot e il valore da calcolare.Implementazione della tabella pivot Java utilizzando gli stream

Il catch è che i dati sono in un elenco < Elenco < Oggetto >>, dove l'oggetto può essere String, Integer o Double. ma non lo saprò fino al runtime. E devo restituire i risultati come Elenco < Elenco < Oggetto >>.

Ho problemi con MAX/MIN (Sto supponendo che MEDIA sarà simile a MAX e MIN)

Per ruotare su più valori di tabella, ho creato una classe di utilizzare la mia seconda groupingBy

Questo non verrà compilato, non sono sicuro su cosa confrontare, in cui convertire l'oggetto in int o se è necessario. Mi piacerebbe farlo tutto con un flusso, ma non sono sicuro che sia possibile. Cosa sto facendo di sbagliato, o potrei farlo in modo diverso. Grazie in anticipo.

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

Questa è una * enorme * domanda. Potresti voler leggere le domande * Minime *. Vedi http://stackoverflow.com/help/mcve --- Non sono andato molto lontano, ma hai detto che hai un 'Elenco' di oggetti, sia' String', 'Integer', o' Double' e che non lo saprai fino al runtime, ma poi continuerai a mostrare una classe 'Row' ben definita e completamente digitata. Quindi, qual è, lo sai o no? – Andreas

+0

Un motivo particolare per cui * deve * essere flussi? – Andreas

+0

No. Sono completamente aperto ad altre soluzioni che non coinvolgono flussi. –

risposta

3

Come tutti i possibili valori (String, Integer, Double) sono noti per essere Comparable, è possibile eseguire un cast incontrollato all'interfaccia Comparable. Inoltre, non dimenticare di disimballare l'opzionale. Infine, se ho capito bene, il risultato dovrebbe essere Map<Object, Map<Object, Object>> myList, non Map<Object, Map<Object, Integer>> myList, come la colonna può avere valori non interi:

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); 
} 

risultati:

> 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}} 

Come si può vedere, è possibile gestire entrambi Colonna Integer e Double. È possibile gestire anche String (verrà selezionato il valore lessicografico massimo).

Per il calcolo della media si può presumere che i valori della colonna sono numeri (Number classe, sia Integer o Double) e raccogliere per Double (media di interi può essere non intero pure):

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); 
} 

uscita :

> 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

Con in ingresso un List di righe, ciascuna fila essendo un List di colonne, e colonna essendo un String , Integer, o Double, e non sapendo quale e quante colonne raggruppare, e non sapendo quale e quale tipo di colonna aggregare, suggerirei di implementare il proprio aggregatore.

Presumibilmente, tutte le righe hanno lo stesso numero di colonne e tutti i valori di una determinata colonna saranno sempre dello stesso tipo (o null).

Ciò che si vuole è fondamentalmente un'implementazione Java di un gruppo SQL-by dichiarazione:

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, ... 

avete bisogno di 3 classi.La prima è la classe GroupBy, che deve implementare equals() e hashCode() come uguali combinati/hashcode del gruppo-da colonne: column1, colonna2, ...

La seconda classe è il Aggregator, che è in realtà due classi di attuazione un'interfaccia comune, una classe per l'aggregazione di Integer e un'altra per l'aggregazione di Double. Aggregatore riceverà un valore (Object) e accumulerà i valori somma/min/max/conteggio.

La terza classe è la classe principale, quella che viene chiamata la classe Pivot. Dovrebbe essere detto circa le colonne di raggruppamento desiderate (con tipo) e le colonne di aggregazione desiderate (con tipo), preferibilmente usando un builder pattern. È quindi possibile fornire i dati e raccogliere tali dati in un HashMap<GroupBy, Aggregator>, quindi convertire tali risultati nel formato necessario per il valore restituito.

Esempio di come richiamare la classe 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); 

Oppure, se non si vuole sempre tutte le aggregazioni, potrebbe essere:

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

Con questo, l'implementazione di Pivot può gestire un numero qualsiasi di gruppi per colonne e colonne aggregate e usarlo è molto intuitivo.