Python 将列表分为三个列表,使其总和彼此接近

Python 将列表分为三个列表,使其总和彼此接近,python,algorithm,Python,Algorithm,假设我有一个数字s=[6,2,1,7,4,3,9,5,3,1]的数组。我想分成三个数组。数字的顺序和这些数组中项目的数量并不重要 假设A1、A2和A3是子阵列。我想最小化函数 f(x) = ( SUM(A1) - SUM(S) / 3 )^2 / 3 + ( SUM(A2) - SUM(S) / 3 )^2 / 3 + ( SUM(A3) - SUM(S) / 3 )^2 / 3 我不需要最优的解决方案;我只需要一个足够好的解决方案 我不想要一个太慢的算法。我可以

假设我有一个数字s=[6,2,1,7,4,3,9,5,3,1]的数组。我想分成三个数组。数字的顺序和这些数组中项目的数量并不重要

假设A1、A2和A3是子阵列。我想最小化函数

f(x) = ( SUM(A1) - SUM(S) / 3 )^2 / 3 +
       ( SUM(A2) - SUM(S) / 3 )^2 / 3 +
       ( SUM(A3) - SUM(S) / 3 )^2 / 3
  • 我不需要最优的解决方案;我只需要一个足够好的解决方案
  • 我不想要一个太慢的算法。我可以用一些速度来换取更好的结果,但我不能交易太多
  • S的长度约为10到30
为什么? 为什么我需要解决这个问题?我想把这个框很好地排列成三列,这样每列的总高度彼此之间不会有太大的差异

我试过什么 我的第一本能是使用贪婪。结果并不是那么糟糕,但它不能确保获得最佳解决方案。有更好的办法吗

s = [6, 2, 1, 7, 4, 3, 9, 5, 3, 1]
s = sorted(s, reverse=True)

a = [[], [], []]
sum_a = [0, 0, 0]

for x in s:
    i = sum_a.index(min(sum_a))
    sum_a[i] += x
    a[i].append(x)

print(a)

我们可以研究您找到的解决方案在替换找到的列表之间的元素方面的稳定性。我把代码放在下面。如果我们通过替换使目标功能更好,我们会保留找到的列表,并进一步希望通过另一个替换使功能更好。作为起点,我们可以采用您的解决方案。最终结果将类似于局部最小值

from copy import deepcopy

s = [6, 2, 1, 7, 4, 3, 9, 5, 3, 1]
s = sorted(s, reverse=True)

a = [[], [], []]
sum_a = [0, 0, 0]

for x in s:
    i = sum_a.index(min(sum_a))
    sum_a[i] += x
    a[i].append(x)


def f(a):
    return ((sum(a[0]) - sum(s) / 3.0)**2 + (sum(a[1]) - sum(s) / 3.0)**2 + (sum(a[2]) - sum(s) / 3.0)**2) / 3


fa = f(a)

while True:
    modified = False

    # placing
    for i_from, i_to in [(0, 1), (0, 2), (1, 0), (1, 2), (2, 0), (2, 1)]:
        for j in range(len(a[i_from])):
            a_new = deepcopy(a)
            a_new[i_to].append(a_new[i_from][j])
            del a_new[i_from][j]
            fa_new = f(a_new)
            if fa_new < fa:
                a = a_new
                fa = fa_new
                modified = True
                break
        if modified:
            break

    # replacing
    for i_from, i_to in [(0, 1), (0, 2), (1, 0), (1, 2), (2, 0), (2, 1)]:
        for j_from in range(len(a[i_from])):
            for j_to in range(len(a[i_to])):
                a_new = deepcopy(a)
                a_new[i_to].append(a_new[i_from][j_from])
                a_new[i_from].append(a_new[i_to][j_to])
                del a_new[i_from][j_from]
                del a_new[i_to][j_to]
                fa_new = f(a_new)
                if fa_new < fa:
                    a = a_new
                    fa = fa_new
                    modified = True
                    break
            if modified:
                break
        if modified:
            break

    if not modified:
        break

print(a, f(a)) # [[9, 3, 1, 1], [7, 4, 3], [6, 5, 2]] 0.2222222222222222222

它提供了不同的结果,但函数的值相同。

正如我在问题的评论中提到的,这是直接的动态规划方法。
s=range(1,30)
所需时间不到1秒,并给出了优化的解决方案

如果你知道的话,我认为代码是自我解释的


正如您所说,您不介意非最佳解决方案,我想我会重新使用您的初始函数,并添加一种方法,为初始列表
s
s找到一个良好的开始安排

您的初始功能:

def鸽子洞:
a=[]、[]、[]
和a=[0,0,0]
对于s中的x:
i=总和指数(最小值(总和))
和a[i]+=x
a[i].追加(x)
返回映射(总和,a)
这是一种为列表找到合理的初始顺序的方法,其工作原理是按排序和反向排序顺序创建列表的旋转。将列表归档后,通过最小化标准偏差找到最佳旋转:

def rotate(l):
    l = sorted(l)
    lr = l[::-1]
    rotation = [np.roll(l, i) for i in range(len(l))] + [np.roll(lr, i) for i in range(len(l))]
    blocks = [pigeon_hole(i) for i in rotation]
    return rotation[np.argmin(np.std(blocks, axis=1))]  # the best rotation

import random
print pigeon_hole(rotate([random.randint(0, 20) for i in range(20)]))

# Testing with some random numbers, these are the sums of the three sub lists
>>> [64, 63, 63]
虽然这可以进一步优化,但20个数字需要0.0013秒的速度非常快。使用
a=rotate(范围(1,30))

在许多情况下,这种方法似乎也能找到最优解,尽管这可能不适用于所有情况。使用包含30个数字的列表与Mo Tao的答案进行500次测试,并比较子列表的总和是否相同:

c=0
对于范围(500)内的i:
r=[random.randint(1,10)表示范围(30)内的j]
res=鸽子洞(旋转(r))
d、 e=排序(res),排序(tao(r))#将其与莫涛的最优解进行比较
如果全部([k==kk]表示k,zip(d,e)中的kk):
c+=1
内存={}
最佳功率=功率(总和,3)
最佳状态=无
>>>500#(他们有)
我想我会在这里提供一个更优化版本的函数更新:

def旋转2(l):
#计算鸽子洞列表中可接受的最小标准偏差
如果总和(l)%3==0:
标准=0
其他:
标准=np.std([0,0,1])
l=已排序(l,反向=真)
最佳旋转=无
最佳标准=100
对于范围内的i(len(l)):
旋转=np.滚动(l,i)
sd=np.std(鸽子洞(旋转))
如果sd==std:
返回旋转#如果找到最小stdev
elif sd<最佳标准:
最佳标准=标准差
最佳旋转=旋转
返回最佳旋转
主要的变化是,一旦找到合适的旋转,对良好排序的搜索就会停止。此外,只搜索反向排序列表,该列表似乎不会改变结果。时间安排

print timeit.timeit("rotate2([random.randint(1, 10) for i in range(30)])", "from __main__ import rotate2, random", number=1000) / 1000.

结果是速度大大加快。在我当前的计算机上,旋转大约需要1.84毫秒,
rotate2
大约需要0.13毫秒,因此大约需要14倍的加速。相比之下,在我的机器上,•㪞עבקן的实现花费了大约0.99毫秒。

我不得不说,您的贪婪函数确实产生了很好的结果,但如果输入大小很大,比如超过100,则会变得非常缓慢

但是,您已经说过,您的输入大小固定在范围-
10,30
内。因此,贪婪的解决方案实际上是相当好的。与其一开始就变得太贪婪,我建议先变得有点懒惰,然后在最后变得贪婪

这是一个经过修改的函数lazy:

def lazy(s):
    k = (len(s)//3-2)*3 #slice limit

    s.sort(reverse=True)
    #Perform limited extended slicing
    a = [s[1:k:3],s[2:k:3],s[:k:3]]

    sum_a = list(map(sum,a))
    for x in s[k:]:
        i = sum_a.index(min(sum_a))
        sum_a[i] += x
        a[i].append(x)
    return a
它首先按降序对输入进行排序,然后逐个填充三个子列表中的项目,直到剩下大约6个项目。(您可以更改此限制并进行测试,但对于大小10-30,我认为这是最好的)

这样做后,只需继续使用贪婪方法。这种方法比贪婪方法平均花费的时间更少,而且更精确

这是一个大小与时间的折线图-

尺寸与精度的对比-

准确度是最终子列表和原始列表平均值的标准偏差。因为您希望列堆叠在几乎相同的高度,而不是(原始列表的平均值)高度

此外,项目值的范围在
3-15
之间,因此,如您所述,总和约为
100-150

这些是测试功能-

def test_accuracy():
    rsd = lambda s:round(math.sqrt(sum([(sum(s)//3-y)**2 for y in s])/3),4)
    sm = lambda s:list(map(sum,s))

    N=[i for i in range(10,30)]
    ST=[]
    MT=[]
    for n in N:
        case = [r(3,15) for x in range(n)]

        ST.append(rsd(sm(lazy(case))))
        MT.append(rsd(sm(pigeon(case))))

    strace = go.Scatter(x=N,y=ST,name='Lazy pigeon')
    mtrace = go.Scatter(x=N,y=MT,name='Pigeon')
    data = [strace,mtrace]

    layout = go.Layout(
    title='Uniform distribution in 3 sublists',
    xaxis=dict(title='List size',),
    yaxis=dict(title='Accuracy - Standard deviation',))
    fig = go.Figure(data=data, layout=layout)

    plotly.offline.plot(fig,filename='N vs A2.html')

def test_timings():
    N=[i for i in range(10,30)]
    ST=[]
    MT=[]
    for n in N:
        case = [r(3,15) for x in range(n)]           
        start=time.clock()
        lazy(case)
        ST.append(time.clock()-start)
        start=time.clock()
        pigeon(case)
        MT.append(time.clock()-start)

    strace = go.Scatter(x=N,y=ST,name='Lazy pigeon')
    mtrace = go.Scatter(x=N,y=MT,name='Pigeon')
    data = [strace,mtrace]

    layout = go.Layout(
    title='Uniform distribution in 3 sublists',
    xaxis=dict(title='List size',),
    yaxis=dict(title='Time (seconds)',))

    fig = go.Figure(data=data, layout=layout)

    plotly.offline.plot(fig,filename='N vs T2.html')
这是完整的

编辑-

我测试了kezzos的答案的准确性,结果非常好。偏差始终小于0.8

100次运行的平均标准偏差

Lazy Pigeon Pigeon Rotation 1.10668795 1.1573573 0.54776425 懒鸽轮换 1.10668795 1.1573573 0.54776425 在c
def lazy(s):
    k = (len(s)//3-2)*3 #slice limit

    s.sort(reverse=True)
    #Perform limited extended slicing
    a = [s[1:k:3],s[2:k:3],s[:k:3]]

    sum_a = list(map(sum,a))
    for x in s[k:]:
        i = sum_a.index(min(sum_a))
        sum_a[i] += x
        a[i].append(x)
    return a
def test_accuracy():
    rsd = lambda s:round(math.sqrt(sum([(sum(s)//3-y)**2 for y in s])/3),4)
    sm = lambda s:list(map(sum,s))

    N=[i for i in range(10,30)]
    ST=[]
    MT=[]
    for n in N:
        case = [r(3,15) for x in range(n)]

        ST.append(rsd(sm(lazy(case))))
        MT.append(rsd(sm(pigeon(case))))

    strace = go.Scatter(x=N,y=ST,name='Lazy pigeon')
    mtrace = go.Scatter(x=N,y=MT,name='Pigeon')
    data = [strace,mtrace]

    layout = go.Layout(
    title='Uniform distribution in 3 sublists',
    xaxis=dict(title='List size',),
    yaxis=dict(title='Accuracy - Standard deviation',))
    fig = go.Figure(data=data, layout=layout)

    plotly.offline.plot(fig,filename='N vs A2.html')

def test_timings():
    N=[i for i in range(10,30)]
    ST=[]
    MT=[]
    for n in N:
        case = [r(3,15) for x in range(n)]           
        start=time.clock()
        lazy(case)
        ST.append(time.clock()-start)
        start=time.clock()
        pigeon(case)
        MT.append(time.clock()-start)

    strace = go.Scatter(x=N,y=ST,name='Lazy pigeon')
    mtrace = go.Scatter(x=N,y=MT,name='Pigeon')
    data = [strace,mtrace]

    layout = go.Layout(
    title='Uniform distribution in 3 sublists',
    xaxis=dict(title='List size',),
    yaxis=dict(title='Time (seconds)',))

    fig = go.Figure(data=data, layout=layout)

    plotly.offline.plot(fig,filename='N vs T2.html')
Lazy Pigeon Pigeon Rotation 1.10668795 1.1573573 0.54776425 Lazy Pigeon Pigeon Rotation 5.384013e-05 5.930269e-05 0.004980