2014-04-05 35 views
5

Vorrei creare una classe semplice applicando statistiche comuni usando l'espressione lambda. Mi chiedo come posso evitare di utilizzare l'interruttore caso nel metodo statistico()?Java 8 refactoring lambda expressions

Per esempio, io può decidere di scrivere un nuovo lambda per calcolare la varianza della lista, ecc

Grazie.

public class DescriptiveStatistics { 

    public static void main(String[] args) { 
     List<Double> numbers = Arrays.asList(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0); 
     numbers.stream().forEach(n-> System.out.print(n + " ")); 
     System.out.println(); 
     System.out.println("Descriptive statistics"); 
     System.out.println("Sum: " + statistic(numbers, "Sum")); 
     System.out.println("Max: " + statistic(numbers, "Max")); 
     System.out.println("Min: " + statistic(numbers, "Min")); 
     System.out.println("Average: " + statistic(numbers, "Average")); 
     System.out.println("Count: " + statistic(numbers, "Count")); 
    } 

    private static double statistic(List<Double> numbers, String function) { 
     switch (function.toLowerCase()) { 
      case "sum": 
       return numbers.stream().mapToDouble(Double::doubleValue).sum(); 
      case "max": 
       return numbers.stream().mapToDouble(Double::doubleValue).max().getAsDouble(); 
      case "min": 
       return numbers.stream().mapToDouble(Double::doubleValue).min().getAsDouble(); 
      case "average": 
       return numbers.stream().mapToDouble(Double::doubleValue).average().getAsDouble(); 
      case "count": 
       return numbers.stream().mapToDouble(Double::doubleValue).count(); 
     } 
     return 0; 
    } 

che ho in mente di un metodo come questo

private static double newStatistics(List<Double> numbers, Function<Double, Double> function){ 
     return numbers.stream().mapToDouble(Double::doubleValue).function(); 
    } 
+0

Si deve usare sia un 'enum' (se si conosce in anticipo tutte le funzioni) o un'interfaccia di strategia (se è necessario essere in grado di inserirne di nuove in fase di runtime). – chrylis

+0

Penso che l'interfaccia della strategia risponda alla domanda. Grazie! – CheJharia

risposta

11

Perché non è sufficiente utilizzare DoubleStream#summaryStatistics o applicare un modello simile?

Si potrebbe anche estendere la classe di aggiungere metodi personalizzati, dire una varianza, asimmetria e curtosi per esempio:

/** 
* Algorithms derived from: Philippe Pébay, Formulas for Robust, One-Pass Parallel 
* Computation of Covariances and Arbitrary-Order Statistical Moments. 
*/ 
public class MoreDoubleStatistics extends DoubleSummaryStatistics { 

    private double M1, M2, M3, M4; 

    @Override 
    public void accept(double x) { 
     super.accept(x); 

     long n = getCount(); 

     double delta = x - M1;      // δ 
     double delta_n = delta/n;     // δ/n 
     double delta2_n = delta * delta_n;   // δ^2/n 
     double delta2_n2 = delta_n * delta_n;  // δ^2/n^2 
     double delta3_n2 = delta2_n * delta_n;  // δ^3/n^2 
     double delta4_n3 = delta3_n2 * delta_n;  // δ^4/n^3 

     M4 += (n - 1) * (n * n - 3 * n + 3) * delta4_n3 
       + 6 * M2 * delta2_n2 
       - 4 * M3 * delta_n; 
     M3 += (n - 1) * (n - 2) * delta3_n2 
       - 3 * M2 * delta_n; 
     M2 += (n - 1) * delta2_n; 
     M1 += delta_n; 
    } 

    @Override 
    public void combine(DoubleSummaryStatistics other) { 
     throw new UnsupportedOperationException(
       "Can't combine a standard DoubleSummaryStatistics with this class"); 
    } 

    public void combine(MoreDoubleStatistics other) { 
     MoreDoubleStatistics s1 = this; 
     MoreDoubleStatistics s2 = other; 

     long n1 = s1.n(); 
     long n2 = s2.n(); 
     long n = n1 + n2; 

     double delta = s2.M1 - s1.M1;    // δ 
     double delta_n = delta/n;     // δ/n 
     double delta2_n = delta * delta_n;   // δ^2/n 
     double delta2_n2 = delta_n * delta_n;  // δ^2/n^2 
     double delta3_n2 = delta2_n * delta_n;  // δ^3/n^2 
     double delta4_n3 = delta3_n2 * delta_n;  // δ^4/n^3 

     this.M4 = s1.M4 + s2.M4 + n1 * n2 * (n1 * n1 - n1 * n2 + n2 * n2) * delta4_n3 
       + 6.0 * (n1 * n1 * s2.M2 + n2 * n2 * s1.M2) * delta2_n2 
       + 4.0 * (n1 * s2.M3 - n2 * s1.M3) * delta_n; 

     this.M3 = s1.M3 + s2.M3 + n1 * n2 * (n1 - n2) * delta3_n2 
       + 3.0 * (n1 * s2.M2 - n2 * s1.M2) * delta_n; 

     this.M2 = s1.M2 + s2.M2 + n1 * n2 * delta2_n; 

     this.M1 = s1.M1 + n2 * delta; 

     super.combine(other); 
    } 

    private long n() { return getCount(); } 

    public double mean() { return getAverage(); } 
    public double variance() { return n() <= 1 ? 0 : M2/(n() - 1); } 
    public double stdDev() { return sqrt(variance()); } 
    public double skew() { return M2 == 0 ? 0 : sqrt(n()) * M3/ pow(M2, 1.5); } 
    public double kurtosis() { return M2 == 0 ? 0 : n() * M4/(M2 * M2) - 3.0; } 
} 
+0

potresti fornire il codice per una funzione combinata, in modo che questa classe possa essere utilizzata come un raccoglitore? –

+0

ma non varianza l'inclinazione e la curtosi si sbaglia nel risultato finale? Intendo DoubleSummaryStatistics :: combine non ne tiene conto, o mi manca qualcosa? –

+0

@RolandGude Buon punto hai assolutamente ragione. Modificato. – assylias

8

Sostituire il String parametro del metodo statistica con un tipo di funzione di, che prende un DoubleStream e ritorna l'aggregato.

private static double statistic(List<Double> numbers, 
           ToDoubleFunction<DoubleStream> function) { 
    return function.applyAsDouble(
     numbers.stream().mapToDouble(Double::doubleValue)); 
} 

Ora, è possibile richiamare il metodo come segue, senza utilizzare un interruttore economico per le diverse operazioni sul torrente:

System.out.println("Sum: " + statistic(numbers, s -> s.sum())); 
System.out.println("Max: " + statistic(numbers, s -> s.max().getAsDouble())); 
System.out.println("Min: " + statistic(numbers, s -> s.min().getAsDouble())); 
System.out.println("Average: " + statistic(numbers, s -> s.average().getAsDouble())); 
System.out.println("Count: " + statistic(numbers, s -> s.count())); 
+1

Grazie. E dove potrei leggere di più sulla creazione del mio ToDoubleFunction()? Ad esempio, per calcolare la varianza. – CheJharia

+0

Invece di 's -> s.sum()', puoi semplificarlo in 'DoubleStream :: sum'. Lo stesso vale per 's -> s.count()'. – bcsb1001

+0

@ bcsb1001: A mio parere, 's -> s.sum()' è più semplice di 'DoubleStream :: sum' (riguardo alla leggibilità e alla manutenibilità). – nosid