【问题标题】:Find numbers within a range bisect python查找范围二等分python内的数字
【发布时间】:2012-08-20 02:32:47
【问题描述】:

我有一个整数列表,我想编写一个函数来返回一个范围内的数字子集。类似 NumbersWithinRange(list, interval) 函数名...

即,

list = [4,2,1,7,9,4,3,6,8,97,7,65,3,2,2,78,23,1,3,4,5,67,8,100]
interval = [4,20]
results = NumbersWithinRange(list, interval)  # [4,4,6,8,7,8]

也许我忘了在结果中再写一个数字,但这就是我的想法......

列表的长度可以达到 10/20 百万,范围通常为几百个。

关于如何使用 python 有效地做到这一点的任何建议 - 我正在考虑使用 bisect。

谢谢。

【问题讨论】:

  • 您不应使用list 作为变量名。如果你这样做,Python 允许你(默默地)重新分配内置列表构造函数......
  • 正确。这只是为了示例,我不会在代码中使用该名称。谢谢指正。

标签: python search sorting binary bisect


【解决方案1】:

如果您的数据集不是太稀疏,您可以使用"bins" 来存储和检索数据。例如:

a = [4,2,1,7,9,4,3,6,8,97,7,65,3,2,2,78,23,1,3,4,5,67,8,100]

# Initalize a list of 0's [0, 0, ...]
# This is assuming that the minimum possible value is 0
bins = [0 for _ in range(max(a) + 1)]  

# Update the bins with the frequency of each number
for i in a:
    bins[i] += 1


def NumbersWithinRange(data, interval):
    result = []
    for i in range(interval[0], interval[1] + 1):
        freq = data[i]
        if freq > 0:
            result += [i] * freq
    return result

这适用于这个测试用例:

print(NumbersWithinRange(bins, [4, 20]))
# [4, 4, 4, 5, 6, 7, 7, 8, 8, 9]

为简单起见,我省略了函数中的一些边界检查。

重申一下,这在空间和时间使用方面可能会更好,但这在很大程度上取决于您的特定数据集。数据集越稀疏,效果就越好。

【讨论】:

    【解决方案2】:

    纯 Python Python sortedcontainers module 有一个 SortedList 类型可以帮助你。它按排序顺序自动维护列表,并已通过数千万个元素的测试。排序列表类型有一个可以使用的 bisect 函数。

    from sortedcontainers import SortedList
    data = SortedList(...)
    
    def NumbersWithinRange(items, lower, upper):
        start = items.bisect(lower)
        end = items.bisect_right(upper)
        return items[start:end]
    
    subset = NumbersWithinRange(data, 4, 20)
    

    这种方式的二分和索引将比扫描整个列表快得多。排序容器模块非常快,并且有一个performance comparison 页面,其中包含针对替代实现的基准。

    【讨论】:

      【解决方案3】:

      我想你正在寻找这样的东西..

      b=[i for i in a if 4<=i<90]
      print sorted(set(b))
      [4, 5, 6, 7, 8, 9, 23, 65, 67, 78]
      

      【讨论】:

        【解决方案4】:

        我会为此使用 numpy,特别是如果列表那么长。例如:

        In [101]: list = np.array([4,2,1,7,9,4,3,6,8,97,7,65,3,2,2,78,23,1,3,4,5,67,8,100])
        In [102]: list
        Out[102]: 
        array([  4,   2,   1,   7,   9,   4,   3,   6,   8,  97,   7,  65,   3,
                 2,   2,  78,  23,   1,   3,   4,   5,  67,   8, 100])
        In [103]: good = np.where((list > 4) & (list < 20)) 
        In [104]: list[good]
        Out[104]: array([7, 9, 6, 8, 7, 5, 8])
        
        # %timeit says that numpy is MUCH faster than any list comprehension: 
        # create an array 10**6 random ints b/w 0 and 100
        In [129]: arr = np.random.randint(0,100,1000000)
        In [130]: interval = xrange(4,21)
        In [126]: %timeit r = [x for x in arr if x in interval]
        1 loops, best of 3: 14.2 s per loop
        
        In [136]: %timeit good = np.where((list > 4) & (list < 20)) ; new_list = list[good]
        100 loops, best of 3: 10.8 ms per loop
        
        In [134]: %timeit r = [x for x in arr if 4 < x < 20]
        1 loops, best of 3: 2.22 s per loop 
        
        In [142]: %timeit filtered = [i for i in ifilter(lambda x: 4 < x < 20, arr)]
        1 loops, best of 3: 2.56 s per loop
        

        【讨论】:

        • 当您使用4 &lt;= x &lt;= 20 而不是x in interval 时,列表解析的效果如何?检查值是否在 xrange 迭代器中会减慢速度。
        • 是的,它快了 6 倍,但仍然比 numpy 慢 4 个数量级
        • 啊,是的,我现在明白了。我没有密切关注单位(s vs ms)。 numpy 确实快得多。
        • 您的时间不包括arr[good]。在 numpy 数组中逐个枚举项目很慢,Python 列表应该更快。 OP还说生成的数组应该有~100个项目;对 randint 使用更大的限制。
        • 不,由于在 numpy 数组中的强类型化,这在 numpy 中总是比使用 Python 列表更快。添加了 arr[good],没有性能影响
        【解决方案5】:

        让我们创建一个类似于您描述的列表:

        import random  
        l = [random.randint(-100000,100000) for i in xrange(1000000)]
        

        现在测试一些可能的解决方案:

        interval=range(400,800)
        
        def v2():
            """ return a list """
            return [i for i in l if i in interval]
        
        def v3():
            """ return a generator """
            return list((i for i in l if i in interval))
        
        def v4():
            def te(x):
                return x in interval
        
            return filter(te,l)
        
        def v5():
            return [i for i in ifilter(lambda x: x in interval, l)]    
        
        
        print len(v2()),len(v3()), len(v4()), len(v5())
        cmpthese.cmpthese([v2,v3,v4,v5],micro=True, c=2)
        

        打印这个:

           rate/sec   usec/pass   v5    v4    v2    v3
        v5        0 6929225.922   -- -0.4% -1.0% -1.6%
        v4        0 6903028.488 0.4%    -- -0.6% -1.2%
        v2        0 6861472.487 1.0%  0.6%    -- -0.6%
        v3        0 6817855.477 1.6%  1.2%  0.6%    --
        

        但是,如果 interval 是一个集合而不是一个列表,请注意会发生什么:

        interval=set(range(400,800))
        cmpthese.cmpthese([v2,v3,v4,v5],micro=True, c=2)
        
          rate/sec  usec/pass     v5     v4     v3     v2
        v5        5 201332.569     -- -20.6% -62.9% -64.6%
        v4        6 159871.578  25.9%     -- -53.2% -55.4%
        v3       13  74769.974 169.3% 113.8%     --  -4.7%
        v2       14  71270.943 182.5% 124.3%   4.9%     --
        

        现在与 numpy 比较:

        na=np.array(l)
        
        def v7():
            """ assume you have to convert from list => numpy array and return a list """
            arr=np.array(l)
            tgt = np.where((arr >= 400) & (arr < 800)) 
            return [arr[x] for x in tgt][0].tolist()
        
        
        def v8():
            """ start with a numpy list but return a python list """
            tgt = np.where((na >= 400) & (na < 800)) 
            return na[tgt].tolist()
        
        
        def v9():
            """ numpy all the way through """
            tgt = np.where((na >= 400) & (na < 800)) 
            return [na[x] for x in tgt][0]  
            # or return na[tgt] if you prefer that syntax...    
        
        cmpthese.cmpthese([v2,v3,v4,v5, v7, v8,v9],micro=True, c=2)  
        
           rate/sec  usec/pass      v5      v4      v7     v3     v2     v8     v9
        v5        5 185431.957      --  -17.4%  -24.7% -63.3% -63.4% -93.6% -93.6%
        v4        7 153095.007   21.1%      --   -8.8% -55.6% -55.7% -92.3% -92.3%
        v7        7 139570.475   32.9%    9.7%      -- -51.3% -51.4% -91.5% -91.5%
        v3       15  67983.985  172.8%  125.2%  105.3%     --  -0.2% -82.6% -82.6%
        v2       15  67861.438  173.3%  125.6%  105.7%   0.2%     -- -82.5% -82.5%
        v8       84  11850.476 1464.8% 1191.9% 1077.8% 473.7% 472.6%     --  -0.0%
        v9       84  11847.973 1465.1% 1192.2% 1078.0% 473.8% 472.8%   0.0%     --   
        

        只要你能一直使用 numpy,显然 numpy 比纯 python 更快。否则,为间隔使用一个集合以加快一点...

        【讨论】:

        • 这不是一个公平的比较。 v3 实际上并没有做任何工作。您需要将v2 与实际构建列表的v3 版本进行比较。
        • @DSM:将 v3 修复为公平比较
        【解决方案6】:

        我认为这应该足够有效:

        >>> nums = [4,2,1,7,9,4,3,6,8,97,7,65,3,2,2,78,23,1,3,4,5,67,8,100]
        >>> r = [x for x in nums if 4 <= x <21]
        >>> r
        [4, 7, 9, 4, 6, 8, 7, 4, 5, 8]
        

        编辑:

        经过 J.F. Sebastian 的出色观察,修改了代码。

        【讨论】:

        • 这会起作用,但如果列表真的是 10^6 顺序,那就慢一点
        • 列表是否排序未明确;如果是,那么 bisect 应该更快。
        • i in xrange 未优化(与 Python 3 上的 i in range 不同)。它与i in iterable 相同,即,它一一枚举值。所以应该改用4 &lt;= x &lt; 21
        【解决方案7】:

        使用迭代器

        >>> from itertools import ifilter
        >>> A = [4,2,1,7,9,4,3,6,8,97,7,65,3,2,2,78,23,1,3,4,5,67,8,100]
        >>> [i for i in ifilter(lambda x: 4 < x < 20, A)]
        [7, 9, 6, 8, 7, 5, 8]
        

        【讨论】:

          【解决方案8】:

          如果列表没有排序,则需要扫描整个列表:

          lst = [ 4,2,1,...]
          interval=[4,20]
          results = [ x for x in lst if interval[0] <= x <= interval[1] ]
          

          如果列表 已排序,您可以使用bisect 查找 限制你的范围。

          left = bisect.bisect_left(lst, interval[0])
          right = bisect.bisect_right(lst, interval[1])
          
          results = lst[left+1:right]
          

          由于扫描列表是 O(n) 并且排序是 O(n lg n),因此可能不值得对列表进行排序列出只是为了使用bisect,除非您打算进行大量范围提取。

          【讨论】:

          • 是的,我正计划进行 100 或更多的数百万次提取,这听起来很现实还是需要很长时间?
          • 很难说,但使用 numpy 答案,因为它比纯 python 解决方案快得多。
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2017-04-03
          • 2014-04-27
          • 1970-01-01
          相关资源
          最近更新 更多