最好将项目添加到集合中,还是将最终列表转换为集合?

时间:2013-09-15 00:06:04

标签: python loops set

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

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

其中每一行都是一个组。

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

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

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

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

4 个答案:

答案 0 :(得分:5)

选项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'])}

答案 1 :(得分:4)

TL; DR:选项2和3同样复杂,选项1更差。

理论上,选项2和3应该同样困难。我不确定在Python中是如何实现集合和列表的,但是可以安全地假设添加到列表需要恒定的时间并且添加到集合需要对数时间。

选项1: 如果搜索树支持传统列表的线性时间,则搜索列表最好采用对数时间。因此,如果你想添加n个东西(恒定时间来添加* n个东西=线性时间)并在列表中搜索每个n次(线性搜索时间* n搜索=二次时间),那么你最终得到O(n ^ 2)复杂性。我们可以做得更好。

选项2和3: 创建列表需要线性时间(每次添加的常量* n次),创建一个集需要线性时间(log(n)加上* n次= O(n * log(n)))。要将列表转换为集合,您只需遍历列表并将每个元素添加到集合中(除非有更好的方法,我不知道)。所以一切都应该在set add步骤遇到瓶颈,给两者带来O(n * log(n))复杂度。但是通过从集合开始,您可以通过不创建列表来节省时间和空间的一些开销。对于足够大的数据集,开销可能没什么区别,但在大多数正常情况下,您将通过不首先列出列表来节省大量工作。

胜利的选项2。

答案 2 :(得分:0)

我同意先前的分析,即选项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倍的速度差异。

答案 3 :(得分:0)

所以,我定时了几个不同的选项,经过几次迭代后,提出了以下策略。我认为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