【问题标题】:Python - categorizing items in a list based on occurrence in a dictionary of listsPython - 根据列表字典中的出现对列表中的项目进行分类
【发布时间】:2019-03-08 08:15:28
【问题描述】:

我有一个这样的数据集(简化):

foods_dict = {}
foods_dict['fruit'] = ['apple', 'orange', 'plum']
foods_dict['veg'] = ['cabbage', 'potato', 'carrot']

我有一个我想要分类的项目列表:

items = ['orange', 'potato', 'cabbage', 'plum', 'farmer', 'egg']

我希望能够根据它们在foods_dict 中的出现将items 列表中的项目分配到更小的列表中。我认为这些子列表实际上应该是sets,因为我不希望其中有任何重复。

我第一次通过代码是这样的:

fruits = set()
veggies = set()
others = set()
for item in items:
    if item in foods_dict.get('fruit'):
        fruits.add(item)
    elif item in foods_dict.get('veg'):
        veggies.add(item)
    else:
        others.add(item)

但这对我来说似乎真的效率低下而且不必要地冗长。我的问题是,如何改进这段代码?我猜列表理解在这里可能有用,但我不确定列表的数量。

【问题讨论】:

    标签: python list dictionary


    【解决方案1】:

    对于一个有效的解决方案,您希望尽可能避免显式循环:

    items = set(items)
    fruits = set(foods_dict['fruit']) & items
    veggies = set(foods_dict['veg']) & items
    others = items - fruits - veggies
    

    这几乎肯定会比使用显式循环更快。如果水果列表很长,特别是item in foods_dict['fruit'] 会很耗时。


    目前解决方案之间的非常简单基准:

    In [5]: %%timeit
       ...: items2 = set(items)
       ...: fruits = set(foods_dict['fruit']) & items2
       ...: veggies = set(foods_dict['veg']) & items2
       ...: others = items2 - fruits - veggies
       ...: 
    1000000 loops, best of 3: 1.75 us per loop
    
    In [6]: %%timeit
       ...: fruits = set()
       ...: veggies = set()
       ...: others = set()
       ...: for item in items:
       ...:     if item in foods_dict.get('fruit'):
       ...:         fruits.add(item)
       ...:     elif item in foods_dict.get('veg'):
       ...:         veggies.add(item)
       ...:     else:
       ...:         others.add(item)
       ...: 
    100000 loops, best of 3: 2.57 us per loop
    
    In [7]: %%timeit
       ...: veggies = set(elem for elem in items if elem in foods_dict['veg'])
       ...: fruits = set(elem for elem in items if elem in foods_dict['fruit'])
       ...: others = set(items) - veggies - fruits
       ...: 
    100000 loops, best of 3: 3.34 us per loop
    

    当然,在选择之前,您应该使用“真实输入”进行一些测试。我不知道您的问题中有多少元素,并且随着输入的增加,时间可能会发生很大变化。无论如何,我的经验告诉我,至少在 CPython 中,显式循环往往比仅使用内置操作要慢。


    Edit2:更大输入的示例:

    In [9]: foods_dict = {}
       ...: foods_dict['fruit'] = list(range(0, 10000, 2))
       ...: foods_dict['veg'] = list(range(1, 10000, 2))
    
    In [10]: items = list(range(5, 10000, 13))  #some odd some even
    
    In [11]: %%timeit
        ...: fruits = set()
        ...: veggies = set()
        ...: others = set()
        ...: for item in items:
        ...:     if item in foods_dict.get('fruit'):
        ...:         fruits.add(item)
        ...:     elif item in foods_dict.get('veg'):
        ...:         veggies.add(item)
        ...:     else:
        ...:         others.add(item)
        ...: 
    10 loops, best of 3: 68.8 ms per loop
    
    In [12]: %%timeit
        ...: veggies = set(elem for elem in items if elem in foods_dict['veg'])
        ...: fruits = set(elem for elem in items if elem in foods_dict['fruit'])
        ...: others = set(items) - veggies - fruits
        ...: 
    10 loops, best of 3: 99.9 ms per loop
    
    In [13]: %%timeit
        ...: items2 = set(items)
        ...: fruits = set(foods_dict['fruit']) & items2
        ...: veggies = set(foods_dict['veg']) & items2
        ...: others = items2 - fruits - veggies
        ...: 
    1000 loops, best of 3: 445 us per loop
    

    如您所见,仅使用内置函数比显式循环快约 20 倍。

    【讨论】:

    • 很好的答案,谢谢。我不熟悉集合的 & 语法。将不得不研究那个。
    • @DarwinTech:这是set.intersection() 的简写符号。见docs.python.org/2/library/sets.html#set-objects
    • @bukzor 是也不是。 & 参数必须为sets,而set.intersection 接受任何可迭代对象。
    • @Bakuriu:奇怪的特殊情况很奇怪。他们为什么不让它们完全相同?
    • @bukzor 如果你说它们是相同的,这意味着每次你有a & b 你都可以用a.intersection(b) 替换它反之亦然。这不是的情况。当然是一个很小的区别,但如果你不知道,你可以在某个地方写 a & [1,2,3] 认为它在你遇到异常时有效。
    【解决方案2】:

    这可能会满足您的需求(例如蔬菜盒):

    veggies = set(elem for elem in items if elem in foods_dict['veg'])
    

    更全面:

    veggies = set(elem for elem in items if elem in foods_dict['veg'])
    fruits = set(elem for elem in items if elem in foods_dict['fruit'])
    others = set(items) - veggies - fruits
    

    【讨论】:

    • 太棒了! Bakuriu 和 EMS 的答案看起来都不错。关于哪个更有效/更快的任何意见。 Bakuriu 似乎更清晰一些。
    • 使用& 语法是另一个答案的好主意;我只是不知道您是否愿意从一开始就以set 的身份使用items。但是,我认为这两种方法之间的速度不会有任何显着差异(即使考虑到另一个答案中的“基准”),直到您谈论的是荒谬的大数组。在这种情况下,无论如何您都需要更优化的数据结构。但总的来说,由于这是一个类似集合的问题,因此尽可能多地使用类似集合的符号(@Bakuriu 的解决方案更适合此问题)。
    • 是的,我实际上很高兴从一开始就使用集合,因为重复完全无关紧要。对于@Bakuriu 的全面回答,我还必须给他加分。不过,所有的答案都很好 - 非常感谢您抽出宝贵的时间。
    【解决方案3】:

    这样的事情怎么样(避免使用集合操作进行列表推导):

    fruits = set(items).intersection(set(foods_dict['fruit']))
    veggies = set(items).intersection(set(foods_dict['veg']))
    others = set(items).difference(veggies.union(fruits))
    

    如果你能帮上忙,你或许可以从集合开始以避免 set() 转换。

    希望有帮助!

    编辑:似乎您关心的是效率还是冗长(并且是“pythonic”)。如果您关心效率,请记住在字节码编译器和解释器之间您不知道正在实施哪些优化(如果有的话)。通常很难在如此高的水平上优化事物。可能,但您首先需要一些基准。如果您担心成为pythonic,我会尝试更高级别(我可以在这里说声明性吗?或者我们还没有:))。

    换句话说,与其循环并告诉python它应该如何决定哪个项目去哪里,我会尽量做到可读、清晰和简洁。我认为(因为我写了上面的内容)这种风格可以准确地告诉读者你想对项目列表做什么。

    希望这会有所帮助,所有这些都只是我的意见,应该持保留态度。

    【讨论】:

      【解决方案4】:

      如果您有更多类别,这里是更通用的。 (因此每个类别都没有单独的变量。)

      from collections import defaultdict
      
      foods_dict = {}
      foods_dict['fruit'] = set(['apple', 'orange', 'plum'])
      foods_dict['veg']   = set(['cabbage', 'potato', 'carrot'])
      
      items = set(['orange', 'potato', 'cabbage', 'plum', 'farmer', 'egg'])
      
      dict_items = set.union(*foods_dict.values())
      
      assignments = defaultdict(set)
      
      assignments['other'] = dict_items.copy()
      for key in foods_dict.keys():
          assignments[key] = foods_dict[key] & items
          assignments['other'] -= foods_dict[key]
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2021-06-20
        • 1970-01-01
        • 2021-06-06
        • 1970-01-01
        • 1970-01-01
        • 2022-08-16
        • 2023-03-26
        相关资源
        最近更新 更多