2025年你能用 Python 的 bisect 模块做到这些事

你能用 Python 的 bisect 模块做到这些事你能用 Python 的 bisect 模块做到这些事 翻译自 martinheinz dev blog 106 虽然 Python 的 bisect 模块非常简单 仅包含 2 个函数 但我们可以用它做很多事情 包括高效搜索数据 保持数据排序等等 在本文中我们将探讨所有的 bisect left

大家好,我是讯享网,很高兴认识大家。

[你能用 Python 的 bisect 模块做到这些事]

翻译自:(martinheinz.dev/blog/106)

虽然 Python 的 bisect 模块非常简单 - 仅包含 2 个函数 - 但我们可以用它做很多事情,包括高效搜索数据、保持数据排序等等 - 在本文中我们将探讨所有的!

bisect_left

bisect.bisect_left(a, x, lo=0, hi=len(a), *, key=None)

a 中找到 x 合适的插入点以维持有序。参数 lohi 可以被用于确定需要考虑的子集;默认情况下整个列表都会被使用。如果 x 已经在 a 里存在,那么插入点会在已存在元素之前(也就是左边)。如果 a 是列表(list)的话,返回值是可以被放在 list.insert() 的第一个参数的。

返回的插入点 ip 将数组 a 分为两个切片使得对于左侧切片 all(elem < x for elem in a[lo : ip]) 为真值而对于右侧切片 all(elem >= x for elem in a[ip : hi]) 为真值。

key 指定带有单个参数的 key function 用来从数组的每个元素中提取比较键。 为了支持搜索复杂记录,键函数不会被应用到 x 值。

如果 keyNone,则将直接比较元素而不调用任何键函数。

在 3.10 版更改: 增加了 key 形参。

insort_left

bisect.insort_left(a, x, lo=0, hi=len(a), *, key=None)

按照已排序顺序将 x 插入到 a 中。

此函数首先会运行 bisect_left() 来定位一个插入点。 然后,它会在 a 上运行 insert() 方法在适当的位置插入 x 以保持排序顺序。

为了支持将记录插入到表中,key 函数(如果存在)将被应用到 x 用于搜索步骤但不会用于插入步骤。

请记住 O(log n) 搜索是由缓慢的 O(n) 抛入步骤主导的。

在 3.10 版更改: 增加了 key 形参。

什么是二等分(离子)?

在我们开始使用该模块之前,让我们首先解释一下 bisect(ion) 实际上是什么。官方定义:

Bisection is the division of a given curve, figure, or interval into two equal parts (halves). 二分是将给定的曲线、图形或区间分成两个相等的部分(两半)。

用简单的英语来说,这意味着它实现了二分搜索。在实践中,这意味着我们可以使用它 - 例如 - 将元素插入到列表中,同时维护列表的排序顺序:

import bisect some_list = [0, 6, 1, 5, 8, 2] some_list.sort() print(some_list) # [0, 1, 2, 5, 6, 8] i = bisect.bisect_left(some_list, 4) print(i) # 3 some_list.insert(i, 4) print(some_list) # [0, 1, 2, 4, 5, 6, 8] # 或者,我们可以直接使用 `insort_left` 函数 bisect.insort_left(some_list, 4) print(some_list) # [0, 1, 2, 4, 5, 6, 8] 

讯享网

在这个基本示例中,我们首先对列表进行排序,因为我们只能在排序的可迭代对象上使用 bisect 中的函数。然后,我们在列表上使用 bisect_left 来查找应插入第二个参数 ( 4 ) 以维持排序顺序的索引。然后我们继续这样做 - 在索引 3 处插入数字 4 。或者,我们可以直接使用 insort_left 函数,该函数首先在内部使用 bisect_left ,然后也执行 insert

嗯,这很酷,但是您为什么要关心这个模块呢?好吧,让我向您展示您可以用它做的所有有用的事情…

二分查找

正如已经提到的, bisect 实现了二分搜索,因此它最明显的用途就是:

讯享网from bisect import bisect_left def binary_search(a, x, lo=0, hi=len(a)): pos = bisect_left(a, x, lo, hi) # find insertion position return pos if pos != hi and a[pos] == x else -1 # don't walk off the end print(binary_search([0, 1, 2, 5, 6, 8], 5)) # 3 print(binary_search([0, 1, 2, 5, 6, 8], 4)) # -1 

上述 binary_search 函数的参数与 bisect 模块中的函数遵循相同的模式。也就是说,我们在索引 lohi 之间的列表 a 中查找值 x

唯一有趣的行是 return 语句,我们在其中测试值 x 是否确实在列表中,如果是,我们返回其位置,否则返回 -1 .

连续的相等值

然而,我们可以使用 bisect 模块做更有趣的事情,例如在列表中查找连续的相等值:

from bisect import bisect_left, bisect_right some_list = [5, 10, 15, 15, 15, 20, 25, 40] # Find all the 15's value = 15 start = bisect_left(some_list, value) end = bisect_right(some_list, value) print(f'Successive values of {value} from index {start} to {end}: {some_list[start:end]}') # Successive values of 15 from index 2 to 5: [15, 15, 15] 

我们已经见过 bisect_left ,所以这里我们还介绍 bisect_right ,它做同样的事情,但从另一端。这样我们就可以找到我们正在寻找的连续值的开始和结束位置。

从区间到值的映射

现在,假设我们有一系列间隔/范围,并且我们想要返回相应的 ID/值。一个天真的、丑陋的解决方案可能看起来像这样:

讯享网def interval_to_value(val): if val <= 100: return 0 elif 100 < val <= 300: return 1 elif 300 < val <= 500: return 2 elif 500 < val <= 800: return 3 elif 800 < val <= 1000: return 4 elif val > 1000: return 5 

但是,使用 bisect_left 有一个更好的解决方案:

def interval_to_value(val): return bisect_left([100, 300, 500, 800, 1000], val) 

这不仅是非常干净的解决方案,而且速度非常快。如果您需要非自然排序,或者例如您想返回不同的内容(例如字符串),也可以对其进行扩展:

讯享网i = interval_to_value(325) a = ['absent', 'low', 'average', 'high', 'very high', 'extreme'] print(a[i]) # average 

字典中最近的键

现在,假设我们有一个字典形式的映射,并且我们想要查找指定键的值。如果该键存在那就很酷,但如果不存在,那么我们想返回最接近的键的值:

import collections some_dict = collections.OrderedDict( [(0, 0), (2, 1), (4, 4), (6, 9), (8, 16), (10, 25), (12, 36), (14, 49), (16, 64), (18, 81)] ) target = 10.5 index = bisect_left(list(some_dict.keys()), target) # 6 items = list(some_dict.items()) distance1 = items[index][0] - target distance2 = target - items[index-1][0] # Check which one is closer: print(f'Distance for to index {index}: {distance1}') # Distance for to index 6: 1.5 print(f'Distance for to index {index-1}: {distance2}') # Distance for to index 5: 0.5 print('Closest value:') if distance1 < distance2: print(items[index]) else: print(items[index-1]) # Closest value: (10, 25) 

这里我们使用 OrderedDict 来确保密钥的顺序正确。然后我们对它们使用 bisect_left 来查找插入点。最后,我们需要检查下一个或上一个索引是否更接近 target

前缀搜索

您可以使用 bisect 进行的另一件事是前缀搜索 - 假设我们有一个非常大的单词列表,并且想要根据给定的前缀查找单词:

讯享网def prefix_search(wordlist, prefix): try: index = bisect_left(wordlist, prefix) return wordlist[index].startswith(prefix) except IndexError: return False words = ['another', 'data', 'date', 'hello', 'text', 'word'] print(prefix_search(words, 'dat')) # True print(prefix_search(words, 'xy')) # False 

上面的函数仅检查列表中是否存在具有指定前缀的单词,但是可以轻松修改为从 index 循环并返回以 prefix 开头的所有单词。

如果您有足够大的单词列表,那么与仅从头开始迭代列表相比,使用 bisect 会更快。

排序的自定义对象

到目前为止,我们仅使用了内置类型,但 bisect 模块中的函数也可以应用于自定义类型。假设我们有一个自定义对象列表,并且我们希望根据某些属性维护它们在列表中的顺序:

from bisect import insort_left class CustomObject: def __init__(self, val): self.prop = val # The value to compare def __lt__(self, other): return self.prop < other.prop def __repr__(self): return 'CustomObject({})'.format(self.prop) some_objects = sorted([CustomObject(7), CustomObject(1), CustomObject(3), CustomObject(9)]) insort_left(some_objects, CustomObject(2)) print(some_objects) # [CustomObject(1), CustomObject(2), CustomObject(3), CustomObject(7), CustomObject(9)] 

此代码片段利用了 bisect 使用 __lt__ 魔术方法来比较对象的事实。但是,必须始终使用 bisect 函数可能会有点不方便,为了避免您可以实现这个 更完整的示例/配方中描述的 SortedCollection


讯享网

key 函数

bisect 模块中的函数还支持使用 key 函数参数进行更复杂的比较/搜索:

讯享网some_list = [1, 3, 7, 16, 25] some_list.reverse() insort_left(some_list, 10, key=lambda x: -1 * x) print(some_list) # [25, 16, 10, 7, 3, 1] 

这里我们使用 key 函数来实现逆序二分搜索,只要记住列表也必须以逆序开始排序。

与逆序排序类似,人们可能也倾向于使用 key 函数来搜索元组列表,但是可以这样做:

list_of_tuples = [(1, 3), (3, 8), (5, 4), (10, 12)] index = bisect_left(list_of_tuples, (5, )) # 2 print(list_of_tuples[2]) # (5, 4) 

省略 tuple 中的第二个值会强制 bisect_left 仅根据第一个值进行比较。如果您想明确地使用 key 函数,那么 key=lambda i: i[0] 也可以。

话虽如此,key 函数能有点不直观 - 人们会期望它像例如 sorted 功能:

讯享网some_tuples = [ (0, 10), (2, 12), (3, 15), (5, 20), ] print(sorted(some_tuples, key=lambda t: t[0] + t[1])) # Works 

但它没有:

index = bisect_left(some_tuples, (4, 17), key=lambda t: t[0] + t[1]) # Doesn't work # Expectation: index = 3 # Reality: "TypeError: '<' not supported between instances of 'int' and 'tuple'" def key_func(t): return t[0] + t[1] index = bisect_left(some_tuples, key_func((4, 17)), key=key_func) print(index) # 3 

相反,我们必须定义一个关键函数,将其作为 key 参数传递,并在第二个参数上调用它。

那是因为(来自文档):

key 指定一个参数的键函数,用于从数组中的每个元素中提取比较键。为了支持搜索复杂记录,key 函数不应用于 x 值。*

另请参阅 cpython 存储库中的此 GitHub 问题 ,了解此设计决策背后的推理。

结论

在我看来,对于这么小的模块,你可以用它做很多事情。

另外,除了您可以使用 bisect 执行的所有上述有用操作之外,我想强调一下它的速度有多快 - 特别是如果您的列表已经排序。例如,考虑 Stack Overflow 上的这个示例 ,显示 bisect_leftin 运算符快得多。

它是二分搜索自然意味着它在 O(log(n)) 中运行,但它通过在 C 中预编译而得到进一步帮助,因此它总是比您自己编写的任何内容都要快。

---------------------------END---------------------------

题外话

在这里插入图片描述

感兴趣的小伙伴,赠送全套Python学习资料,包含面试题、简历资料等具体看下方。

👉CSDN大礼包🎁:全网最全《Python学习资料》免费赠送🆓!(安全链接,放心点击)

一、Python所有方向的学习路线

Python所有方向的技术点做的整理,形成各个领域的知识点汇总,它的用处就在于,你可以按照下面的知识点去找对应的学习资源,保证自己学得较为全面。

img
img

二、Python必备开发工具

工具都帮大家整理好了,安装就可直接上手!img

三、最新Python学习笔记

当我学到一定基础,有自己的理解能力的时候,会去阅读一些前辈整理的书籍或者手写的笔记资料,这些笔记详细记载了他们对一些技术点的理解,这些理解是比较独到,可以学到不一样的思路。

img

四、Python视频合集

观看全面零基础学习视频,看视频学习是最快捷也是最有效果的方式,跟着视频中老师的思路,从基础到深入,还是很容易入门的。

img

五、实战案例

纸上得来终觉浅,要学会跟着视频一起敲,要动手实操,才能将自己的所学运用到实际当中去,这时候可以搞点实战案例来学习。

img

六、面试宝典

在这里插入图片描述

👉CSDN大礼包🎁:全网最全《Python学习资料》免费赠送🆓!(安全链接,放心点击)

若有侵权,请联系删除

小讯
上一篇 2025-04-10 22:59
下一篇 2025-01-28 22:01

相关推荐

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/12405.html