分配一个按比例补偿舍入误差的整数数组

时间:2013-04-26 00:41:52

标签: algorithm math discrete-mathematics

我有一组非负值。我想构建一个值为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个?

5 个答案:

答案 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)

对这个问题有一个非常简单的答案:我已经做过很多次了。在每次分配到新数组后,您将减少正在使用的值,如下所示:

  1. 调用第一个数组A和新的比例数组B(以空白开头)。
  2. 调用A元素T
  3. 的总和
  4. 拨打所需的金额S.
  5. 对于数组的每个元素(i),请执行以下操作:
    一个。 B [i] = round(A [i] / T * S)。 (舍入到最接近的整数,便士或其他任何要求)
    湾T = T - A [i]
    ℃。 S = S - B [i]
  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;余数,我增加前一个值而不是当前值)