/* * This file is part of LibrePlan * * Copyright (C) 2009-2010 Fundación para o Fomento da Calidade Industrial e * Desenvolvemento Tecnolóxico de Galicia * Copyright (C) 2010-2011 Igalia, S.L. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.libreplan.business.common; import java.util.ArrayList; import java.util.Collections; import java.util.List; import org.apache.commons.math3.fraction.Fraction; /** * @author Óscar González Fernández <ogonzalez@igalia.com> */ public class ProportionalDistributor { public static ProportionalDistributor create(int... initialShares) { return new ProportionalDistributor(toFractions(sumIntegerParts(initialShares), initialShares)); } private static int sumIntegerParts(int[] numbers) { int sum = 0; for (int each : numbers) { sum += each; } return sum; } private static Fraction[] toFractions(int initialTotal, int... shares) { Fraction[] result = new Fraction[shares.length]; for (int i = 0; i < result.length; i++) { result[i] = initialTotal == 0 ? Fraction.ZERO : new Fraction(shares[i], initialTotal); } return result; } /** * Note: this class has a natural ordering that is inconsistent with equals. * */ private static class FractionWithPosition implements Comparable<FractionWithPosition> { public static List<FractionWithPosition> transform(Fraction[] fractions) { List<FractionWithPosition> result = new ArrayList<>(); for (int i = 0; i < fractions.length; i++) { result.add(new FractionWithPosition(i, fractions[i])); } return result; } final int position; final Fraction fraction; FractionWithPosition(int position, Fraction fraction) { this.position = position; this.fraction = fraction; } @Override public int compareTo(FractionWithPosition other) { return fraction.compareTo(other.fraction); } } private final Fraction[] fractions; private ProportionalDistributor(Fraction[] fractions) { this.fractions = fractions; } public int[] distribute(final int total) { if ( fractions.length == 0 ) { return new int[0]; } int[] result = new int[fractions.length]; int remaining = total - assignIntegerParts(total, result); if ( remaining == 0 ) { return result; } Fraction[] currentFractions = toFractions(total, result); assignRemaining(result, currentFractions, remaining); return result; } private int assignIntegerParts(int current, int[] result) { Fraction currentAsFraction = new Fraction(current, 1); int substract = 0; for (int i = 0; i < fractions.length; i++) { int intValue = fractions[i].multiply(currentAsFraction).intValue(); if ( intValue > 0 ) { result[i] = result[i] + intValue; substract += intValue; } } return substract; } private void assignRemaining(int[] result, Fraction[] currentProportions, int remaining) { List<FractionWithPosition> transform = FractionWithPosition.transform(difference(currentProportions)); Collections.sort(transform, Collections.reverseOrder()); for (int i = 0; i < remaining; i++) { FractionWithPosition proportionWithPosition = transform.get(i % currentProportions.length); result[proportionWithPosition.position] = result[proportionWithPosition.position] + 1; } } private Fraction[] difference(Fraction[] pr) { Fraction[] result = new Fraction[fractions.length]; for (int i = 0; i < result.length; i++) { result[i] = fractions[i].subtract(pr[i]); } return result; } }