【问题标题】:Group list of tuples efficiently有效地分组元组列表
【发布时间】:2018-01-10 14:30:04
【问题描述】:

我有一个很大的元组列表,例如[ (1,2), (1,3), (1,4), (2,1), (2,3) ] 等。我想有效地将​​其转换为[ (1, [1,2,3,4]), (2, [1,3] ) ]。我按每个元组的第一个元素对元组进行分组,即(1,2), (1,3), (1,4) 变为(1, [2,3,4])(另请参见下面的 Haskell 版本)。我怀疑这可以一次性完成吗? 输入列表总是有序的。

python 中尝试使用defaultdict,我认为这是无需重新发明轮子的自然解决方案。它运行良好,但不保留键的顺序。一种解决方案是使用有序的defaultdict 作为explained here

无论如何,我想知道这个问题的语言独立且有效的解决方案。我当前的解决方案需要两次通过和一次调用列表中的set( )

更新

我正在考虑实现以下 Haskell 版本:

a = [ (1,2), (1,3), (1,4), (2,1), (2,3) ] 
b = groupBy (\ x y -> fst x == fst y ) 
b 
[[(1,2),(1,3),(1,4)],[(2,1),(2,3)]]  
map (\x -> (fst .head $ x, map snd x ) ) b 
[(1,[2,3,4]),(2,[1,3])]

答案的表现

我实现了两个答案(coldspeed 和 pm2ring)。在中等大小的列表(最多 10^4 个元素)上,PM2 环解决方案更快;在大小为 10^5 时,两者都需要相同的时间,在较大的列表中 COLDSPEED 开始获胜。以下是数字(使用python3)。

第一列是列表中的条目数,第二列是coldspeed 花费的时间,第三列是pm2 ring 解决方案花费的时间。所有时间都以秒为单位。

10 0.0001 0.0000
100 0.0001 0.0000
1000 0.0005 0.0001
10000 0.0044 0.0014
100000 0.0517 0.0452
1000000 0.5579 1.5249

脚本在这里http://github.com/dilawar/playground/raw/master/Python/so_group_tuple.py

使用 Ashwini 优化

PM 2Ring 在 Ashwini 的建议下,解决方案甚至更快(大约 3 倍 - 5 倍)。

10 4.887580871582031e-05 1.2636184692382812e-05
100 0.00010132789611816406 2.0742416381835938e-05
1000 0.0005109310150146484 0.000110626220703125
10000 0.004467487335205078 0.0009067058563232422
100000 0.05056118965148926 0.017516136169433594
1000000 0.6100358963012695 0.26450490951538086
10000000 6.092756509780884 2.8253660202026367

使用 PYPY

结果有些混杂。最后一列是第 2 列和第 3 列的比率。

pypy so_group_tuple.py 
(10, [1.6927719116210938e-05, 3.409385681152344e-05], 0.4965034965034965)
(100, [4.601478576660156e-05, 8.296966552734375e-05], 0.5545977011494253)
(1000, [0.010258913040161133, 0.0019040107727050781], 5.388054094665665)
(10000, [0.0002448558807373047, 0.00021600723266601562], 1.1335540838852096)
(100000, [0.002658843994140625, 0.0018231868743896484], 1.45834967961292)
(1000000, [0.0833890438079834, 0.02979302406311035], 2.7989452709245284)
(10000000, [1.0556740760803223, 0.6789278984069824], 1.5549133841124023)

我将使用 PM 2Ring 解决方案,因为它在列表大小为 10^5 之前要快得多

【问题讨论】:

  • 请包括您当前的解决方案,并说明问题所在 - 我不清楚您是如何从第一个列表到第二个列表的。
  • 输入列表总是这样排列吗?顺便说一句,您在该列表中有错字。
  • 你的预期输出实际上是[ (1, [2,3,4]), (2, [1,3] ) ]吗?我不知道第一个元组列表中的1 来自哪里。
  • 感谢您添加时间信息。你应该看看timeit,它比手动使用time 模块更准确(也更方便)。

标签: python algorithm list group-by


【解决方案1】:

您可以使用collections.OrderedDict(先import collections):

o = collections.OrderedDict()

for x in t:
    o.setdefault(x[0], []).append(x[1])  

现在,将o.items() 转换为列表:

list(o.items())
# [(1, [2, 3, 4]), (2, [1, 3])]

【讨论】:

  • 虽然这很容易阅读,但它比 PM 2Ring 解决方案在大小为 10^5 - 10^6 的列表上要慢一些。我在问题正文中添加了一些基准。
  • @Dilawar 性能并不是唯一的考虑因素。如果您想提高速度,请使用 C ;) 您应该选择最简单、最清晰、最容易阅读和理解的内容。可以理解,PM 2Ring 的解决方案很有效,而且看起来很好看,但我想实际上知道我的代码在做什么。最后由你决定。干杯。
【解决方案2】:

如果输入列表已经排序,则不需要使用任何其他排序函数或特性来重新排序列表。 下面的代码会自动给出你所展示的输出。

mylistarr = ((1, 2), (1, 3), (1, 4), (2, 1), (2, 3))
output = dict()
for tuple in mylistarr:
    if tuple[0] not in anotherlist:
        output[tuple[0]] = list()
        output[tuple[0]].append(tuple[0])
    output[tuple[0]].append(tuple[1])
print output

输出: {1: [1, 2, 3, 4], 2: [2, 1, 3]}

【讨论】:

  • anotherlist = dict() 是错误的命名。
【解决方案3】:

您可以使用itertools.groupby 执行此操作,并使用zip 重新排列收集到的组中的数据:

from itertools import groupby
from operator import itemgetter

a = [(1, 2), (1, 3), (1, 4), (2, 1), (2, 3)]
b = [(k, list(list(zip(*g))[1])) for k, g in groupby(a, itemgetter(0))]
print(b)

输出

[(1, [2, 3, 4]), (2, [1, 3])]

该列表组合有点密集。这是一个使用传统 for 循环的变体,它打印一个中间结果,以便更容易看到发生了什么。

b = []
for k, g in groupby(a, itemgetter(0)):
    t = list(zip(*g))
    print(t)
    b.append(list(t[1]))

print('Output', b)

输出

[(1, 1, 1), (2, 3, 4)]
[(2, 2), (1, 3)]
Output [[2, 3, 4], [1, 3]]

正如 Ashwini Chaudhary 在 cmets 中提到的那样,在其中嵌套另一个列表 comp 使代码更加更具可读性,它可能也更有效,因为它避免了几次调用。

b = [(k, [x for _, x in g]) for k, g in groupby(a, itemgetter(0))]

【讨论】:

  • @AshwiniChaudhary 确实如此!谢谢你。
  • @AshwiniChaudhary 您的建议使这个实现更快。我添加了一些基准。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-06-06
  • 2021-10-14
  • 1970-01-01
  • 2011-01-15
相关资源
最近更新 更多