Python中比较连续元组列表的第一个元素

7
我有一个元组列表,每个元组包含两个元素。几个子列表的第一个元素是相同的。我想比较这些子列表的第一个元素,并将第二个元素附加在一个列表中。这是我的列表:
myList=[(1,2),(1,3),(1,4),(1,5),(2,6),(2,7),(2,8),(3,9),(3,10)]

我想把它做成一个列表的列表,看起来像这样:
NewList=[(2,3,4,5),(6,7,8),(9,10)]

希望有有效的方法。

1
如果子元组的第一个元素不是公共的,怎么办?您想要一个单元素的元组吗? - Anand S Kumar
4
这不是列表的列表,而是元组的列表;这对你的问题没有影响,但你仍然应该了解它们之间的区别。 - Marcus Müller
感谢您的纠正。 - PythonNoob
4个回答

6

您可以使用OrderedDict按每个元组的第一个子元素对元素进行分组:

myList=[(1,2),(1,3),(1,4),(1,5),(2,6),(2,7),(2,8),(3,9),(3,10)]

from collections import OrderedDict

od  = OrderedDict()

for a,b in myList:
    od.setdefault(a,[]).append(b)

print(list(od.values()))
[[2, 3, 4, 5], [6, 7, 8], [9, 10]]

如果您真的想要元组:
print(list(map(tuple,od.values())))
[(2, 3, 4, 5), (6, 7, 8), (9, 10)]

如果你不关心元素出现的顺序,只想要最有效的分组方式,可以使用collections.defaultdict
from collections import defaultdict

od  = defaultdict(list)

for a,b in myList:
    od[a].append(b)

print(list(od.values()))

最后,如果您的数据按照您的输入示例排序,即有序的话,您可以简单地使用itertools.groupby按每个元组的第一个子元素进行分组,并从分组的元组中提取第二个元素。
from itertools import groupby
from operator import itemgetter
print([tuple(t[1] for t in v) for k,v in groupby(myList,key=itemgetter(0))])

输出:

[(2, 3, 4, 5), (6, 7, 8), (9, 10)]

再次强调,groupby 只有在数据至少按第一个元素进行了排序后才能正常工作。

对于一个适当大小的列表,我们进行了一些计时:

In [33]: myList = [(randint(1,10000),randint(1,10000)) for _ in range(100000)]

In [34]: myList.sort()

In [35]: timeit ([tuple(t[1] for t in v) for k,v in groupby(myList,key=itemgetter(0))])
10 loops, best of 3: 44.5 ms per loop

In [36]: %%timeit                                                               od = defaultdict(list)
for a,b in myList:
    od[a].append(b)
   ....: 
10 loops, best of 3: 33.8 ms per loop

In [37]: %%timeit
dictionary = OrderedDict()
for x, y in myList:
     if x not in dictionary:
        dictionary[x] = [] # new empty list
    dictionary[x].append(y)
   ....: 
10 loops, best of 3: 63.3 ms per loop

In [38]: %%timeit   
od = OrderedDict()
for a,b in myList:
    od.setdefault(a,[]).append(b)
   ....: 
10 loops, best of 3: 80.3 ms per loop

如果顺序很重要且数据已经排序,使用groupby,如果需要将所有元素映射到defaultdict中的元组,则它将更接近于defaultdict方法。
如果数据未排序或您不关心任何顺序,那么除了使用defaultdict方法外,您找不到更快的分组方式。

谢谢Padraic提供的代码。这帮助我解决了我的问题。 - PythonNoob
同意,这可能是最有效的方法。 - Marcus Müller
@PadraicCunningham,我撤回自己的看法,认为这可能不是最有效的方法;请查看我的第二个答案 - Marcus Müller
@MarcusMüller,你的任何方法都没有保持顺序,如果顺序无关紧要,我的defaultdict方法将比你的两个答案更有效。我不确定groupby是否可行,因为我没有计时。 - Padraic Cunningham
@PadraicCunningham 说得好,但使用 OrderedDict() 而不是 {} 就可以解决这个问题,所以我在我的更新中提到了你的答案。当然,前提是“有序”并不意味着“按第一个元组成员的首次出现顺序排序”,而是“按数字排序”。 - Marcus Müller

4

这似乎是一个需要字典的任务(如果你还不知道字典,可以在python.org上查找)。这是一个非常冗长的例子,所以在日常编码中不会这样写,但是比模糊不清要好:

dictionary = collections.OrderedDict()
for x, y in myList:
    if not dictionary.has_key(x):
        dictionary[x] = [] # new empty list
    # append y to that list
    dictionary[x].append(y)

谢谢马库斯的建议。我想了解Python的不同模块,这将使我精通Python。如果你有更好的建议,请告诉我。 - PythonNoob

2

经过思考,最有效的方法可能是这个一行代码(假设dictionary是一个空的dict,即dictionary = {}dictionary = OrderedDict(),就像Padraic 的优秀答案中所示):

for x,y in myList: dictionary.setdefault(x,[]).append(y)

我不是说这是最容易阅读的方法,但我喜欢它 :)
编辑:哈!基准测试证明了我的错误;`setdefault`方法比`if not dictionary.has_key(x): dictionary[x]=[]`方法慢:
>>> timeit.timeit("for x,y in myList:\n    if not dictionary.has_key(x):\n        dictionary[x]=[]\n    dictionary[x].append(y)", "from collections import OrderedDict\nmyList=[(1,2),(1,3),(
1,4),(1,5),(2,6),(2,7),(2,8),(3,9),(3,10)]\ndictionary=OrderedDict()")
2.2573769092559814
>>> timeit.timeit("for x,y in myList: dictionary.setdefault(x,[]).append(y)", "from collections import OrderedDict\nmyList=[(1,2),(1,3),(1,4),(1,5),(2,6),(2,7),(2,8),(3,9),(3,10)]\ndictiona
ry=OrderedDict()")
3.3534231185913086

当然了,Padraic 仍然是正确的: 他的 defaultdict 方法在我的机器上只需 0.82 秒,因此速度要快三倍。
此外,正如 Padraic 指出的那样:dict.has_key(x) 已被弃用,应该使用 x in dict。但是,我无法测量其速度差异。

这是一个自给自足的答案。我不同意你对此的批评。另外,基准测试! - Marcus Müller
has_key在Python3中已被移除,使用if key in dict更为合适。 - Padraic Cunningham
@PadraicCunningham:没错,但 Q 说的是 [tag:python-2.7]。 - Marcus Müller
@PadraicCunningham:恭喜,比我更快三倍的方法! - Marcus Müller
1
在Python2中已经被弃用,inhas_key更快。 - Padraic Cunningham
显示剩余7条评论

1
以下应该有效:
import itertools

myList = [(1,2),(1,3),(1,4),(1,5),(2,6),(2,7),(2,8),(3,9),(3,10)]
print [tuple(x[1] for x in g) for k, g in itertools.groupby(myList, key=lambda x: x[0])]

这将显示:

[(2, 3, 4, 5), (6, 7, 8), (9, 10)]

抱歉,完全是巧合。 - Martin Evans
可能需要提到数据必须按排序顺序排列,至少在第一个元素方面是这样的。 - Padraic Cunningham

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