按类型有效地汇总项目
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()
返回None
。 if 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】:您可以使用来自collections
的Counter
以及来自itertools
的chain
和repeat
:
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)
显然,它使用repeat
将d['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_times
与 Travel_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】:您可以将reduce
与collections.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 甚至测试了解决方案的性能。你们摇滚!以上是关于按类型有效地汇总项目的主要内容,如果未能解决你的问题,请参考以下文章