按类型有效地汇总项目

Posted

技术标签:

【中文标题】按类型有效地汇总项目【英文标题】:Efficiently sum items by type 【发布时间】:2017-09-08 16:37:28 【问题描述】:

我有一个具有属性 "Type""Time" 的项目列表,我想快速总结每个 "Type" 的时间并附加到另一个列表。该列表如下所示:

Items = ['Name': A, 'Type': 'Run', 'Time': 5,  
          'Name': B, 'Type': 'Walk', 'Time': 15,  
          'Name': C, 'Type': 'Drive', 'Time': 2,
          'Name': D, 'Type': 'Walk', 'Time': 17,  
          'Name': E, 'Type': 'Run', 'Time': 5]

我想做这样的事情:

Travel_Times=[("Time_Running","Time_Walking","Time_Driving")]
Run=0
Walk=0
Drive=0    

for I in Items:
    if I['Type'] == 'Run':
       Run=Run+I['Time']
    elif I['Type'] == 'Walk': 
       Walk=Walk+I['Time']           
    elif I['Type'] == 'Drive': 
       Drive=Drive+I['Time']           

Travel_Times.append((Run,Walk,Drive))    

Travel_Times 最终看起来像这样:

print(Travel_Times)
[("Time_Running","Time_Walking","Time_Driving")
 (10,32,2)]

这似乎应该很容易通过列表理解或类似于collections.Counter 的东西有效地完成,但我无法弄清楚。我认为最好的方法是对每个“类型”使用单独的列表理解,但这需要反复遍历列表。我将不胜感激有关如何加快速度的任何想法。

谢谢

【问题讨论】:

@roganjosh。实际代码计算并汇总了大约 1.1 个不同路径的 Items 列表,每个路径中有 1 到 250 个 Items。我最初的解决方案在 2860 秒内运行。 Roganjosh 的解决方案(使用字典)运行时间为 1285 秒(快 2.2 倍),而 Eric Dumhill 的解决方案(使用 Counter)运行时间为 899 秒(比我的代码快 3.2 倍)。 我还用只有 3000 条路径的较小子集对其进行了测试,并获得了几乎相同的 % 速度提升。 【参考方案1】:

如果您愿意滥用生成器的副作用:

from collections import Counter
count = Counter()
# throw away the resulting elements, as .update does the work for us
[_ for _ in (count.update(item['Type']:item['Time']) for item in items) if _]

>>> count
Counter('Walk': 32, 'Run': 10, 'Drive': 2)

这是因为Counter.update() 返回Noneif None 将始终评估 False 并丢弃该元素。所以这会生成一个副作用空列表[] 作为唯一的内存开销。 if False 也同样有效。

【讨论】:

【参考方案2】:

只需使用字典!请注意,在 python 中,将snake_case 用于变量和键是惯用的。

travel_times = 'run': 0, 'walk': 0, 'drive': 0
for item in items:
    action, time = item['type'], item['time']
    travel_times[action] += time

【讨论】:

【参考方案3】:

您可以使用来自collectionsCounter 以及来自itertoolschainrepeat

from itertools import chain, repeat
from collections import Counter

from_it = chain.from_iterable
res = Counter(from_it(repeat(d['Type'], d['Time']) for d in Items))

这个小 sn-p 生成一个包含总和的 Counter 实例:

print(res)
Counter('Drive': 2, 'Run': 10, 'Walk': 32)

显然,它使用repeatd['Type'] 重复d['Time'] 次,然后将所有这些提供给Counter 以使用chain.from_iterable 求和。


如果您的Items 列表有很多条目,您可以再次使用chain.from_iterable 将它们链接在一起:

res = Counter(from_it(repeat(d['Type'], d['Time']) for d in from_it(Items)))

这将为您提供所有嵌套列表中所有类型的总和。

【讨论】:

至少在 Python 2.7 上,我得到了TypeError: string indices must be integers, not str @roganjosh 我不确定是否从2.7 更改为3.x,因为我没有那么多使用2。据我了解,它在3.x 对你来说很好,对吧? 更新:无法验证2.7中的故障。 不幸的是,Enthought Canopy 将我限制在 2.7 中,因此切换和测试对我来说并不容易。我需要找出它不满意的部分,在 2 和 3 之间给出这种错误似乎是一个很大的变化...... 我认为你们都有不同的测试数据。 Jim 有自己的列表嵌套在另一个列表中,而 rogan 只是有 dicts 列表:) 来自 OP 的错字 @MosesKoledoye 啊,有道理,我们选择了不同的路径来修复不平衡的[ :)【参考方案4】:

以下是用一句话表达您想要表达的简短方式。顺便说一句,您的列表Items 不需要双括号:

>>> Items = ['Type': 'Run', 'Name': 'A', 'Time': 5, 
         'Type': 'Walk', 'Name': 'B', 'Time': 15, 
         'Type': 'Drive', 'Name': 'C', 'Time': 2, 
         'Type': 'Walk', 'Name': 'D', 'Time': 17, 
         'Type': 'Run', 'Name': 'E', 'Time': 5]
>>> zip(("Time_Running","Time_Walking","Time_Driving"), (sum(d['Time'] for d in Items if d['Type'] == atype) for atype in 'Run Walk Drive'.split()))
[('Time_Running', 10), ('Time_Walking', 32), ('Time_Driving', 2)]

在这里,我将您的输出标签压缩到一个生成器,该生成器计算您列出的三种运输类型中的每一种的总和。对于您的确切输出,您可以使用:

>>> [("Time_Running","Time_Walking","Time_Driving"), tuple(sum(d['Time'] for d in Items if d['Type'] == atype) for atype in 'Run Walk Drive'.split())]
[('Time_Running', 'Time_Walking', 'Time_Driving'), (10, 32, 2)]

【讨论】:

@EricDuminil 好点 - 我已经更新了措辞。在 WPM 中也许更快 ;)【参考方案5】:

注意大小写在 Python 中非常重要:

For 不是有效声明 Travel_timesTravel_Times 不同 在elif 之后没有: Travel_Times.append(... 有一个前导空格,这让 Python 感到困惑 items 有一个 [ 太多 A 未定义

话虽如此,Counter 对您的示例来说效果很好:

from collections import Counter

time_counter = Counter()

items = ['Name': 'A', 'Type': 'Run', 'Time': 5,  
          'Name': 'B', 'Type': 'Walk', 'Time': 15,  
          'Name': 'C', 'Type': 'Drive', 'Time': 2,
          'Name': 'D', 'Type': 'Walk', 'Time': 17,  
          'Name': 'E', 'Type': 'Run', 'Time': 5]

for item in items:
    time_counter[item['Type']] += item['Time']

print(time_counter)
# Counter('Walk': 32, 'Run': 10, 'Drive': 2)

获取元组列表:

[tuple(time_counter.keys()), tuple(time_counter.values())]
# [('Run', 'Drive', 'Walk'), (10, 2, 32)]

【讨论】:

我更正了格式问题。我是在工作中输入的,手头没有实际的代码或数据。【参考方案6】:

您可以将reducecollections.Counter 一起使用:

# from functools import reduce # Python 3

d = reduce(lambda x, y: x + Counter(y['Type']: y['Time']), Items, Counter())          
print(d)
# Counter('Walk': 32, 'Run': 10, 'Drive': 2)

它只是使用相应的Time 值构建Counter 更新每个Type

【讨论】:

【参考方案7】:

您可以使用 dict 来跟踪总时间。使用.get() 方法,您可以计算总次数。如果活动的键不存在,请将其计数设置为零并从那里开始计数。

items = ['Name': 'A', 'Type': 'Run', 'Time': 5,  
          'Name': 'B', 'Type': 'Walk', 'Time': 15,  
          'Name': 'C', 'Type': 'Drive', 'Time': 2,
          'Name': 'D', 'Type': 'Walk', 'Time': 17,  
          'Name': 'E', 'Type': 'Run', 'Time': 5]

totals = 

for item in items:
    totals[item['Type']] = totals.get(item['Type'], 0) + item['Time']

for k, v in totals.items():
    print("Time ing:\t  mins".format(k, v))

【讨论】:

这看起来效率很低,有很多查找 @PeterWood 这是 Raymond Hettinger 在他的一个主题演讲中推荐的方法。我针对Counter 对其进行了测试,它实际上稍微快了一点。对于这个特定的应用程序,我不确定它的性能如何,我会timeit @PeterWood 你可能想检查我测试是否正确。我测试了 Eric 的 Counter 方法:10000 loops, best of 3: 14.7 µs per loop 与我的方法:100000 loops, best of 3: 5.58 µs per loop。但是,由于调用Counter() 的开销,我认为这有点人为。对于更大的数据结构,它可能会拉平。 @PeterWood 和 chain/repeat 方法:10000 loops, best of 3: 86.5 µs per loop 在发布我的问题后的 2 小时内,我有 5 个答案,讨论了哪个应该更好,@roganjosh 甚至测试了解决方案的性能。你们摇滚!

以上是关于按类型有效地汇总项目的主要内容,如果未能解决你的问题,请参考以下文章

在熊猫数据框上按条件有效地增加值

在 Python 3.6+ 中有效地按位置访问字典项

如何有效利用点晴项目管理模块提高工作效率

有效地分组字谜

有效地从 32 位数据类型中删除 2 位

在 Powershell 中,按记录类型拆分大型文本文件的最有效方法是啥?