将列表分割为均衡长度的部分

7 投票
5 回答
1083 浏览
提问于 2025-04-15 14:04

我需要一个算法,给定一个列表 L 和一个数字 N,它能返回一个包含 N 个小列表的列表,这些小列表要“平衡”。举个例子:

algo(range(1, 8), 3)  -> [[1,2,3], [4,5], [6,7]]
algo(range(1, 6), 4)  -> [[1,2], [3], [4], [5]]
algo(range(1, 12), 5) -> [[1,2,3], [4,5], [6,7], [8,9], [10, 11]]

你可以看到,算法应该“优先”输出第一个列表。

我已经尝试了好几个小时,但就是想不出一个简洁好用的算法。顺便说一下,这个算法会用在Python中,但我现在主要想要的是算法本身。这不是作业,而是为了一个网站,网站会把内容以三列的形式展示(用Django框架)。


我在freenode的#python频道得到了最好的答案,内容如下:

def split_up(l, n):
    q, r = divmod(len(l), n)
    def division_point(i):
        return i * q + min(i, r)
    return [l[division_point(i):division_point(i+1)] for i in range(n)]

不过别问我为什么它有效。:) 我会把正确答案给投票最多的那个人。

5 个回答

0

这是对喜欢函数式编程的人的致敬:

def algo(l, n):
    if n == 1: return [l]
    q, r = divmod(len(l),n)
    if r: q += 1
    return [l[:q]] + algo(l[q:], n - 1)

这个稍微小一点:

def algo(l, n):
    k = l[:]
    q, r = divmod(len(l),n)
    return [[k.pop(0) for _ in [0] * m]
            for m in [q + 1] * r + [q] * (n - r)]
1

假设你希望输出的列表在长度上尽量相等,如果不可能的话,就优先考虑前面的列表。子列表之间的长度差别不能超过一个。

>>> l = [0, 1, 2, 3, 4, 5, 6]
>>> def algo(li, n):
        a, b = divmod(len(li), n)
        c = [a + 1] * b + [a] * (n-b)
        s = 0
        for i, j in enumerate(c):
            c[i] = li[s:s+j]
            s += j
        return c

>>> algo(l, 3)
[[0, 1, 2], [3, 4], [5, 6]]
>>> algo(l, 4)
[[0, 1], [2, 3], [4, 5], [6]]
5

这是我写的代码,没有进行排序。如果输入的数据没有排序的话,可以直接加上一个 lst.sort()。

我觉得这个代码写得不错,使用了迭代器,并且用 islice 来截取下一部分数据。

import itertools

def partlst(lst, n):
    """Partition @lst in @n balanced parts, in given order"""
    parts, rest = divmod(len(lst), n)
    lstiter = iter(lst)
    for j in xrange(n):
        plen = len(lst)/n + (1 if rest > 0 else 0)
        rest -= 1
        yield list(itertools.islice(lstiter, plen))

parts =  list(partlst(range(1, 15), 5))
print len(parts)
print parts

撰写回答