C#给定部分权重的部分分割整数算法

C#给定部分权重的部分分割整数算法,c#,algorithm,C#,Algorithm,我有一个整数和一个非负权重列表,如何将整数“拆分”为相同数量的具有相应权重的“桶” public int[] SplitIntoBuckets(int count, int[] weights) { // some magic algorithm Debug.Assert(solution.Sum() == count); return solution; } 一个简单的例子是count=200和weights={25,25,50}以及解决方案{50,50,100}(5

我有一个整数和一个非负权重列表,如何将整数“拆分”为相同数量的具有相应权重的“桶”

public int[] SplitIntoBuckets(int count, int[] weights) {
    // some magic algorithm
    Debug.Assert(solution.Sum() == count);
    return solution;
}
一个简单的例子是
count=200
weights={25,25,50}
以及解决方案
{50,50,100}
(50+50+100=200)。然而,输入不必是“nice”数字,没有nice解决方案的另一个例子是
count=150
和权重
{753,42,95,501}

存储桶的总和必须始终等于
计数
输入,算法应尽可能将输入分配到存储桶之间的权重。什么是“尽可能接近”并不重要(例如,它可以是最低的绝对误差、相对误差或平方误差)。

然而,我能找到的最接近的问题是,我的桶不是“偶数”,而且重量是随机选择的“好”数字。

注意,
解决方案[I]
等于:

round(weights[i] / weightSum * count)
存在一种边缘情况,
weights[i]/weightSum*count
是半(x.5)的奇数倍,这会导致
round
不必要地对额外的时间进行取整。例如,
count=3
weights={1,1}
。为了解决这个问题,我们通过从
count
中减去先前存储桶的总和来计算最后一个存储桶。这将确保无论发生什么情况,解决方案的总和都达到
count

public int[] SplitIntoBuckets(int count, int[] weights) {
    int[] solution = new int[weights.Length];
    int weightSum = weights.Sum();
    // for every weight except the last...
    int sum = 0;
    for (int i = 0 ; i < weights.Length - 1 ; i++) {
        solution[i] = (int)Math.Round((double)weights[i] / weightSum * count);
        sum += solution[i];
    }
    // calculate the last bucket by subtracting:
    solution[weights.Length - 1] = count - sum;
    return solution;
}
public int[]拆分为bucket(int计数,int[]权重){
int[]解决方案=新的int[weights.Length];
int weightSum=weights.Sum();
//除了最后一次外,每一次的重量。。。
整数和=0;
对于(int i=0;i
我建议在跟踪精确
双精度
值(
v
)和四舍五入整数1(
)之间的差异(
diff
)时进行四舍五入:

结果:

string demo = sstring.Join(Environment.NewLine, Enumerable
  .Range(200, 15)
  .Select(n => $"{n} = {string.Join(", ", SplitIntoBuckets(n, new int[] { 25, 25, 50 }))}"));

Console.Write(demo);
    
200 = 50, 50, 100
201 = 50, 51, 100
202 = 51, 50, 101
203 = 51, 51, 101
204 = 51, 51, 102
205 = 51, 52, 102
206 = 52, 51, 103
207 = 52, 52, 103
208 = 52, 52, 104
209 = 52, 53, 104
210 = 53, 52, 105
211 = 53, 53, 105
212 = 53, 53, 106
213 = 53, 54, 106
214 = 54, 53, 107

将所有舍入错误阻尼到最后一项中可能会导致非紧密分布的解决方案,例如
拆分为bucket(202,新int[]{50,50,100})
返回
{50,50,102}
;但是
202*100/(50+50+100)=101
。浮点(精确)解是
{50.5,50.5,101}
,最佳(IMHO)整数近似值是
{51,50,101}
{50,51,101}
好吧,OP说他们不太在乎“尽可能接近”是什么意思…:-)但是你的答案看起来不错!我必须站在@DmitryBychenko一边,否则有人可能会说,
reutrn new int[]{count,0,0,…}
是一个好的解决方案,虽然我不在乎什么类型的错误被最小化,但至少应该尝试一些好的解决方案。我尝试过用随机输入运行它几次,但它似乎确实累积了错误。另一个问题是,最后一个bucket可能会导致负值(但忘了提到这个问题,我可以稍后再问)。对不起,我没有提到所有整数都应该是非负值(总和、权重、解)。根据您的解决方案,是否执行以下修改
if(Math.Abs(diff)>=0.5){var correction=(int)Math.Round(diff);if(value+correction>=0){value+=correction;diff-=correction;}
在解决方案中避免负值的同时,给出类似的结果质量?@wondra:如果
总计
权重
均为非负值,则
解决方案
也将为非负值;您可能怀疑
否则,如果(diff@wondra:我怀疑你是否想要这样的修改:1.可读性差2.我不相信
如果(value+correction>=0)
-如果
value==0
那么
correction>=0
value
是对
v
的估计,因为
v>=0
correction
不能是负面的,这会加剧低估
值感谢您的解释,我想我现在更好地理解了-
round
实际上是数学。天花板现在更有意义了。
200 = 50, 50, 100
201 = 50, 51, 100
202 = 51, 50, 101
203 = 51, 51, 101
204 = 51, 51, 102
205 = 51, 52, 102
206 = 52, 51, 103
207 = 52, 52, 103
208 = 52, 52, 104
209 = 52, 53, 104
210 = 53, 52, 105
211 = 53, 53, 105
212 = 53, 53, 106
213 = 53, 54, 106
214 = 54, 53, 107