Last active
September 10, 2021 07:53
-
-
Save in-async/44cf29c7017147b31057c4b622e4ba5f to your computer and use it in GitHub Desktop.
汎用按分器 in .NET
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/// Usage | |
void Main() { | |
var weights = new decimal[] { 4, 6, 7.3m }; | |
Prorate(total: 1014, weights: weights, ProrationRounding.SpecifiedOrder).Dump(nameof(ProrationRounding.SpecifiedOrder)); | |
Prorate(total: 1014, weights: weights, ProrationRounding.DecimalPartDescending).Dump(nameof(ProrationRounding.DecimalPartDescending)); | |
ProrateExact(total: 1014, weights: weights).Dump(nameof(ProrateExact)); | |
} | |
/// <summary> | |
/// 整数按分で生じた端数の処理方法を表します。 | |
/// </summary> | |
public enum ProrationRounding { | |
/// <summary> | |
/// 指定された重み配列の順に端数を割り振ります。 | |
/// </summary> | |
/// <remarks>低コスト</remarks> | |
SpecifiedOrder, | |
/// <summary> | |
/// 小数部の大きい順に端数を割り振ります。 | |
/// </summary> | |
/// <remarks>高コスト</remarks> | |
DecimalPartDescending, | |
} | |
public static IEnumerable<int> Prorate( | |
int total | |
, IEnumerable<decimal> weights | |
, ProrationRounding rounding | |
) { | |
if (weights is null) { throw new ArgumentNullException(nameof(weights)); } | |
List<(int integerPart, decimal decimalPart)> proratedList = new(); | |
int remaineds = total; | |
foreach (decimal prorated in ProrateExact(total, weights)) { | |
int integerPart = decimal.ToInt32(prorated); // NOTE: https://docs.microsoft.com/en-us/dotnet/api/system.decimal.toint32?view=net-5.0#remarks | |
remaineds -= integerPart; | |
proratedList.Add(( | |
integerPart, | |
decimalPart: prorated % 1 | |
)); | |
} | |
if (remaineds is 0) { return proratedList.Select(t => t.integerPart); } | |
switch (rounding) { | |
case ProrationRounding.SpecifiedOrder: | |
return proratedList.Select(t => { | |
if (remaineds is 0) { return t.integerPart; } | |
if (t.decimalPart is 0) { return t.integerPart; } | |
remaineds--; | |
return t.integerPart + 1; | |
}); | |
case ProrationRounding.DecimalPartDescending: | |
return proratedList | |
.Select((parts, i) => (parts, i)) | |
.OrderByDescending(t => t.parts.decimalPart) | |
.Select(t => { | |
if (remaineds is 0) { return (prorated: t.parts.integerPart, t.i); } | |
remaineds--; | |
return (prorated: t.parts.integerPart + 1, t.i); | |
}) | |
.OrderBy(t => t.i) | |
.Select(t => t.prorated) | |
; | |
default: | |
throw new ArgumentException(nameof(rounding)); | |
} | |
} | |
public static IEnumerable<decimal> ProrateExact( | |
decimal total | |
, IEnumerable<decimal> weights | |
) { | |
if (weights is null) { throw new ArgumentNullException(nameof(weights)); } | |
switch (weights) { | |
case IReadOnlyCollection<decimal>: | |
case ICollection<decimal>: | |
break; | |
default: | |
weights = weights.ToArray(); | |
break; | |
} | |
decimal totalWeight = weights.Sum(); | |
return weights.Select(weight => total * weight / totalWeight); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment