ItEr37S08CUCreacionUnidadesPlanificacionItEr36S11: Adding ProportionalDistributor that can handle integers

This commit is contained in:
Óscar González Fernández 2009-12-03 22:15:19 +01:00
parent 3520cd34dc
commit b914f9ed0e
2 changed files with 261 additions and 0 deletions

View file

@ -0,0 +1,132 @@
/*
* This file is part of ###PROJECT_NAME###
*
* Copyright (C) 2009 Fundación para o Fomento da Calidade Industrial e
* Desenvolvemento Tecnolóxico de Galicia
*
* 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.navalplanner.business.common;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* @author Óscar González Fernández <ogonzalez@igalia.com>
*/
public class ProportionalDistributor {
public static ProportionalDistributor create(int... initialShares) {
return new ProportionalDistributor(toProportions(
sumIntegerParts(initialShares), initialShares));
}
private static int sumIntegerParts(int[] numbers) {
int sum = 0;
for (Number each : numbers) {
sum += each.intValue();
}
return sum;
}
private static BigDecimal[] toProportions(int initialTotal, int... shares) {
BigDecimal total = new BigDecimal(initialTotal);
BigDecimal[] result = new BigDecimal[shares.length];
for (int i = 0; i < result.length; i++) {
result[i] = new BigDecimal(shares[i]).divide(total, 4,
RoundingMode.DOWN);
}
return result;
}
private static class ProportionWithPosition implements
Comparable<ProportionWithPosition> {
public static List<ProportionWithPosition> transform(
BigDecimal[] proportions) {
List<ProportionWithPosition> result = new ArrayList<ProportionWithPosition>();
for (int i = 0; i < proportions.length; i++) {
result.add(new ProportionWithPosition(i, proportions[i]));
}
return result;
}
final int position;
final BigDecimal proportion;
ProportionWithPosition(int position, BigDecimal proportion) {
this.position = position;
this.proportion = proportion;
}
@Override
public int compareTo(ProportionWithPosition other) {
return proportion.compareTo(other.proportion);
}
}
private final BigDecimal[] proportions;
private ProportionalDistributor(BigDecimal[] proportions) {
this.proportions = proportions;
}
public int[] distribute(final int total) {
int[] result = new int[proportions.length];
int remaining = total - assignIntegerParts(total, result);
if (remaining == 0) {
return result;
}
BigDecimal[] currentProportions = toProportions(total, result);
assignRemaining(result, currentProportions, remaining);
return result;
}
private int assignIntegerParts(int current, int[] result) {
int substract = 0;
for (int i = 0; i < proportions.length; i++) {
int intValue = proportions[i].multiply(new BigDecimal(current))
.intValue();
if (intValue > 0) {
result[i] = result[i] + intValue;
substract += intValue;
}
}
return substract;
}
private void assignRemaining(int[] result, BigDecimal[] currentProportions,
int remaining) {
List<ProportionWithPosition> transform = ProportionWithPosition
.transform(difference(currentProportions));
Collections.sort(transform, Collections.reverseOrder());
for (int i = 0; i < remaining; i++) {
ProportionWithPosition proportionWithPosition = transform.get(i);
result[proportionWithPosition.position] = result[proportionWithPosition.position] + 1;
}
}
private BigDecimal[] difference(BigDecimal[] pr) {
BigDecimal[] result = new BigDecimal[proportions.length];
for (int i = 0; i < result.length; i++) {
result[i] = proportions[i].subtract(pr[i]);
}
return result;
}
}

View file

@ -0,0 +1,129 @@
/*
* This file is part of ###PROJECT_NAME###
*
* Copyright (C) 2009 Fundación para o Fomento da Calidade Industrial e
* Desenvolvemento Tecnolóxico de Galicia
*
* 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.navalplanner.business.common;
import static org.junit.Assert.assertThat;
import java.util.Arrays;
import org.hamcrest.BaseMatcher;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.junit.Test;
/**
* @author Óscar González Fernández <ogonzalez@igalia.com>
*
*/
public class ProportionalDistributorTest {
@Test
public void mustGiveTheSameDistributionForSameTotal() {
ProportionalDistributor distributor = ProportionalDistributor.create(
100, 200);
assertThat(distributor.distribute(300), equalToDistribution(100, 200));
}
@Test
public void exactDivisionsWorkOk() {
ProportionalDistributor distributor = ProportionalDistributor.create(
100, 100, 100);
assertThat(distributor.distribute(600), equalToDistribution(200, 200,
200));
}
@Test
public void distributingZeroGivesZeroShares() {
ProportionalDistributor distributor = ProportionalDistributor.create(
100, 100, 100);
assertThat(distributor.distribute(0), equalToDistribution(0, 0, 0));
}
@Test
public void ifOneOfTheProportionsIsZeroAlwaysGivesZeros() {
ProportionalDistributor distributor = ProportionalDistributor.create(
100, 100, 0);
assertThat(distributor.distribute(100), equalToDistribution(50, 50, 0));
}
@Test
public void disputedPartGoesToFirstIfEqualWeight() {
ProportionalDistributor distributor = ProportionalDistributor.create(
10, 10, 10);
assertThat(distributor.distribute(10), equalToDistribution(4, 3, 3));
}
@Test
public void distributionIsKept() {
ProportionalDistributor distributor = ProportionalDistributor.create(2,
3, 5);
assertThat(distributor.distribute(1), equalToDistribution(0, 0, 1));
assertThat(distributor.distribute(2), equalToDistribution(0, 1, 1));
assertThat(distributor.distribute(3), equalToDistribution(1, 1, 1));
assertThat(distributor.distribute(4), equalToDistribution(1, 1, 2));
assertThat(distributor.distribute(5), equalToDistribution(1, 2, 2));
assertThat(distributor.distribute(6), equalToDistribution(1, 2, 3));
assertThat(distributor.distribute(10), equalToDistribution(2, 3, 5));
assertThat(distributor.distribute(7), equalToDistribution(1, 2, 4));
}
@Test
public void addingOneEachTime() {
ProportionalDistributor distributor = ProportionalDistributor.create(
99, 101, 800);
assertThat(distributor.distribute(1), equalToDistribution(0, 0, 1));
assertThat(distributor.distribute(3), equalToDistribution(0, 0, 3));
assertThat(distributor.distribute(6), equalToDistribution(0, 1, 5));
assertThat(distributor.distribute(7), equalToDistribution(1, 1, 5));
assertThat(distributor.distribute(8), equalToDistribution(1, 1, 6));
assertThat(distributor.distribute(9), equalToDistribution(1, 1, 7));
assertThat(distributor.distribute(10), equalToDistribution(1, 1, 8));
assertThat(distributor.distribute(11), equalToDistribution(1, 1, 9));
assertThat(distributor.distribute(12), equalToDistribution(1, 1, 10));
assertThat(distributor.distribute(13), equalToDistribution(1, 1, 11));
assertThat(distributor.distribute(14), equalToDistribution(1, 2, 11));
assertThat(distributor.distribute(15), equalToDistribution(1, 2, 12));
assertThat(distributor.distribute(16), equalToDistribution(1, 2, 13));
assertThat(distributor.distribute(17), equalToDistribution(2, 2, 13));
assertThat(distributor.distribute(20), equalToDistribution(2, 2, 16));
}
private static Matcher<int[]> equalToDistribution(final int... distribution) {
return new BaseMatcher<int[]>() {
@Override
public boolean matches(Object object) {
if (object instanceof int[]) {
int[] arg = (int[]) object;
return Arrays.equals(arg, distribution);
}
return false;
}
@Override
public void describeTo(Description description) {
description.appendText("must equal "
+ Arrays.toString(distribution));
}
};
}
}