通过python中元组列表中元组的第一个元素索引元素的最快方法

时间:2014-02-19 02:17:43

标签: python list tuples

list_ = [(1, 'a'), (2, 'b'), (3, 'c')]
item1 = 1
item2 = 'c'
#hypothetical:
assert list_.index_by_first_value(item1) == 0
assert list_.index_by_second_value(item2) == 2

在python中模拟index_by_first/second_value方法的最快方法是什么?

如果你不明白发生了什么;如果你有一个元组列表(如list_中所包含的那样),你将如何找到一个元组的索引,其中元组的第一个/第二个值是你想要索引的元素?


我最好的猜测是:

[i[0] for i in list_].index(item1)
[i[1] for i in list_].index(item2)

但我很想知道你们会想出什么。有什么想法吗?

5 个答案:

答案 0 :(得分:2)

起初,我想到了the same lines as Nick T。如果元组(N)的数量很短,那么你的方法很好。但是线性搜索当然是O(N)。随着元组数量的增加,时间直接随之增加。您可以使用dict将每个元组的第0个元素映射到其索引,从而获得O(1)查找时间:

{el[0]:idx for idx,el in enumerate(list_)}

但是将列表转换为dict的成本可能太高了!以下是我的结果:

>>> from timeit import timeit as t
>>> t('[i[0] for i in list_].index(1)', "import random;list_=[(i,'a') for i in range(10)]; random.shuffle(list_)")
1.557116985321045
>>> t('[i[0] for i in list_].index(1)', "import random;list_=[(i,'a') for i in range(100)]; random.shuffle(list_)")
7.415766954421997
>>> t('{el[0]:idx for idx,el in enumerate(list_)}[1]', "import random;list_=[(i,'a') for i in range(10)]; random.shuffle(list_)")
2.1753010749816895
>>> t('{el[0]:idx for idx,el in enumerate(list_)}[1]', "import random;list_=[(i,'a') for i in range(100)]; random.shuffle(list_)")
15.062835216522217

因此,list-to-dict转换正在扼杀我们从O(1)查找中获得的任何好处。但是,如果我们可以避免不止一次地进行转换,那么只是为了证明dict真的很快:

>>> t('dict_[1]', "import random;list_=[(i,'a') for i in range(10)];random.shuffle(list_);dict_={el[0]:idx for idx,el in enumerate(list_)}")
0.050583839416503906
>>> t('dict_[1]', "import random;list_=[(i,'a') for i in range(100)];random.shuffle(list_);dict_={el[0]:idx for idx,el in enumerate(list_)}")
0.05001211166381836
>>> t('dict_[1]', "import random;list_=[(i,'a') for i in range(1000)];random.shuffle(list_);dict_={el[0]:idx for idx,el in enumerate(list_)}")
0.050894975662231445

答案 1 :(得分:1)

搜索列表是O(n)。将其转换为字典,然后查找取O(1)。

>>> list_ = [(1, 'a'), (2, 'b'), (3, 'c')]
>>> dict(list_)
{1: 'a', 2: 'b', 3: 'c'}
>>> dict((k, v) for v, k in list_)
{'a': 1, 'c': 3, 'b': 2}

如果你想要原始索引,你可以枚举它:

>>> dict((kv[0], (i, kv[1])) for i, kv in enumerate(list_))
{1: (0, 'a'), 2: (1, 'b'), 3: (2, 'c')}

>> dict((kv[1], (i, kv[0])) for i, kv in enumerate(list_))
{'a': (0, 1), 'c': (2, 3), 'b': (1, 2)}

答案 2 :(得分:1)

编辑:开玩笑吧。随着列表变长,看起来手动for循环花费的时间更少。更新以通过kojiro的方法生成随机列表:

在维护列表时,只需对您的信息进行一些时序测试。保留列表形式与字典的好处在于包含任意长度的元组都是可扩展的。

import timeit
from operator import itemgetter
import random

list_= [('a', i) for i in range(10)]
random.shuffle(list_)

def a():
    return [i[1] for i in list_].index(1)

def b():
    return zip(*list_)[1].index(1)

def c():
    return map(itemgetter(1), list_).index(1)

def d():
     for index, value in enumerate(list_):
         if 1 == value[1]:
             return index

使用timeit

C:\Users\Jesse\Desktop>python -m timeit -s "import test" "test.a()"
1000000 loops, best of 3: 1.21 usec per loop

C:\Users\Jesse\Desktop>python -m timeit -s "import test" "test.b()"
1000000 loops, best of 3: 1.2 usec per loop

C:\Users\Jesse\Desktop>python -m timeit -s "import test" "test.c()"
1000000 loops, best of 3: 1.45 usec per loop

C:\Users\Jesse\Desktop>python -m timeit -s "import test" "test.d()"
1000000 loops, best of 3: 0.922 usec per loop

答案 3 :(得分:1)

什么是最快的? 这取决于您需要使用它的次数,以及您是否能够从一开始就创建索引字典。

正如其他人所提到的,一旦你拥有它,字典就会快得多,但将列表转换成字典会很昂贵。我将展示我在计算机上得到的内容,以便我可以比较数字。这就是我得到的:

>>> import timeit
>>> timeit.timeit('mydict = {val[0]:(ind, val[1]) for ind, val in enumerate(mylist)}', 'mylist = [(i, "a") for i in range(1000)]')
200.36049539601527

令人惊讶的是,这比起创建列表的速度要慢得多:

>>> timeit.timeit('mylist = [(i, "a") for i in range(1000)]')
70.15259253453814

那么这与首先创建字典相比如何?

>>> timeit.timeit('mydict = {i:("a", i) for i in range(1000)}')
90.78464277950229

显然,这并不总是可行的,因为你并不总是创建列表的人,但我想把它包括在内进行比较。

初始化摘要:

  • 创建列表 - 70.15
  • 创建字典 - 90.78
  • 索引现有列表 - 70.15 + 200.36 = 270.51

现在,假设您已经设置了列表或词典,需要多长时间?

>>> timeit.timeit('[i[0] for i in mylist].index(random.randint(0,999))', 'import random; mylist = [(i, "a") for i in range(1000)]')
68.15473008213394

但是,每次都会创建一个新的临时列表,所以让我们看一下细分

>>> timeit.timeit('indexed = [i[0] for i in mylist]', 'import random; mylist = [(i, "a") for i in range(1000)];')
55.86422327528999
>>> timeit.timeit('indexed.index(random.randint(0,999))', 'import random; mylist = [(i, "a") for i in range(1000)]; indexed = [i[0] for i in mylist]')
12.302146224677017

55.86 + 12.30 = 68.16,这与之前的结果给出的68.15一致。现在是字典:

>>> timeit.timeit('mydict[random.randint(0,999)]', 'import random; mylist = [(i, "a") for i in range(1000)]; mydict = {val[0]:(ind, val[1]) for ind, val in enumerate(mylist)}')
1.5201382921450204

当然,在每一种情况下,我都在使用random.randint,所以让我们抽出时间来考虑它:

>>> timeit.timeit('random.randint(0,999)', 'import random')
1.4206546251180043

现在是使用索引的摘要:

  • 使用清单 - (68.16-1.42)= 66.74第一次,(12.30-1.42)= 10.88之后
  • 每次使用字典 - (1.52-1.42)= 0.10

现在让我们弄清楚字典变得更有用所需的访问次数。首先,作为访问次数函数的时间公式:

  • 列表 - 55.86 + 10.88x
  • 字典 - 200.36 + 0.10x
  • 初始字典 - 20.63 + 0.10x

根据这些公式,如果您需要至少14次访问它,字典会变得更快。如果你可以从get-go而不是列表创建一个字典,那么创建一个字典而不是一个列表的额外开销远远超过了创建元组中第一个值列表的开销。

哪个最快? 这取决于您需要使用它的次数,以及您是否能够从一开始就创建索引字典。 < / p>

注意:我使用的是Python 2.7.5。 Python 3.x中的计时可能非常不同,并且在不同的计算机上可能会有所不同。我很想知道别人会在他们的机器上想出什么。

所有时间都在几秒钟内,但时间为一百万次。因此,单个运行大约相同的数字,以微秒为单位。

答案 4 :(得分:0)

@Nick T

我认为有些时间浪费了枚举列表然后将其转换为字典,所以即使它是一个字典的O(1)查找,首先创建字典太昂贵了,不能认为它是可行的大型名单的选项。

这是我用来确定它的测试:

import time
l = [(i, chr(i)) for i in range(1000000)]
def test1():
    t1 = time.time()
    ([i[0] for i in l].index(10872))
    t2 = time.time()
    return t2 - t1

def test2():
    t1 = time.time()
    (dict((kv[0], (i, kv[1])) for i, kv in enumerate(l))[10872][0])
    t2 = time.time()
    return t2 - t1

def test3():
    sum1 = []
    sum2 = []
    for i in range(1000):
        sum1.append(test1())
        sum2.append(test2())
    print(sum(sum1)/1000)
    print(sum(sum2)/1000)

test3()
编辑:哈哈小次郎,你打败了我!