2014-04-05 2 views
5

Ich möchte eine einfache Klasse erstellen, die gemeinsame Statistiken mit Lambda-Ausdruck anwenden. Ich frage mich, wie kann ich vermeiden, den Schalter Fall in der Methode statistic() verwenden?Java 8 Refactoring Lambda-Ausdrücke

Zum Beispiel kann ich möchte eine neue Lambda schreiben, um die Varianz der Liste zu berechnen usw.

Danke.

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

Ich denke dabei an ein Verfahren wie dieses

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

Sie entweder eine 'enum' verwenden sollten (wenn Sie im Voraus wissen, alle Funktionen) oder eine Strategy-Oberfläche (wenn Sie zur Laufzeit neue hinzufügen müssen). – chrylis

+0

Ich denke, dass die Strategie-Schnittstelle die Frage beantwortet. Vielen Dank! – CheJharia

Antwort

11

Warum nicht einfach DoubleStream#summaryStatistics verwenden oder ein ähnliches Muster anwenden?

Sie könnten sogar die Klasse erweitern benutzerdefinierte Methoden hinzufügen, sagen eine Varianz, Schiefe und Kurtosis zum Beispiel:

/** 
* 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

können Sie Code für eine Kombinationsfunktion bereitstellen, sodass diese Klasse als Collector verwendet werden kann? –

+0

aber würde nicht Varianz Skew und Kurtosis im Endergebnis dann falsch sein? Ich meine DoubleSummaryStatistics :: Kombinieren berücksichtigt sie nicht, oder fehlt mir etwas? –

+0

@RolandGude Guter Punkt Sie sind absolut richtig. Bearbeitet. – assylias

8

Ersetzen Sie den String Parameter der Methode Statistik mit einem Funktionstyp, die eine Doppelstrom und kehrt nimmt Das Aggregat.

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

Nun können Sie die Methode wie folgt aufrufen, ohne Verwendung eines Schalter Erklärung für die verschiedenen Operationen auf dem Strom:

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

Danke. Und wo könnte ich mehr über das Erstellen meiner eigenen ToDoubleFunction() lesen? Zum Beispiel für die Berechnung der Varianz. – CheJharia

+0

Anstelle von 's -> s.sum()' können Sie es zu 'DoubleStream :: sum' vereinfachen. Dasselbe gilt für 's -> s.count()'. – bcsb1001

+0

@ bcsb1001: Meiner Meinung nach ist 's -> s.sum()' einfacher als 'DoubleStream :: sum' (bezüglich Lesbarkeit und Wartbarkeit). – nosid