切片和分组列表

9
例如,如果我们的源列表是:
input = [1, 2, 3, 4, 5, 6, 7, 8, 9, ... ]

我需要类似这样的东西:

output = {1:[1], 2:[2,3], 3:[4,5,6], 4:[7,8,9,...], ...}

我尝试了以下方法,但是它没有正确地工作:
groups = {}
N = 1
group = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
for i in range(0, len(group)-1):
    groups.update({N:group[i:i+N]})
    N+=1

2
不要将变量命名为 input,因为它会遮蔽内置函数。 - Bhargav Rao
这只是一个例子。 - TramZzZ
应该在哪里结束? - Padraic Cunningham
这似乎是一个有趣的问题。我建议我们对所有解决方案进行基准测试 :) - Shashank
6个回答

5
为了完整起见 - 您还可以编写适用于任何可迭代对象的版本。
from itertools import islice, count

group = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
res = {k:v for k,v in enumerate(iter(lambda i=iter(group), c=count(1): list(islice(i, next(c))), []), 1)}
# {1: [1], 2: [2, 3], 3: [4, 5, 6], 4: [7, 8, 9, 10]}

3
你可以使用一个字典推导式,但首先需要找到一个适当的索引范围,以便可以根据它们拆分元素!为此,您可以使用一个简单的数学公式,即从1...n的序列的总和是n*(n+1)/2,因此在这种情况下,n*(n+1)/2=len(l),通过解方程可以得到n,公式为(1+math.sqrt(1+8*len(l)))/2)
一些例子:
>>> l=[23,12,33,42,5,6,7,8,39,10,11,102]
>>> ind=range(1,int((1+math.sqrt(1+8*len(l)))/2))
>>> {i:l[sum(ind[:i-1]):sum(ind[:i-1])+i] for i in ind}
{1: [23], 2: [12, 33], 3: [42, 5, 6], 4: [7, 8, 39, 10]}

由于11,102的长度不是5,因此在这种情况下n将为4!但以下代码涵盖了所有元素:

>>> l=[23,12,33,42,5,6,7,8,39,10,11,102,4,0,5]
>>> ind=range(1,int((1+math.sqrt(1+8*len(l)))/2))
>>> {i:l[sum(ind[:i-1]):sum(ind[:i-1])+i] for i in ind}
{1: [23], 2: [12, 33], 3: [42, 5, 6], 4: [7, 8, 39, 10], 5: [11, 102, 4, 0, 5]}

作为更好的方法,您可以仅计算一次sum(ind[:i-1])

>>> for i in ind:
...    s=sum(ind[:i-1])
...    d[i]=l[s:s+i]
... 
>>> d
{1: [23], 2: [12, 33], 3: [42, 5, 6], 4: [7, 8, 39, 10], 5: [11, 102, 4, 0, 5]}

最后的说明,正如你在第一个例子中所看到的,这个解决方案如果最后元素的数量与相应的长度不匹配,就不会保留它们。如果你想保留最后的元素,可以使用其他好的答案!


二次公式和三角形数?令人印象深刻 :) 我也在考虑类似的东西。 - Shashank
那么 l = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10,11] 呢? - Padraic Cunningham
@PadraicCunningham {1: [1], 2: [2, 3], 3: [4, 5, 6], 4: [7, 8, 9, 10]} - Mazdak
2
对于那些感到困惑的人,这个问题可以通过使用 http://en.wikipedia.org/wiki/Quadratic_equation 并结合从这里得出的公式 n(n+1)/2 来解决:http://en.wikipedia.org/wiki/Triangular_number - Shashank
我们不应该保留11吗? - Padraic Cunningham
@PadraicCunningham OP没有澄清,但我在考虑为此添加一个替代方案! - Mazdak

3

与Jon的方法类似:

from itertools import islice 
it = iter(l)      
d = {k: v for k,v in ((i,list(islice(it, i))) for i in range(1, len(l))) if v}
print(d)
{1: [1], 2: [2, 3], 3: [4, 5, 6], 4: [7, 8, 9, 10]}

或者使用takewhile和itemgetter:
from itertools import islice,takewhile
from operator import itemgetter

it = iter(l)

d = {k: v for k,v in takewhile(itemgetter(1),((i,list(islice(it, i))) for i in range(1, len(l))))}
print(d)
{1: [1], 2: [2, 3], 3: [4, 5, 6], 4: [7, 8, 9, 10]}

takewhile函数更加高效:

In [18]: l = list(range(100000))

In [19]: %%timeit
it = iter(l)
d = {k: v for k,v in takewhile(itemgetter(1),((i,list(islice(it, i))) for i in range(1, len(l))))}
   ....: 
100 loops, best of 3: 2.51 ms per loop    
In [20]: %%timeit
it = iter(l)                 
d = {k: v for k,v in ((i,list(islice(it, i))) for i in range(1, len(l))) if v}
   ....: 
10 loops, best of 3: 65.7 ms per loop
In [29]: timeit {k:v for k,v in enumerate(iter(lambda i=iter(group), c=count(1): list(islice(i, next(c))), []), 1)}
100 loops, best of 3: 2.74 ms per loop

In [33]: %%timeit
  ....: it = iter(l)
  ....: dict(zip(count(1), takewhile(lambda x: x, (list(islice(it, i))   for i in count(1)))))
   ....: 
   100 loops, best of 3: 2.73 ms per loop

数学获胜,但并没有我想象中的那么多:

In [23]: timeit  dict(groups(l))

1000 loops, best of 3: 1.53 ms per loop

使用 itertools.count 代替 range 函数可以进一步提高性能:

n [36]: %%timeit
   ....: it = iter(l)
   ....: {k: v for k, v in takewhile(itemgetter(1),
   ....:         ((i, list(islice(it, i))) for i in count(1)))}
   ....: 
100 loops, best of 3: 2.38 ms per loop

如果需要更简洁的选项,请使用dict:

it = iter(l)
d= dict(takewhile(itemgetter(1),
    ((i, list(islice(it, i))) for i in count(1))))

1
看起来更好了! :) - Bhargav Rao
1
@BhargavRao,教授Bhargav,现在您开心了吗?;) - Padraic Cunningham
慢了6秒...这很糟糕。 - Bhargav Rao
1
@BhargavRao,我变老了,糟糕的事情发生了 ;) - Padraic Cunningham
不错的解决方案!:) 如果你将数学与高速访问缓存的预计算解决方案进行比较,数学几乎总是会胜出。 - Shashank
显示剩余2条评论

2

您的代码几乎正确,但逻辑有误。我添加了一个名为 start 的变量,它是每个新组应该开始的索引,并将循环改为 while;当 start 大于或等于列表长度时,我们已处理完所有项。

groups = {}
N = 1
group = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
start = 0
while start < len(group):
    groups[N] = group[start:start + N]
    start += N
    N += 1

输出

{1: [1], 2: [2, 3], 3: [4, 5, 6], 4: [7, 8, 9, 10]}

此外,在这里没有必要使用update,仅将值分配给字典键即可。


2
一种基于数学的解决方案:
import math

def groups(l):
  for i in range(1,int((math.sqrt(8*len(l)+1)+1)/2)):
    start = int(i*(i-1)/2)
    yield i, l[start:start+i]

l = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
r = dict(groups(l))

结果:r == {1: [1], 2: [2, 3], 3: [4, 5, 6], 4: [7, 8, 9, 10]}

不截断的版本:

import math

def groups(l):
  for i in range(1,math.ceil((math.sqrt(8*len(l)+1)+1)/2)):
    start = int(i*(i-1)/2)
    yield i, l[start:min(start+i,len(l))]

l = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
r = dict(groups(l))

结果:r == {1:[1],2:[2,3],3:[4,5,6],4:[7,8,9,10],5:[11]}


如果你把11加起来,你会失去它。 - Padraic Cunningham
是的,但是通过添加11到15确实可以解决问题。问题没有指定输入是否适合输出格式。 - user2124834
1
是的,我只是指出它截断了,我在评论中问了原帖发布者应该在哪里结束。 - Padraic Cunningham
添加了一个不截断的版本。 - user2124834
好的加一,我一开始尝试了数学方法,但公式错了。我需要对它们进行计时并观察。 - Padraic Cunningham

1
你可以使用一个生成器:

from itertools import count, repeat

def gen(it):
    for i in count(1):
        yield i, map(next, repeat(it, i))

print dict(gen(iter([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])))

结果:

{1: [1], 2: [2, 3], 3: [4, 5, 6], 4: [7, 8, 9, 10]}

或者只是:

或者:

from itertools import count, takewhile, islice, izip

it = iter([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
dict(izip(count(1), takewhile(lambda x: x, (list(islice(it, i)) for i in count(1)))))

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接