I just want to put some comparative timing out there for these two answers.
Given this benchmark:
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)
If you run it with small, mid sized and larger lists each with the case of 1) found in the middle or 2) scan all the way to the end, you can see that bisect is substantially faster. By orders of magnitude.
The benchmark prints on my computer:
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% --