我已经看到很多关于从列表中删除重复项并计算它们的问题。但是我试图找到将它们分组的最佳方式 - 列表列表。
鉴于这个例子,我想按第三个字段分组:
[[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))。但我认为这是一个更好的方法。
答案 0 :(得分: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))])
或者使用第三个元素对O(n)
解决方案进行OrderedDict分组,方法是使用第三个元素作为键,并将子列表作为值附加。 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']]]
如果订单无关紧要,您可以使用defaultdict代替OrderedDict。
如果顺序无关紧要,则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仍然表现得相当好,因为与二次方法相比,sort仍然相当便宜。正如您所看到的,随着数据的增长,滤波器的二次复杂性表现不佳。
答案 1 :(得分: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']]]
答案 2 :(得分:0)
最简单的方法是使用key
函数的sorted()
参数。在您的示例中:
>>> 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上找到有关此参数的更多信息。
答案 3 :(得分:0)
将sorted
与您要排序的元素一起用作key
和itertools groupby
以对其进行分组:
>>> 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']]]
答案 4 :(得分: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']]}
这不完全是您要求的输出,但可能也符合您的需求。