【问题标题】: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 条路径的较小子集对其进行了测试,并获得了几乎相同的 % 速度提升。

标签: python python-3.x iteration list-comprehension


【解决方案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 甚至测试了解决方案的性能。你们摇滚!
        猜你喜欢
        • 2019-02-16
        • 1970-01-01
        • 1970-01-01
        • 2021-12-18
        • 2021-05-05
        • 2018-12-12
        • 1970-01-01
        • 1970-01-01
        • 2017-05-19
        相关资源
        最近更新 更多