【问题标题】:Better to add item to a set, or convert final list to a set?最好将项目添加到集合中,还是将最终列表转换为集合?
【发布时间】:2013-09-19 09:33:44
【问题描述】:

我有一些看起来像这样的数据:

ID1 ID2 ID3  
ID1 ID4 ID5  
ID3 ID5 ID7 ID6  
...  
...  

每一行都是一个组。

我的目标是为每个 ID 提供一个字典,然后是一组与其共享 >= 1 个组的其他 ID。

例如,此数据将返回 {ID1: [ID2, ID3, ID4, ID5], ID2:[ID1, ID3] ... }

我可以为此想到 3 个选项,我想知道哪个(通常)最好:

  1. 在添加之前检查一个 ID 是否已经在列表中
  2. 创建集合而不是列表,并将每个 ID 添加到集合中
  3. 将所有 ID 添加到列表中,然后将所有列表转换为最后的集合。

【问题讨论】:

  • 在什么意义上最好。速度?总是有timeit 模块...

标签: python loops set


【解决方案1】:

选项 2 对我来说听起来最合乎逻辑,尤其是使用 defaultdict 它应该很容易做到:)

import pprint
import collections

data = '''ID1 ID2 ID3
ID1 ID4 ID5
ID3 ID5 ID7 ID6'''

groups = collections.defaultdict(set)

for row in data.split('\n'):
    cols = row.split()
    for groupcol in cols:
        for col in cols:
            if col is not groupcol:
                groups[groupcol].add(col)

pprint.pprint(dict(groups))

结果:

{'ID1': set(['ID2', 'ID3', 'ID4', 'ID5']),
 'ID2': set(['ID1', 'ID3']),
 'ID3': set(['ID1', 'ID2', 'ID5', 'ID6', 'ID7']),
 'ID4': set(['ID1', 'ID5']),
 'ID5': set(['ID1', 'ID3', 'ID4', 'ID6', 'ID7']),
 'ID6': set(['ID3', 'ID5', 'ID7']),
 'ID7': set(['ID3', 'ID5', 'ID6'])}

【讨论】:

  • 在将元素添加到集合之前不需要进行测试。如果您尝试添加集合中已有的元素,集合不会报错。
  • 我没有测试它是否在集合中,我正在测试它是否不是 groupcol,因为它必须从集合中排除。
  • 如果 col 不是 groupcol: groups[groupcol].add(col)
  • 完全正确 :) 重新阅读代码,你会发现我没有做你认为我在做的事情 ;)
【解决方案2】:

TL;DR:选择选项 2。从一开始就使用集合。

在 Python 中,集合是哈希集,列表是动态数组。两者的插入都是O(1),但检查元素是否存在是O(n) 用于列表和O(1) 用于集合。

所以选项 1 立即失效。如果你是插入n的项目,并且每次都需要检查列表,那么整体的复杂度就变成了O(n^2)

选项 2 和 3 在O(n) 的整体上都是最佳的。选项 2 在微型基准测试中可能更快,因为您不需要在集合之间移动对象。在实践中,请选择在您的特定情况下更易于阅读和维护的选项。

【讨论】:

  • 集合和字典是hashtables
  • 好的,这很有意义。然后添加到一个集合需要恒定的时间,所以制作整个集合应该需要线性时间。选项 2 和 3 仍然具有相同的复杂度,O(n)。
  • @cbarrick 您如何看待以下作为基准的答案并发现选项 3 获胜?
  • 飞行和驾驶也是 O(距离),这很难使它们成为到达某个地方的等效方式。在比较具有相同大 O 的算法时,您需要查看在该分析中过滤掉的细节。
  • @Barmar。是的。我是几年前写的,可能太缺乏经验而无法发布答案。这绝对是重写的原因。选项 1 仍然是二次时间,因此应避免使用周期。只有非常小的集合才会更快,到那时就没有关系了。选项 2 和 3 实际上几乎是等价的。任何差异都会因情况而异,因此我们无法做出笼统的回答。
【解决方案3】:

我同意前面的分析,即选项 B 是最好的,但是在这些情况下,微观基准通常会有所启发:

import time

class Timer(object):
  def __init__(self, desc):
    self.desc = desc
  def __enter__(self):
    self.start = time.time()
  def __exit__(self, type, value, traceback):
    self.finish = time.time()
    print self.desc, 'took', self.finish - self.start

data = list(range(4000000))

with Timer('option 2'):
  myset = set()
  for x in data: myset.add(x)

with Timer('option 3'):
  mylist = list()
  for x in data: mylist.append(x)
  myset = set(mylist)

结果令我惊讶:

$ python test.py 
option 2 took 0.652163028717
option 3 took 0.748883008957

我预计至少会有 2 倍的速度差异。

【讨论】:

    【解决方案4】:

    所以,我计时了几个不同的选项,经过几次迭代,想出了以下策略。我认为 sets2 会是赢家,但 listToSet2 对于每种类型的组都更快。

    除了 listFilter 之外的所有函数都在同一个范围内 - listFilter 慢得多。

    import random
    import collections
    
    small = [[random.randint(1,25) for _ in range(5)] for i in range(100)]
    medium = [[random.randint(1,250) for _ in range(5)] for i in range(1000)]
    mediumLotsReps = [[random.randint(1,25) for _ in range(5)] for i in range(1000)]
    bigGroups = [[random.randint(1,250) for _ in range(75)] for i in range(100)]
    huge = [[random.randint(1,2500) for _ in range(5)] for i in range(10000)]
    
    def sets(groups):
        results = collections.defaultdict(set)
        for group in groups:
            for i in group:
                for j in group:
                    if i is not j:
                        results[i].add(j)
        return results
    
    def listToSet(groups):
        results = collections.defaultdict(list)
        for group in groups:
            for i,j in enumerate(group):
                results[j] += group[:i] + group[i:]
        return {k:set(v) for k, v in results.iteritems()}
    
    def listToSet2(groups):
        results = collections.defaultdict(list)
        for group in groups:
            for i,j in enumerate(group):
                results[j] += group
        return {k:set(v)-set([k]) for k, v in results.iteritems()}
    
    def sets2(groups):
        results = collections.defaultdict(set)
        for group in groups:
            for i in group:
                results[i] |= set(group)
        return {k:v - set([k]) for k, v in results.iteritems()}
    
    def listFilter(groups):
        results = collections.defaultdict(list)
        for group in groups:
            for i,j in enumerate(group):
                filteredGroup = group[:i] + group[i:]
                results[j] += ([k for k in filteredGroup if k not in results[j]])
        return results
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-12-16
      • 2011-01-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-10-03
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多