Skip to content

Commit

Permalink
allocate long
Browse files Browse the repository at this point in the history
  • Loading branch information
AnthonyLloyd committed Nov 20, 2023
1 parent f03aeb5 commit 150e1d9
Show file tree
Hide file tree
Showing 2 changed files with 104 additions and 25 deletions.
88 changes: 66 additions & 22 deletions Tests/Allocator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,28 +17,33 @@ public static long[] Allocate(long quantity, double[] weights)
residual -= allocation;
results[i] = allocation;
}
if (residual == 0) return results;
if (residual >= weights.Length || residual <= -weights.Length)
throw new Exception($"Allocate numeric overflow, quantity={quantity}, weights={string.Join(',', weights)}, residual={residual}");
while (residual != 0)
if (residual * sumWeights < 0)
{
var minErrorIncrease = double.MaxValue;
quantity = -quantity;
sumWeights = -sumWeights;
}
do
{
var minError = double.MaxValue;
var maxWeight = 0.0;
var index = 0;
var increment = Math.Sign(residual);
for (int i = 0; i < weights.Length; i++)
{
var error = results[i] * sumWeights - quantity * weights[i];
var errorIncrease = Math.Abs(error + increment * sumWeights) - Math.Abs(error);
if (errorIncrease < minErrorIncrease || errorIncrease == minErrorIncrease && Math.Abs(weights[i]) > maxWeight)
if (error < minError || error == minError && Math.Abs(weights[i]) > maxWeight)
{
minErrorIncrease = errorIncrease;
minError = error;
maxWeight = Math.Abs(weights[i]);
index = i;
}
}
var increment = Math.Sign(residual);
results[index] += increment;
residual -= increment;
}
} while (residual != 0);
return results;
}

Expand All @@ -54,28 +59,33 @@ public static int[] Allocate(int quantity, double[] weights)
residual -= allocation;
results[i] = allocation;
}
if (residual == 0) return results;
if (residual >= weights.Length || residual <= -weights.Length)
throw new Exception($"Allocate numeric overflow, quantity={quantity}, weights={string.Join(',', weights)}, residual={residual}");
while (residual != 0)
if (residual * sumWeights < 0)
{
var minErrorIncrease = double.MaxValue;
quantity = -quantity;
sumWeights = -sumWeights;
}
do
{
var minError = double.MaxValue;
var maxWeight = 0.0;
var index = 0;
var increment = Math.Sign(residual);
for (int i = 0; i < weights.Length; i++)
{
var error = results[i] * sumWeights - quantity * weights[i];
var errorIncrease = Math.Abs(error + increment * sumWeights) - Math.Abs(error);
if (errorIncrease < minErrorIncrease || errorIncrease == minErrorIncrease && Math.Abs(weights[i]) > maxWeight)
if (error < minError || error == minError && Math.Abs(weights[i]) > maxWeight)
{
minErrorIncrease = errorIncrease;
minError = error;
maxWeight = Math.Abs(weights[i]);
index = i;
}
}
var increment = Math.Sign(residual);
results[index] += increment;
residual -= increment;
}
} while (residual != 0);
return results;
}

Expand All @@ -92,26 +102,31 @@ public static long[] Allocate(long quantity, long[] weights)
residual -= allocation;
results[i] = allocation;
}
while (residual != 0)
if (residual == 0) return results;
if (residual * sumWeights < 0)
{
var minErrorIncrease = long.MaxValue;
quantity = -quantity;
sumWeights = -sumWeights;
}
do
{
var minError = long.MaxValue;
var maxWeight = 0L;
var index = 0;
var increment = Math.Sign(residual);
for (int i = 0; i < weights.Length; i++)
{
var error = results[i] * sumWeights - quantity * weights[i];
var errorIncrease = Math.Abs(error + increment * sumWeights) - Math.Abs(error);
if (errorIncrease < minErrorIncrease || errorIncrease == minErrorIncrease && Math.Abs(weights[i]) > maxWeight)
if (error < minError || error == minError && Math.Abs(weights[i]) > maxWeight)
{
minErrorIncrease = errorIncrease;
minError = error;
maxWeight = Math.Abs(weights[i]);
index = i;
}
}
var increment = Math.Sign(residual);
results[index] += increment;
residual -= increment;
}
} while (residual != 0);
return results;
}

Expand All @@ -128,7 +143,7 @@ public static long[] Allocate_BalinskiYoung(long quantity, double[] weights)
{
var a = allocations[i];
var w = weights[i];
if (a < w * q / sumWeights) // to keep to quota rule
if (sumWeights * a < w * q) // to keep to quota rule
{
var r = w / (1 + a); // divisor method
if (r > rmax || (r == rmax && a < amin))
Expand All @@ -143,4 +158,33 @@ public static long[] Allocate_BalinskiYoung(long quantity, double[] weights)
}
return allocations;
}

public static long[] Allocate_BalinskiYoung(long quantity, long[] weights)
{
var sumWeights = weights.Sum();
var allocations = new long[weights.Length];
for (var q = 1L; q <= quantity; q++)
{
var rmax = double.MinValue;
var amin = long.MaxValue;
var index = -1;
for (int i = 0; i < weights.Length; i++)
{
var a = allocations[i];
var w = weights[i];
if (sumWeights * a < w * q) // to keep to quota rule
{
var r = (double)w / (1 + a); // divisor method
if (r > rmax || (r == rmax && a < amin))
{
rmax = r;
amin = a;
index = i;
}
}
}
allocations[index]++;
}
return allocations;
}
}
41 changes: 38 additions & 3 deletions Tests/Allocator_Tests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,14 @@ public class Allocator_Tests(Xunit.Abstractions.ITestOutputHelper output)
readonly static Gen<(long Quantity, double[] Weights)> genAllSigns =
Gen.Select(Gen.Long[-1000, 1000], Gen.Double[-1000, 1000].Array[2, 100].Where(ws => ws.Sum() > 1e-3));

readonly static Gen<(long Quantity, long[] Weights)> genAllSignsLong =
Gen.Select(Gen.Long[-1000, 1000], Gen.Long[-1000, 1000].Array[2, 100].Where(ws => ws.Sum() != 0));

readonly static Gen<(long Quantity, double[] Weights)> genPositive =
Gen.Select(Gen.Long[1, 10_000], Gen.Double[0, 100_000].Array[1, 30].Where(ws => Math.Abs(ws.Sum()) > 1e-9));

readonly static Gen<(long Quantity, long[] Weights)> genAllSignsLong =
Gen.Select(Gen.Long[-1000, 1000], Gen.Long[-1000, 1000].Array[2, 100].Where(ws => ws.Sum() != 0));
readonly static Gen<(long Quantity, long[] Weights)> genPositiveLong =
Gen.Select(Gen.Long[1, 10_000], Gen.Long[0, 100_000].Array[1, 30].Where(ws => ws.Sum() != 0));

static bool TotalCorrectly<W>(long quantity, W[] weights, Func<long, W[], long[]> allocator)
=> allocator(quantity, weights).Sum() == quantity;
Expand All @@ -39,6 +42,12 @@ public void Allocate_BalinskiYoung_TotalsCorrectly()
genPositive.Sample((quantity, weights) => TotalCorrectly(quantity, weights, Allocator.Allocate_BalinskiYoung));
}

[Fact]
public void Allocate_BalinskiYoung_Long_TotalsCorrectly()
{
genPositiveLong.Sample((quantity, weights) => TotalCorrectly(quantity, weights, Allocator.Allocate_BalinskiYoung));
}

static bool BetweenFloorAndCeiling<W>(long quantity, W[] weights, Func<long, W[], long[]> allocate)
{
var sumWeights = weights.Sum(w => Convert.ToDouble(w));
Expand Down Expand Up @@ -70,6 +79,12 @@ public void Allocate_BalinskiYoung_BetweenFloorAndCeiling()
genPositive.Sample((quantity, weights) => BetweenFloorAndCeiling(quantity, weights, Allocator.Allocate_BalinskiYoung));
}

[Fact]
public void Allocate_BalinskiYoung_Long_BetweenFloorAndCeiling()
{
genPositiveLong.Sample((quantity, weights) => BetweenFloorAndCeiling(quantity, weights, Allocator.Allocate_BalinskiYoung));
}

static bool SmallerWeightsDontGetLargerAllocation<W>(long quantity, W[] weights, Func<long, W[], long[]> allocate) where W : notnull
{
var allocations = allocate(quantity, weights);
Expand Down Expand Up @@ -109,6 +124,12 @@ public void Allocate_BalinskiYoung_SmallerWeightsDontGetLargerAllocation()
genPositive.Sample((quantity, weights) => SmallerWeightsDontGetLargerAllocation(quantity, weights, Allocator.Allocate_BalinskiYoung));
}

[Fact]
public void Allocate_BalinskiYoung_Long_SmallerWeightsDontGetLargerAllocation()
{
genPositiveLong.Sample((quantity, weights) => SmallerWeightsDontGetLargerAllocation(quantity, weights, Allocator.Allocate_BalinskiYoung));
}

static bool GivesSameResultReorderedForReorderedWeights<W>(long quantity, W[] weights, W[] shuffled, Func<long, W[], long[]> allocate)
{
var allocations = allocate(quantity, weights);
Expand Down Expand Up @@ -140,6 +161,14 @@ public void Allocate_BalinskiYoung_GivesSameResultReorderedForReorderedWeights()
, output.WriteLine);
}

[Fact]
public void Allocate_BalinskiYoung_Long_GivesSameResultReorderedForReorderedWeights()
{
genPositiveLong.SelectMany((q, w) => Gen.Shuffle(w).Select(s => (q, w, s)))
.Sample((quantity, weights, shuffled) => GivesSameResultReorderedForReorderedWeights(quantity, weights, shuffled, Allocator.Allocate_BalinskiYoung)
, output.WriteLine);
}

static bool GivesOppositeForNegativeQuantity<W>(long quantity, W[] weights, Func<long, W[], long[]> allocate)
{
var allocationsPositive = allocate(quantity, weights);
Expand Down Expand Up @@ -305,7 +334,7 @@ public void Allocate_Long_HasSmallestAllocationError()
}, seed: "82h-ttvU9vZM");
}

static bool NoAlabamaParadox(long quantity, double[] weights, Func<long, double[], long[]> allocate)
static bool NoAlabamaParadox<W>(long quantity, W[] weights, Func<long, W[], long[]> allocate)
{
var allocations = allocate(quantity, weights);
var allocationsPlus = allocate(quantity + 1, weights);
Expand All @@ -321,6 +350,12 @@ public void Allocate_BalinskiYoung_NoAlabamaParadox()
genPositive.Sample((quantity, weights) => NoAlabamaParadox(quantity, weights, Allocator.Allocate_BalinskiYoung));
}

[Fact]
public void Allocate_BalinskiYoung_Long_NoAlabamaParadox()
{
genPositiveLong.Sample((quantity, weights) => NoAlabamaParadox(quantity, weights, Allocator.Allocate_BalinskiYoung));
}

[Fact]
public void Allocate_BalinskiYoung_MinErrorExample()
{
Expand Down

0 comments on commit 150e1d9

Please sign in to comment.