package org.openlca.core.results; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.List; public final class Contributions { private Contributions() { } /** * Calculates a contribution set of the given collection of items to the * given total amount. The contribution values and shares are calculated * with the given amount functions which maps an item to the respective * contribution amount of this item: <br> * <code> * contributionSet = Contributions.calculate(items, item -> amount) * </code> The share of the contribution item is calculated via: <br> * <code> * share = item -> amount / abs(totalAmount) * </code> An contribution item is set as the "rest" item * (contributionItem.isRest = true) if the item in the collection is null). */ public static <T> ContributionSet<T> calculate(Collection<T> items, double totalAmount, Function<T> fn) { List<ContributionItem<T>> contributions = new ArrayList<>(); double total = Math.abs(totalAmount); for (T item : items) { ContributionItem<T> contribution = new ContributionItem<>(); contribution.rest = item == null; contribution.item = item; double val = fn.value(item); contribution.amount = val; if (total != 0) contribution.share = val / total; contributions.add(contribution); } return new ContributionSet<>(contributions); } public static <T> ContributionSet<T> calculate(Collection<T> items, Function<T> fn) { List<ContributionItem<T>> contributions = new ArrayList<>(); for (T item : items) { ContributionItem<T> contribution = new ContributionItem<>(); contribution.rest = item == null; contribution.item = item; double val = fn.value(item); contribution.amount = val; contributions.add(contribution); } calculateShares(contributions); return new ContributionSet<>(contributions); } /** * Calculates the relative shares of the given contribution items. */ public static void calculateShares( List<? extends ContributionItem<?>> contributions) { if (contributions == null || contributions.isEmpty()) return; double refVal = Math.abs(getRefValue(contributions)); for (ContributionItem<?> c : contributions) { if (refVal == 0) c.share = (double) 0; else c.share = c.amount / refVal; } } private static double getRefValue( List<? extends ContributionItem<?>> contributions) { ContributionItem<?> first = contributions.get(0); double max = first.amount; double min = max; for (int i = 1; i < contributions.size(); i++) { ContributionItem<?> next = contributions.get(i); double nextVal = next.amount; max = Math.max(max, nextVal); min = Math.min(min, nextVal); } return Math.max(Math.abs(max), Math.abs(min)); } public static <T> void sortAscending(List<ContributionItem<T>> items) { Collections.sort(items, new Sorter(true)); } public static <T> void sortDescending(List<ContributionItem<T>> items) { Collections.sort(items, new Sorter(false)); } /** * Returns the top-contributors of the given list ordered by their * contribution values in descending order. If there are more items than the * given number (maxItems) a rest-item is created at the bottom of the list * which gets the sum of the items not in the list. Thus the returned list * has <code>maxItems</code> entries. */ public static <T> List<ContributionItem<T>> topWithRest( List<ContributionItem<T>> items, int maxItems) { if (items == null) return Collections.emptyList(); sortDescending(items); if (items.size() <= maxItems) return items; List<ContributionItem<T>> list = new ArrayList<>(); ContributionItem<T> restItem = new ContributionItem<>(); restItem.rest = true; for (int i = 0; i < items.size(); i++) { ContributionItem<T> item = items.get(i); if (i < (maxItems - 1)) list.add(item); else { restItem.amount = restItem.amount + item.amount; restItem.share = restItem.share + item.share; } } list.add(restItem); return list; } @FunctionalInterface public interface Function<T> { double value(T t); } private static class Sorter implements Comparator<ContributionItem<?>> { private final boolean ascending; public Sorter(boolean ascending) { this.ascending = ascending; } @Override public int compare(ContributionItem<?> o1, ContributionItem<?> o2) { if (o1 == null || o2 == null) return 0; if (o1.rest) return 1; if (o2.rest) return -1; if (ascending) return Double.compare(o1.amount, o2.amount); else return -Double.compare(o1.amount, o2.amount); } } }