【问题标题】:What is the most performant way to check existance of a number in a set of numbers in python在python中检查一组数字中是否存在数字的最有效方法是什么
【发布时间】:2012-01-12 09:27:10
【问题描述】:

所以我在 python 中的列表中有一个数字列表,如下所示: [1,89,1221,1919,1920,10210,...] 有数千个数字,我想检查变量 i 是否在其中。

我是这样做的:

if i in mylist:

但这是最快的方法吗?

一些进一步的规格:

  • 列表中没有重复项
  • 列表只包含整数
  • 如果可以提高性能,可以对列表进行排序
  • 列表实际上可以是一个集合(所以没有重复)
  • 列表可以是任何类型的集合

【问题讨论】:

  • 数字集/列表的大小是否固定?
  • 不,它可能会有所不同,尽管对固定号码列表的见解也是受欢迎的。
  • 转换为一个集合是一个相对昂贵的操作,但i in mylist 也很昂贵。如果您在同一个列表中多次这样做,最好使用一组
  • 你试过x in some_setx in some_list 使用timeit 了吗?用timeit测量后,你学到了什么?可以发一下结果吗?

标签: python performance list set


【解决方案1】:

只有当您在此列表上进行多次查找时,转换为 set 才值得。如果性能很重要,您应该衡量从一开始就使用set(同时仍然插入元素)是否比说列表提供更好的性能。简而言之,尝试一些事情并衡量。

但是,如果仅仅是因为创建新数据结构的开销,那么仅为单个成员资格测试转换为集合听起来效率很低。

import random
import timeit

mylist = list(random.randint(1, 50000) for i in xrange(1000))
myset = set(mylist)

s = "1919 in mylist"
t = timeit.Timer(s, "from __main__ import mylist")
print s + ":%.2f usec/pass" % (1000000 * t.timeit(number = 100000)/100000)

s = "1919 in set(mylist)"
t = timeit.Timer(s, "from __main__ import mylist")
print s + ":%.2f usec/pass" % (1000000 * t.timeit(number = 100000)/100000)

结果如下:

1919 in mylist:22.81 usec/pass
1919 in set(mylist):65.42 usec/pass

【讨论】:

    【解决方案2】:

    一种方法是使用timeit module 来测试各种方法。例如,我编写了以下代码:

    import array
    import bisect
    import random
    import timeit
    
    mylist = list(random.randint(1, 50000) for i in xrange(1000))
    myset = set(mylist)
    myarray = array.array('l', mylist)
    
    s = "1919 in mylist"
    t = timeit.Timer(s, "from __main__ import mylist")
    print s + ":%.2f usec/pass" % (1000000 * t.timeit(number = 100000)/100000)
    
    s = "1919 in myset"
    t = timeit.Timer(s, "from __main__ import myset")
    print s + ":%.2f usec/pass" % (1000000 * t.timeit(number = 100000)/100000)
    
    s = "1919 in myarray"
    t = timeit.Timer(s, "from __main__ import myarray")
    print s + ":%.2f usec/pass" % (1000000 * t.timeit(number = 100000)/100000)
    
    mysortedlist = sorted(mylist)
    mysortedarray = array.array('l', mysortedlist)
    
    s = "1919 in mysortedlist"
    t = timeit.Timer(s, "from __main__ import mysortedlist")
    print s + ":%.2f usec/pass" % (1000000 * t.timeit(number = 100000)/100000)
    
    s = "1919 in mysortedarray"
    t = timeit.Timer(s, "from __main__ import mysortedarray")
    print s + ":%.2f usec/pass" % (1000000 * t.timeit(number = 100000)/100000)
    
    def bisect_in(a, x):
        i = bisect.bisect_left(a, x)
        return (i != len(a) and a[i] == x)
    
    s = "bisect_in(mysortedlist, 1919)"
    t = timeit.Timer(s, "from __main__ import bisect_in, mysortedlist")
    print s + ":%.2f usec/pass" % (1000000 * t.timeit(number = 100000)/100000)
    

    我得到了以下结果:

    1919 in mylist:73.89 usec/pass
    1919 in myset:0.29 usec/pass
    1919 in myarray:103.77 usec/pass
    1919 in mysortedlist:75.12 usec/pass
    1919 in mysortedarray:114.21 usec/pass
    bisect_in(mysortedlist, 1919):4.17 usec/pass
    

    它支持其他人的论点,即使用集合是最快的(在此测试代码做出的假设下)。

    【讨论】:

      【解决方案3】:

      转换为一组并执行in 是最快的方法。

      if i in set(mylist):
      

      一个集合基本上是一个哈希表,查找是 O(1)。

      【讨论】:

      • 谢谢,如果没有人认为这是禁食方式,我稍后接受
      • 没有。如果他仅在if 语句中将列表转换为集合,它实际上会比原始代码慢,因为这必须遍历列表并创建新的数据结构。
      • 请注意,只有在重复执行该操作时,该方法才会更快。否则,将列表转换为集合的开销将大于在 O(1) 而不是 O(N) 中进行搜索所获得的性能。
      • 谢谢 Björn,但实际上,它有很多重复,我可以将我的列表更改为一组开始
      • @Peter:请编辑你的问题,说你想反复检查一个值是否存在于一个大集合中,这可能是一开始就准备好的。
      【解决方案4】:

      从列表中创建一个集合,与集合相交并检查相交的大小?

      【讨论】:

      • +1:我喜欢你的想法,“?”虽然不会使其成为接受答案的候选者。 (另外:我无法想象这是最快的方式)
      • 我认为我误读了您最初的问题-您想检查一个数字是否存在,而不是一组数字,在这种情况下您最好使用(i in mylist),它在 O(n ) 如果您正在创建集合,请直接创建一个集合,在这种情况下 in 将是 O(1)
      【解决方案5】:

      如果您愿意牺牲一些内存效率,您可以构建一个查找表,其中列表索引是您希望检查的值。

      原名单:

         In [106]: %timeit i in myList
         10000 loops, best of 3: 21.3 us per loop
      

      构建查找表:

         In [90]: lookup = [False for i in range( max(myList)+1 )]
      
         In [91]: for i in myList:
                      lookup[i] = True
      
         In [92]: %timeit lookup[i]
         10000000 loops, best of 3: 50.7 ns per loop  
      

      查找表在这里比未排序列表快约 400 倍。

      只有在列表的最大值可以接受的低并且设置查找表的时间明显少于检查变量是否在表中所花费的总时间时,此选项才真正可行。

      有趣的是,查找表方法在使用 Numpy 数组时会慢 25%。 (但是构建查找表要快得多)

      编辑:此方法的速度也比“i in set(myList)”高出 2 倍。

      【讨论】:

      • 您确定仅对列表进行排序会有所改善吗?我尝试在未排序列表和已排序列表中搜索不存在的元素,每个元素花费的时间大致相同。在排序列表中搜索现存值所花费的时间或多或少取决于该值在原始列表中的位置。
      • 你是对的。这是一个错误,排序会像加快进程一样频繁地减慢进程。更有理由使用集合或查找表。我已更改帖子以免误导。
      猜你喜欢
      • 2011-10-28
      • 1970-01-01
      • 2011-10-11
      • 1970-01-01
      • 2020-03-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-03-18
      相关资源
      最近更新 更多