我有一组非负值。我想构建一个值为20的值数组,以便它们与第一个数组成比例。
这将是一个简单的问题,除了我希望比例数组完全相加 20,补偿任何舍入误差。
例如,数组
input = [400, 400, 0, 0, 100, 50, 50]
会产生
output = [8, 8, 0, 0, 2, 1, 1]
sum(output) = 20
但是,大多数情况下会出现很多舍入错误,例如
input = [3, 3, 3, 3, 3, 3, 18]
天真的收益
output = [1, 1, 1, 1, 1, 1, 10]
sum(output) = 16 (ouch)
有没有一种很好的方法来分配输出数组,每次最多可以增加20个?
答案 0 :(得分:10)
您的问题类似于proportional representation,您想要在他们获得的选票中按比例分享N个席位(在您的情况下为20个)[3,3,3,3,3, 3,18]
不同国家/地区使用多种方法来处理舍入问题。下面的My code使用瑞士使用的Hagenbach-Bischoff quota方法,该方法基本上将整数除以(N + 1)后剩下的席位分配给余数最多的方:
def proportional(nseats,votes):
"""assign n seats proportionaly to votes using Hagenbach-Bischoff quota
:param nseats: int number of seats to assign
:param votes: iterable of int or float weighting each party
:result: list of ints seats allocated to each party
"""
quota=sum(votes)/(1.+nseats) #force float
frac=[vote/quota for vote in votes]
res=[int(f) for f in frac]
n=nseats-sum(res) #number of seats remaining to allocate
if n==0: return res #done
if n<0: return [min(x,nseats) for x in res] # see siamii's comment
#give the remaining seats to the n parties with the largest remainder
remainders=[ai-bi for ai,bi in zip(frac,res)]
limit=sorted(remainders,reverse=True)[n-1]
#n parties with remainter larger than limit get an extra seat
for i,r in enumerate(remainders):
if r>=limit:
res[i]+=1
n-=1 # attempt to handle perfect equality
if n==0: return res #done
raise #should never happen
然而,这种方法并不总能给出与你的情况完全平等的派对相同数量的席位:
proportional(20,[3, 3, 3, 3, 3, 3, 18])
[2,2,2,2,1,1,10]
答案 1 :(得分:6)
对这个问题有一个非常简单的答案:我已经做过很多次了。在每次分配到新数组后,您将减少正在使用的值,如下所示:
就是这样!易于在任何编程语言或电子表格中实现。
解决方案是最佳的,因为结果数组的元素永远不会超过理想的非舍入值。让我们用你的例子来证明:
T = 36,S = 20.B [1] =圆(A [1] / T * S)= 2.(理想情况下,1.666 ......)
T = 33,S = 18.B [2] =圆(A [2] / T * S)= 2.(理想情况下,1.666 ......)
T = 30,S = 16.B [3] =圆(A [3] / T * S)= 2.(理想情况下,1.666 ......)
T = 27,S = 14.B [4] =圆(A [4] / T * S)= 2.(理想情况下,1.666 ......)
T = 24,S = 12.B [5] =圆(A [5] / T * S)= 2.(理想情况下,1.666 ......)
T = 21,S = 10.B [6] =圆(A [6] / T * S)= 1.(理想情况下,1.666 ......)
T = 18,S = 9.B [7] =圆(A [7] / T * S)= 9.(理想情况下,10)
请注意,将B中的每个值与括号中的理想值进行比较,差值绝不会超过1
值得注意的是,重新排列数组中的元素可能会在结果数组中产生不同的对应值。我发现以升序排列元素是最好的,因为它导致实际和理想之间的最小平均百分比差异。
答案 2 :(得分:2)
您已设置3个不兼容的要求。与[1,1,1]
成比例的整数值数组不能精确地求和为20.您必须选择将“总和精确到20”,“与输入成比例”和“整数值”要求中的一个。 / p>
如果您选择中断整数值的要求,则使用浮点数或有理数。如果您选择打破确切的总和要求,那么您已经解决了问题。选择打破比例是有点棘手的。您可能采取的一种方法是计算总和的距离,然后通过输出数组随机分配更正。例如,如果您的输入是:
[1, 1, 1]
然后你可以在尽可能保持比例的同时尽可能地总和:
[7, 7, 7]
从20 - (7+7+7) = -1
起,选择一个随机递减的元素:
[7, 6, 7]
如果错误为4
,您可以选择四个要递增的元素。
答案 3 :(得分:1)
一种天真的解决方案效果不佳,但会提供正确的结果......
编写一个迭代器,给定一个包含八个整数(candidate
)和input
数组的数组,输出最远离其他元素的索引(伪代码):< / p>
function next_index(candidate, input)
// Calculate weights
for i in 1 .. 8
w[i] = candidate[i] / input[i]
end for
// find the smallest weight
min = 0
min_index = 0
for i in 1 .. 8
if w[i] < min then
min = w[i]
min_index = i
end if
end for
return min_index
end function
然后就这样做
result = [0, 0, 0, 0, 0, 0, 0, 0]
result[next_index(result, input)]++ for 1 .. 20
如果没有最佳解决方案,它将偏向阵列的开头。
使用上面的方法,您可以通过向下舍入来减少迭代次数(正如您在示例中所做的那样),然后使用上面的方法添加由于舍入错误而遗漏的内容:
result = <<approach using rounding down>>
while sum(result) < 20
result[next_index(result, input)]++
答案 4 :(得分:0)
所以上面的答案和评论都很有用......特别是来自@Frederik的评论减少。
我想出的解决方案利用了这样的事实:对于输入数组v,sum(v_i * 20)可以被sum(v)整除。因此,对于v中的每个值,我多乘20并除以总和。我保留商,并积累余数。只要累加器大于sum(v),我就在值上加一。这样我就可以保证所有剩余部分都会进入结果。
这是否清晰可辨?这是Python中的实现:
def proportion(values, total):
# set up by getting the sum of the values and starting
# with an empty result list and accumulator
sum_values = sum(values)
new_values = []
acc = 0
for v in values:
# for each value, find quotient and remainder
q, r = divmod(v * total, sum_values)
if acc + r < sum_values:
# if the accumlator plus remainder is too small, just add and move on
acc += r
else:
# we've accumulated enough to go over sum(values), so add 1 to result
if acc > r:
# add to previous
new_values[-1] += 1
else:
# add to current
q += 1
acc -= sum_values - r
# save the new value
new_values.append(q)
# accumulator is guaranteed to be zero at the end
print new_values, sum_values, acc
return new_values
(我添加了一个增强功能,如果累加器&gt;余数,我增加前一个值而不是当前值)