Python按增大尺寸分组列表元素

7 投票
6 回答
545 浏览
提问于 2025-04-18 02:22
my_list = [my_list[int((i**2 + i)/2):int((i**2 + 3*i + 3)/2)] for i in range(int((-1 + (1 + 8*len(my_list))**0.5)/2))]

有没有比这个更简洁的方法,可以把列表里的元素分成越来越大的小组呢?

举个例子:

[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11] --> [[1], [2, 3], [4, 5, 6], [7, 8, 9, 10]]
[1, 2, 3, 4] --> [[1], [2, 3]]
[1, 2, 3, 4, 5, 6] --> [[1], [2, 3], [4, 5, 6]]

编辑

这里是用 timeit 得到的结果:

from timeit import Timer
from itertools import count

def martijn(it):
    it = iter(it)
    return list([next(it) for _ in range(s)] for s in count(1))

def mathematical(it):
    upper_bound = int(((1 + 8*len(it))**0.5 + 1)//2)
    return [it[i*(i-1)//2:i*(i+1)//2] for i in range(1, upper_bound)]

def time(test, n):
    a = Timer(lambda: martijn(test)).timeit(n)
    b = Timer(lambda: mathematical(test)).timeit(n)
    return round(a, 3), round(b, 3)

>>> for i in range(8):
        loops = 10**max(0, (6-i))
        print(time([n for n in range(10**i)], loops), loops)
(6.753, 4.416) 1000000
(1.166, 0.629) 100000
(0.366, 0.123) 10000
(0.217, 0.036) 1000
(0.164, 0.017) 100
(0.157, 0.017) 10
(0.167, 0.021) 1
(1.749, 0.251) 1
>>> for i in range(8):
        loops = 10**max(0, (6-i))
        print(time(range(10**i), loops), loops)
(6.721, 4.779) 1000000
(1.184, 0.796) 100000
(0.367, 0.173) 10000
(0.218, 0.051) 1000
(0.202, 0.015) 100
(0.178, 0.005) 10
(0.207, 0.002) 1
(1.872, 0.005) 1

6 个回答

1

老实说,我不太明白你为什么想这么做,我提到这一点是因为可能有更适合你任务的方法来回答你的问题。不过,我觉得下面的方式至少更清楚一些:

def increasing_groups(l):
    current_size = 1
    while l:
        yield l[:current_size]
        l = l[current_size:]
        current_size += 1

在这个时候,你可以通过 list(increasing_groups(some_list)) 来获取它。

1

这个

(n * (n - 1) / 2, n * (n + 1) / 2)

根据高斯的说法,它会给你新列表中第n个元素的起始和结束索引。

所以

my_list[n * (n - 1) / 2 : n * (n + 1) / 2]

就是列表中的第n个元素,稍微过滤一下:

my_list = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
[my_list[n * (n - 1) / 2: n * (n + 1)/ 2] for n in range(1, len(my_list)) if n * (n + 1)/ 2 <= len(my_list)]
# [[1], [2, 3], [4, 5, 6], [7, 8, 9, 10]]

不过,使用一个真正的循环和实际的 break 可能会更好一些。

编辑

现在我知道了 StopIteration 是如何被 list 捕获的(谢谢你,Martjin),可以用以下方式简单地设置结束条件:

list(my_list[n * (n - 1) // 2: n * (n + 1) // 2] for n in count(1) if iter(my_list[n * (n + 1)/ 2:]).next() > -1)

前提是 -1 小于你列表中的任何一个项目。(而且这些地板除法是为了在python 3中处理整数类型。)

1

你可以用 itertools.count 来记录你想切割的项目数量,然后用 itertools.islice 来选择这些项目。

# Initializations and declarations
data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
from itertools import count, islice
counter, it = count(0), iter(data)

# Actual list construction
result = [[item] + list(islice(it, next(counter))) for item in it]

# Making sure that the last item of the list is consistent with the previous item
if len(result) > 1 and len(result[-1]) <= len(result[-2]): del result[-1]

print(result)
# [[1], [2, 3], [4, 5, 6], [7, 8, 9, 10]]

这里面最重要的是

if len(result) > 1 and len(result[-1]) <= len(result[-2]): del result[-1]

这一行代码确保了,列表中的最后一个项目只有在它的长度大于倒数第二个项目时才会保留。

1
def incr_grouped(iterable):
    it, n = iter(iterable), 1
    while True:
        yield [next(it) for _ in range(n)]
        n += 1

这里的关键是,next(it)StopIteration 异常会让 while 循环也停止。这意味着你可能会丢失最后一些没有被分到组里的元素。

>>> list(incr_grouped('ABCDEF'))
[['A'], ['B', 'C'], ['D', 'E', 'F']]
>>> list(incr_grouped([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]))
[[1], [2, 3], [4, 5, 6], [7, 8, 9, 10]]

使用 itertools 可以让代码变得更加简洁。可以看看 Martijn Pieters 的回答。

13

使用生成器表达式:

from itertools import count

try:
    _range = xrange
except NameError:
    # Python 3
    _range = range


def incremental_window(it):
    """Produce monotonically increasing windows on an iterable.

    Only complete windows are yielded, if the last elements do not form
    a complete window they are ignored.

    incremental_window('ABCDEF') -> ['A'], ['B', 'C'], ['D', 'E', 'F']
    incremental_window('ABCDE') -> ['A'], ['B', 'C']

    """
    it = iter(it)
    return ([next(it) for _ in _range(s)] for s in count(1))

示例:

>>> list(incremental_window([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]))
[[1], [2, 3], [4, 5, 6], [7, 8, 9, 10]]
>>> list(incremental_window([1, 2, 3, 4]))
[[1], [2, 3]]
>>> list(incremental_window([1, 2, 3, 4, 5, 6]))
[[1], [2, 3], [4, 5, 6]]

这是一个生成器,可以与任何可迭代的对象一起使用,包括那些无穷无尽的可迭代对象:

>>> from itertools import count
>>> for window in incremental_window(count()):
...     print window
...     if 25 in window:
...         break
... 
[0]
[1, 2]
[3, 4, 5]
[6, 7, 8, 9]
[10, 11, 12, 13, 14]
[15, 16, 17, 18, 19, 20]
[21, 22, 23, 24, 25, 26, 27]

可以通过一些小技巧,把它变成一行代码,这样就可以在你的列表对象上直接使用iter()函数:

list([next(it) for _ in _range(s)] for it in (iter(my_list),) for s in count(1))

撰写回答