Python 使用上界不在列表中的列表进行切片的最快方法

Python 使用上界不在列表中的列表进行切片的最快方法,python,list,Python,List,假设我们有一个巨大的整数排序列表。使用不在列表中的上限来分割此列表的最快方法是什么 例如,假设我们的列表是: l=list(range(0,1000000, 2)) (这是一个简单的示例,列表可以是任意长度,并且没有特定的间隔,因此它不能与某个范围相关) 我们希望得到一个切片,其中项目小于limit=1001 理想情况下,不检查列表中的所有项目,实现这一目标的最快方法是什么? 一种常见的方法是使用列表理解,例如[i表示l中的i,如果i可以使用: import bisect print(l[:b

假设我们有一个巨大的整数排序列表。使用不在列表中的上限来分割此列表的最快方法是什么

例如,假设我们的列表是:

l=list(range(0,1000000, 2))
(这是一个简单的示例,列表可以是任意长度,并且没有特定的间隔,因此它不能与某个范围相关)

我们希望得到一个切片,其中项目小于
limit=1001

理想情况下,不检查列表中的所有项目,实现这一目标的最快方法是什么? 一种常见的方法是使用列表理解,例如
[i表示l中的i,如果i可以使用:

import bisect
print(l[:bisect.bisect_right(l, 1001)])

O(n)
(特别是
2k
,其中
k
是列表需要切片的索引)中,不依赖二进制搜索的一种快速简便的方法是搜索不满足条件的第一个元素的索引,并使用迭代器切片到该点:

slice_condition = lambda num: num >= limit
slice_idx = next((idx for idx, num in enumerate(l) if slice_condition(num)), len(l))
slice = l[:slice_idx]

当然,二进制搜索会在
O(log(n))
时间中找到
slice\u idx
,但是切片无论如何都是一个线性操作,所以整个单元的复杂性仍然是
O(n)

我只想为这两个答案提供一些比较时间

鉴于这一基准:

import bisect 
import time 

def f1(l, tgt):
    return bisect.bisect_right(l, tgt)

def f2(l,tgt):
    slice_condition = lambda num: num >= tgt
    try:
        slice_idx = next(idx for idx, num in enumerate(l) if slice_condition(num))
    except StopIteration:
        slice_idx = len(l)
    return slice_idx 

def f3(l,tgt):
    return next((idx for idx, val in enumerate(l) if val>=tgt), len(l))


def cmpthese(funcs, args=(), cnt=10, rate=True, micro=True, deepcopy=True):
    from copy import deepcopy 
    """Generate a Perl style function benchmark"""                   
    def pprint_table(table):
        """Perl style table output"""
        def format_field(field, fmt='{:,.0f}'):
            if type(field) is str: return field
            if type(field) is tuple: return field[1].format(field[0])
            return fmt.format(field)     

        def get_max_col_w(table, index):
            return max([len(format_field(row[index])) for row in table])         

        col_paddings=[get_max_col_w(table, i) for i in range(len(table[0]))]
        for i,row in enumerate(table):
            # left col
            row_tab=[row[0].ljust(col_paddings[0])]
            # rest of the cols
            row_tab+=[format_field(row[j]).rjust(col_paddings[j]) for j in range(1,len(row))]
            print(' '.join(row_tab))                

    results={}
    for i in range(cnt):
        for f in funcs:
            if args:
                local_args=deepcopy(args)
                start=time.perf_counter_ns()
                f(*local_args)
                stop=time.perf_counter_ns()
            results.setdefault(f.__name__, []).append(stop-start)
    results={k:float(sum(v))/len(v) for k,v in results.items()}     
    fastest=sorted(results,key=results.get, reverse=True)
    table=[['']]
    if rate: table[0].append('rate/sec')
    if micro: table[0].append('\u03bcsec/pass')
    table[0].extend(fastest)
    for e in fastest:
        tmp=[e]
        if rate:
            tmp.append('{:,}'.format(int(round(float(cnt)*1000000.0/results[e]))))

        if micro:
            tmp.append('{:,.1f}'.format(results[e]/float(cnt)))

        for x in fastest:
            if x==e: tmp.append('--')
            else: tmp.append('{:.1%}'.format((results[x]-results[e])/results[e]))
        table.append(tmp) 

    pprint_table(table)                    

if __name__=='__main__':
    import sys
    print(sys.version)
    
    small=range(1_000)
    mid=range(100_000)
    large=range(1_000_000)
    cases=(
        ('small, found', small, len(small)//2),
        ('small, not found', small, len(small)),
        ('mid, found', mid, len(mid)//2),
        ('mid, not found', mid, len(mid)),
        ('large, found', large, len(large)//2),
        ('large, not found', large, len(large))
    )
    for txt, x, tgt in cases:
        print(f'\n{txt}:')
        l=list(x)
        args=(l,tgt)
            cmpthese([f1,f2,f3],args)

如果你用小的,中等大小的列表,每一个中有1个例子发现中间或2个)一直扫描到结尾,你可以看到<代码>平分是<强>大大快< < /强>。按数量级。

基准在我的计算机上打印:

3.9.1 (default, Jan 30 2021, 15:51:59) 
[Clang 12.0.0 (clang-1200.0.32.29)]

small, found:
   rate/sec μsec/pass      f2      f3     f1
f2      182   5,501.4      --  -59.2% -98.8%
f3      445   2,246.9  144.9%      -- -96.9%
f1   14,562      68.7 7911.4% 3172.0%     --

small, not found:
   rate/sec μsec/pass       f2      f3     f1
f2       90  11,053.2       --  -58.8% -99.5%
f3      220   4,555.5   142.6%      -- -98.7%
f1   17,349      57.6 19076.4% 7803.3%     --

mid, found:
   rate/sec μsec/pass        f2       f3     f1
f2        2 561,882.8        --   -57.2% -99.9%
f3        4 240,253.2    133.9%       -- -99.9%
f1    2,942     339.9 165184.0% 70573.1%     --

mid, not found:
   rate/sec   μsec/pass        f2        f3      f1
f2        1 1,119,041.1        --    -58.0% -100.0%
f3        2   469,960.8    138.1%        --  -99.9%
f1    3,804       262.9 425552.8% 178660.3%      --

large, found:
   rate/sec   μsec/pass        f2       f3     f1
f2        0 5,833,734.0        --   -55.3% -99.9%
f3        0 2,605,010.2    123.9%       -- -99.9%
f1      335     2,988.1 195135.5% 87080.9%     --

large, not found:
   rate/sec    μsec/pass        f2        f3      f1
f2        0 11,553,311.3        --    -54.4% -100.0%
f3        0  5,264,216.7    119.5%        -- -100.0%
f1      710      1,408.9 819923.5% 373540.2%      --

你要找的是二进制搜索。FWIW
l.index
也是
O(n)
,所以
l[:l.index(limit)]
[i for i in l ifi@ScottHunter二进制搜索也不会提高渐进复杂性,因为切片仍然是线性的。但是为什么要将
范围
对象转换为列表?如果您知道确切的边界,只需创建另一个
范围
。列表是否始终排序,元素之间的间隔是否相同,并且没有遗漏元素?如果是这样的话,你可以使用一些基本的数学公式(很像
range
它本身是如何实现的)顺便说一句,我希望你知道你可以直接切分
range
对象!这很快,而且
bisect
没有得到充分利用。这真的很快!做得好,谢谢!如果你使用
下一步,这会更快(thing,默认)
而不是
try
块IMHO@dawg老实说,我忘了下一件事(默认)
已存在。感谢您的提醒,我已根据您的新版本编辑了答案。您的新版本引发了一个错误:SyntaxError:
如果不是唯一的参数,则必须将生成器表达式括起来。
不过,您的原始解决方案速度足够快,谢谢!比较不错。例如,我想知道使用循环的常见解决方案在哪里下面的一个:<代码> M=[L](0)n=1,而M[-1 ]不确定我可以直接比较。Bisect是基于C的,将比纯Python方法快。 >()(代码)>列表也不是最优的。如果<代码> .POP()/代码>对你的应用程序来说是至关重要的,考虑使用一个代替在每一个端的更快的插入/删除…