Python在列表中对元素进行分组,其大小不断增加

时间:2014-04-11 14:28:42

标签: python list python-3.x

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 个答案:

答案 0 :(得分: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()

答案 1 :(得分: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))获取。

答案 2 :(得分:1)

是的,答案很简单。

>>> test = [1, 2, 3, 4, 5, 6, 7]
>>> bound = int((-1 + (1 + 8 * len(test)) ** 0.5) / 2)
>>> res = [test[(i + 1) * i // 2 : (i + 1) * (i + 2) // 2] for i in xrange(bound)]
>>> res
[[1], [2, 3], [4, 5, 6]]

因为每个切片的大小是一个算术序列。并且计算算术序列总数的等式是已知的。因此,我们可以直接用该等式计算每个切片的开始和结束索引。

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

这一行确保了,列表中的最后一项只有在长度大于最后一项时才会保留。

答案 4 :(得分:1)

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

此处的关键是StopIteration next(it)例外也会打破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&#39;答案。

答案 5 :(得分: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中进行整数输入。)