package org.threadly.util; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; /** * Utilities for getting some basic statistics out of numerical data collections. * * @since 4.5.0 */ public class StatisticsUtils { /** * Calculates the average from a collection of numeric values. * * @param list List of numbers to average against * @return Zero if the list is empty, otherwise the average of the values inside the list */ public static double getAverage(Collection<? extends Number> list) { if (list.isEmpty()) { return 0; } double totalTime = 0; Iterator<? extends Number> it = list.iterator(); while (it.hasNext()) { totalTime += it.next().doubleValue(); } return totalTime / list.size(); } /** * Gets percentile values from a collection of numeric values. This function is NOT dependent * on the collection already being sorted. This function accepts any decimal percentile between * zero and one hundred, but requests for 99.9 and 99.99 may return the same result if the sample * set is not large or varied enough. There is no attempt to extrapolate trends, thus only real * samples are returned. * <p> * The returned map's keys correspond exactly to the percentiles provided. Iterating over the * returned map will iterate in order of the requested percentiles as well. * * @param values A non-empty collection of numbers to examine for percentiles * @param percentiles Percentiles requested, any decimal values between 0 and 100 (inclusive) * @return Map with keys being the percentiles requested in the second argument * @param <T> Specific number type contained in the collection */ public static <T extends Number> Map<Double, T> getPercentiles(Collection<? extends T> values, double ... percentiles) { if (percentiles.length == 0) { throw new IllegalArgumentException("No percentiles requested"); } else if (values.isEmpty()) { throw new IllegalArgumentException("No values provided to calculate against"); } List<T> valuesCopy = new ArrayList<>(values); Collections.sort(valuesCopy, new Comparator<Number>() { @Override public int compare(Number o1, Number o2) { double result = o1.doubleValue() - o2.doubleValue(); if (result > 0) { return 1; } else if (result < 0) { return -1; } else { return 0; } } }); Map<Double, T> result = new LinkedHashMap<>(); for (double p : percentiles) { if (p > 100 || p < 0) { throw new IllegalArgumentException("Percentile not in range of 0 to 100: " + p); } int index; if (p == 100) { index = valuesCopy.size() - 1; } else { index = (int)((p / 100.) * valuesCopy.size()); } result.put(p, valuesCopy.get(index)); } return result; } }