I would like to create a simple class applying common statistics using lambda expression. I am wondering how can I avoid using the switch case in the statistic() method?
For example, I may want to write a new lambda to calculate the variance of the list, etc.
Thank you.
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;
}
I have in mind of a method like this
private static double newStatistics(List<Double> numbers, Function<Double, Double> function){
return numbers.stream().mapToDouble(Double::doubleValue).function();
}
Explanation. Both of the above options are correct. Q 5 - Which of the following is correct about Java 8 lambda expression? A - Lambda expressions are used primarily to define inline implementation of a functional interface.
Lambda Expressions were added in Java 8.
If you are using a lambda expression as an anonymous function but not doing anything with the argument passed, you can replace lambda expression with method reference. In the first two cases, the method reference is equivalent to lambda expression that supplies the parameters of the method e.g. System.
The method references can only be used to replace a single method of the lambda expression. A code is more clear and short if one uses a lambda expression rather than using an anonymous class and one can use method reference rather than using a single function lambda expression to achieve the same.
Why not simply use DoubleStream#summaryStatistics or apply a similar pattern?
You could even extend the class to add custom methods, say a variance, skewness and kurtosis for example:
/**
* 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; }
}
Replace the String parameter of the method statistic with a function type, that takes a DoubleStream and returns the aggregate.
private static double statistic(List<Double> numbers,
ToDoubleFunction<DoubleStream> function) {
return function.applyAsDouble(
numbers.stream().mapToDouble(Double::doubleValue));
}
Now, you can invoke the method as follows, without using a switch statement for the different operations on the stream:
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()));
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With