2015-05-06 47 views
3

我见过很多关于从列表中删除重复项并计算它们的问题。但我试图找到将它们分组的最佳方式 - 列表列表。Python - 根据索引在列表中重复列表

鉴于这个例子,我想在第三场小组:

[[1, "text", "name1", "text"], 
[2, "text", "name2", "text"], 
[3, "text", "name2", "text"], 
[4, "text", "name1", "text"]] 

我希望得到这样的:

[[[1, "text", "name1", "text"], 
    [4, "text", "name1", "text"]], 
[[2, "text", "name2", "text"], 
    [3, "text", "name2", "text"]]] 

我可以通过,只是循环想用简单的方式跟踪发现的内容(O(n^2))。但我会认为还有更好的办法。

回答

4

你可以排序,并使用GROUPBY但这是O(n log n)

from operator import itemgetter 
from itertools import groupby 

print([list(v) for _,v in groupby(sorted(l,key=itemgetter(2)),itemgetter(2))]) 

或者由第三个元素使用第三个元素为重点,并追加子列表作为值使用OrderedDict分组为O(n)解决方案。 setdefault将处理重复键:

from collections import OrderedDict 

od = OrderedDict() 

for sub in l: 
    od.setdefault(sub[2],[]).append(sub) 
from pprint import pprint as pp 
pp(od.values()) 
[[[1, 'text', 'name1', 'text'], [4, 'text', 'name1', 'text']], 
[[2, 'text', 'name2', 'text'], [3, 'text', 'name2', 'text']]] 

如果为了不要紧,你可以代替OrderedDict的使用defaultdict

如果顺序无关紧要,defaultdict是迄今为止效率最高的。

In [7]: from itertools import groupby 

In [8]: from collections import OrderedDict, defaultdict        

In [9]: l = [[1, "text", "name{}".format(choice(list(range(2000)))), "text"] for _ in xrange(40000)] 

In [13]: from operator import itemgetter 

In [14]: timeit [list(v) for _,v in groupby(sorted(l,key=itemgetter(2)),itemgetter(2))] 
10 loops, best of 3: 42.5 ms per loop 

In [15]: %%timeit                  
od = defaultdict(list) 
for sub in l: 
    od[sub[2]].append(sub) 
    ....: 
100 loops, best of 3: 9.42 ms per loop 

In [16]: %%timeit                  
od = OrderedDict() 
for sub in l: 
    od.setdefault(sub[2],[]).append(sub) 
    ....: 
10 loops, best of 3: 25.5 ms per loop 

In [17]: lists = l 

In [18]: %%timeit 
    ....: groupers = set(l[2] for l in lists) 
    ....: [filter(lambda x: x[2] == y, lists) for y in groupers] 
    ....: 

1 loops, best of 3: 8.48 s per loop 

In [19]: timeit l = [filter(lambda x: x[2] == y, lists) for y in set(l[2] for l in lists)] 
1 loops, best of 3: 8.29 s per loop 

所以,如果顺序并不重要,那么defaultdict胜,GROUPBY仍然执行得很好的排序依然是相比于二次方法相当便宜。正如你所看到的,随着数据的增长,过滤器的二次复杂性表现不佳。

+1

'for _,v in groupby'会更好! – Kasramvd

+1

@卡斯拉,是的。太习惯于检查'如果k'! –

+0

如果关键项目不只是重复,并且如果您先将列表随机洗牌,我认为groupby会失去速度测试。它为我做了。 –

1

在这里你去:

>>> lists = [[1, "text", "name1", "text"], 
... [2, "text", "name2", "text"], 
... [3, "text", "name2", "text"], 
... [4, "text", "name1", "text"]] 
>>> groupers = set(l[2] for l in lists) 
>>> groupers 
set(['name2', 'name1']) 
>>> l = [filter(lambda x: x[2] == y, lists) for y in groupers] 
>>> pprint.pprint(l) 
[[[2, 'text', 'name2', 'text'], [3, 'text', 'name2', 'text']], 
[[1, 'text', 'name1', 'text'], [4, 'text', 'name1', 'text']]] 

当然你可以写全分组逻辑一行:

>>> l = [filter(lambda x: x[2] == y, lists) for y in set(l[2] for l in lists)] 
>>> pprint.pprint(l) 
[[[2, 'text', 'name2', 'text'], [3, 'text', 'name2', 'text']], 
[[1, 'text', 'name1', 'text'], [4, 'text', 'name1', 'text']]] 
0

这样做的最简单的方法是使用sorted()key参数功能。在您的例子:

>>> a = [[1, "text", "name1", "text"], [2, "text", "name2", "text"], [3, "text", "name2", "text"], [4, "text", "name1", "text"]]

>>> sorted(a[:], key=lambda item:item[2])

>>> [[1, 'text', 'name1', 'text'], [4, 'text', 'name1', 'text'], [2, 'text', 'name2', 'text'], [3, 'text', 'name2', 'text']]

您可以找到有关this link这种说法的更多信息。

+0

分组在哪里? –

+0

是的,但只是对它进行排序。我想你需要groupby()下一个 –

0

使用sorted与元素要排序为keyitertools groupby到组“EM:

>>> from itertools import groupby 
>>> sl = sorted(your_list, key=lambda your_list: your_list[2]) 
>>> [list(v) for k,v in groupby(sl, key=lambda sl:sl[2])] 
[[[1, 'text', 'name1', 'text'], 
    [4, 'text', 'name1', 'text']], 
[[2, 'text', 'name2', 'text'], 
    [3, 'text', 'name2', 'text']]] 
+0

是的,就是这样。那么分组呢? –

+0

没有注意分组,可以使用itertools groupby;更新了解决方案.. –

0

下列功能就会很快(没有排序需要)组任意长度的子序列由指定的密钥索引

# given a sequence of sequences like [(3,'c',6),(7,'a',2),(88,'c',4),(45,'a',0)], 
# returns a dict grouping sequences by idx-th element - with idx=1 we have: 
# if merge is True {'c':(3,6,88,4),  'a':(7,2,45,0)} 
# if merge is False {'c':((3,6),(88,4)), 'a':((7,2),(45,0))} 
def group_by_idx(seqs,idx=0,merge=True): 
    d = dict() 
    for seq in seqs: 
     if isinstance(seq,tuple): seq_kind = tuple 
     if isinstance(seq,list): seq_kind = list 
     k = seq[idx] 
     v = d.get(k,seq_kind()) + (seq[:idx]+seq[idx+1:] if merge else seq_kind((seq[:idx]+seq[idx+1:],))) 
     d.update({k:v}) 
    return d 

在的情况下你的问题,关键是有元素索引2,因此

group_by_idx(your_list,2,False) 

{'name1': [[1, 'text', 'text'], [4, 'text', 'text']], 
'name2': [[2, 'text', 'text'], [3, 'text', 'text']]} 

是不完全的输出,你问这,但还不如满足您的需求。