将列表转换为字典时速度问题

时间:2014-02-26 21:21:10

标签: python performance

关于将列表转换为词典,我遇到了一些速度问题,其中以下操作占总运行时间的90%左右:

def list2dict(list_):
    return_dict = {}

    for idx, word in enumerate(list_):
        if word in return_dict:
            raise ValueError("duplicate string found in list: %s" % (word))
        return_dict[word] = idx

    return return_dict

我很难看到导致这种情况的原因。您是否在代码中看到了明显的瓶颈,或者有关如何加快速度的建议?

感谢。

5 个答案:

答案 0 :(得分:1)

编辑:

想象一下,我把它放在首位,因为它更大 - 事实证明,对OP的代码进行微小的调整会给性能带来很大的影响。

def list2dict(list_):    # OLD
    return_dict = {}
    for idx, word in enumerate(list_):
        if word in return_dict: # this compare is happening every iteration!
            raise ValueError("duplicate string found in list: %s" % (word))
        return_dict[word] = idx
    return return_dict

def list2dictNEW(list_): #NEW HOTNESS
    return_dict = {}
    for idx, word in enumerate(list_):
        return_dict[word] = idx # overwrite if you want to, because...
    if len(return_dict) == len(list_): return return_dict
    # if the lengths aren't the same, something got overwritten so we
    # won't return. If they ARE the same, toss it back with only one
    # compare (rather than n compares in the original
    else: raise ValueError("There were duplicates in list {}".format(list_))

DEMO:
>>> timeit(lambda: list2dictNEW(TEST))
1.9117132451798682
>>> timeit(lambda: list2dict(TEST)):
2.2543816669587216
# gains of a third of a second per million iterations!
# that's a 15.2% speed bost

没有明显的答案,但你可以尝试类似的东西:

def list2dict(list_):
    return_dict = dict()
    for idx,word in enumerate(list_):
        return_dict.setdefault(word,idx)
    return return_dict

您也可以构建一个集合并执行list.index,因为您说列表相当小,但我认为这样会更慢而不是更快。这需要进行分析才能确定(使用timeit.timeit

def list2dict(list_):
    set_ = set(list_)
    return {word:list_.index(word) for word in set_}

我冒昧地在一组测试数据上运行一些配置文件。结果如下:

TEST = ['a','b','c','d','e','f','g','h','i','j'] # 10 items

def list2dictA(list_): # build set and index word
    set_ = set(list_)
    return {word:list_.index(word) for word in set_}

def list2dictB(list_): # setdefault over enumerate(list)
    return_dict = dict()
    for idx,word in enumerate(list_):
        return_dict.setdefault(word,idx)
    return return_dict

def list2dictC(list_): # dict comp over enumerate(list)
    return_dict = {word:idx for idx,word in enumerate(list_)}
    if len(return_dict) == len(list_):
        return return_dict
    else:
        raise ValueError("Duplicate string found in list")

def list2dictD(list_): # Original example from Question
    return_dict = {}
    for idx, word in enumerate(list_):
        if word in return_dict:
            raise ValueError("duplicate string found in list: %s" % (word))
        return_dict[word] = idx
    return return_dict

>>> timeit(lambda: list2dictA(TEST))
5.336584700190931
>>> timeit(lambda: list2dictB(TEST))
2.7587691306531
>>> timeit(lambda: list2dictC(TEST))
2.1609074989233292
>>> timeit(lambda: list2dictD(TEST))
2.2543816669587216

答案 1 :(得分:1)

最快的功能取决于list_的长度。对于小型列表(少于约80项),adsmithlist2dictC()非常快。但是当列表大小增加时,我发现list2dictE()的速度提高了约8%。

def list2dictC(list_):  # dict comp over enumerate(list)
    return_dict = {word: idx for idx, word in enumerate(list_)}
    if len(return_dict) == len(list_):
        return return_dict
    else:
        raise ValueError("Duplicate string found in list")

def list2dictE(list_):  # Faster for lists with ~80 items or more
    l = len(list_)
    return_dict = dict(zip(list_, range(l)))
    if len(return_dict) == l:
        return return_dict
    else:
        raise ValueError("Duplicate string found in list")

如果已知长度很小,则保持它没有用,但如果不是这样,可能会添加l = len(list_); if l < 80: ... else: ...之类的东西。它只是一个额外的if语句,因为两个函数都需要知道列表长度。 80个项目的门槛可能很大程度上取决于你的设置,但对于我来说,这对于我来说都是python 2.7和3.3。

答案 2 :(得分:0)

def l2d(list_):
    dupes = set(filter(lambda x: a.count(x) > 1, a))
    if len(dupes) > 0:
        raise ValueError('duplicates: %s' % (dupes))
    return dict((word, idx) for (idx, word) in enumerate(list_))

这是如何比较的?

答案 3 :(得分:0)

使用熊猫似乎加速了三分之一,尽管据说它已经“足够快”了

> TEST = # english scrabble dictionary, 83882 entries
> def mylist2dict(list_):
      return_dict = pd.Series(index=list_, data=np.arange(len(list_)))
      if not return_dict.index.is_unique:
          raise ValueError
      return return_dict
> %timeit list2dict(TEST)
10 loops, best of 3: 28.8 ms per loop
> %timeit mylist2dict(TEST)
100 loops, best of 3: 18.8 ms per loop

答案 4 :(得分:0)

每次运行的平均值为2.36μs。因为OPs代码看起来像是支持第一个值的事件,所以我颠倒了范围,因此它不需要任何逻辑来检查值是否存在。

def mkdct(dct):
    l = len(dct-1)
    return {dct[x]:(l-x) for x in range(len(dct), -1, -1)}

编辑:那里有一些愚蠢的错误。