0

0

深入理解 SortedList 中自定义对象的高效搜索

花韻仙語

花韻仙語

发布时间:2025-10-25 12:30:01

|

448人浏览过

|

来源于php中文网

原创

深入理解 sortedlist 中自定义对象的高效搜索

本文探讨了如何在 Python 的 `sortedcontainers.SortedList` 中高效搜索自定义类的对象,特别是当 `SortedList` 内部存储的是复杂对象,而搜索条件是其某个特定属性时。通过重载自定义类的富比较方法(如 `__lt__`),我们能够使对象直接与搜索值(例如字符串)进行比较,从而简化 `bisect_left` 的使用,避免了创建临时对象或编写复杂的自定义二分查找逻辑,显著提升了代码的简洁性和可维护性。

SortedList 中自定义对象搜索的挑战

sortedcontainers 库提供的 SortedList 是一个功能强大的有序列表实现,它在保持元素有序的同时,提供了高效的插入、删除和查找操作。当 SortedList 中存储的是基本数据类型(如整数、字符串)时,其内置的 bisect_left 等方法可以直观地用于查找。然而,当列表中存储的是自定义类的对象时,情况会变得复杂。

考虑一个场景,我们有一个 Supplier 类,包含 Name、Id、SapId 等属性,并且我们希望将这些 Supplier 对象存储在一个按 Name 属性排序的 SortedList 中。

from typing import List
from sortedcontainers import SortedList

class Supplier:
    def __init__(self, name: str, id: int = 0, sap_id: int = 0):
        self.Name = name
        self.Id = id
        self.SapId = sap_id

    def __repr__(self):
        return f"Supplier(Name='{self.Name}', Id={self.Id})"

class Data:
    def __init__(self):
        # 初始化时指定key,按Supplier的Name属性(小写)排序
        self.suppliers = SortedList(key=lambda x: x.Name.lower())

# 示例数据
data_store = Data()
data_store.suppliers.add(Supplier("Apple", 101, 2001))
data_store.suppliers.add(Supplier("Banana", 102, 2002))
data_store.suppliers.add(Supplier("Cherry", 103, 2003))
print(data_store.suppliers)
# 输出: SortedList([Supplier(Name='Apple', Id=101), Supplier(Name='Banana', Id=102), Supplier(Name='Cherry', Id=103)])

现在,我们想要根据供应商的名称来查找 Supplier 对象。直观的尝试是直接使用 bisect_left 方法:

# 假设在Data类中有一个查找方法
# def find_supplier(self, name: str):
#     index = self.suppliers.bisect_left(name.lower()) # 尝试直接传入字符串
#     # ... 后续检查

然而,这种做法会遇到类型不匹配的问题。bisect_left 在内部进行比较时,会尝试将传入的搜索值(一个字符串)与 SortedList 中的元素(Supplier 对象)进行比较。由于 SortedList 是通过 key=lambda x: x.Name.lower() 来排序的,bisect_left 期望一个可以与 Supplier 对象的 Name.lower() 属性进行比较的值,但它本身在查找过程中,实际上是将 name.lower() 与 Supplier 对象本身进行比较,或者更准确地说,是与 Supplier 对象通过 key 函数转换后的结果进行比较。当直接传入一个字符串时,它会尝试将这个字符串与 key 函数的返回值(也是一个字符串)进行比较。

更深层次的问题在于,虽然 SortedList 使用 key 函数来决定元素的排序位置,但 bisect_left 在执行二分查找时,其比较逻辑是基于列表元素自身的。如果列表中存储的是 Supplier 对象,那么 bisect_left 在内部比较时,会尝试比较 Supplier 对象与你传入的搜索值。当传入一个字符串时,Python 默认的比较行为会认为一个 Supplier 对象和一个字符串是不同类型的,通常会导致 TypeError 或不符合预期的比较结果。

一种常见的“变通”方法是创建一个临时的 Supplier 对象,将其 Name 属性设置为搜索名称,然后用这个临时对象进行查找:

码上飞
码上飞

码上飞(CodeFlying) 是一款AI自动化开发平台,通过自然语言描述即可自动生成完整应用程序。

下载
# 在Data类中
def find_supplier_with_temp_object(self, name: str):
    temporary_supplier = Supplier(name) # 创建一个临时对象
    index = self.suppliers.bisect_left(temporary_supplier)

    if index != len(self.suppliers) and self.suppliers[index].Name.lower() == name.lower():
        return self.suppliers[index]

    return None

# print(data_store.find_supplier_with_temp_object("Apple"))

这种方法虽然能够工作,但它引入了不必要的临时对象创建,增加了代码的复杂性和潜在的性能开销,尤其是在高频查找的场景下,显得不够优雅。

优雅的解决方案:重载富比较方法

为了避免创建临时对象并实现更简洁的搜索逻辑,我们可以通过在自定义类 Supplier 中重载富比较方法(rich comparison methods)来解决这个问题。核心思想是让 Supplier 对象能够直接与字符串进行比较。

Python 中的富比较方法包括:

  • __lt__(self, other): 小于 (
  • __le__(self, other): 小于等于 (
  • __eq__(self, other): 等于 (==)
  • __ne__(self, other): 不等于 (!=)
  • __gt__(self, other): 大于 (>)
  • __ge__(self, other): 大于等于 (>=)

通过实现 __lt__ 方法,我们可以定义 Supplier 对象如何与另一个对象(包括字符串)进行“小于”比较。

from typing import List
from sortedcontainers import SortedList

class Supplier:
    def __init__(self, name: str, id: int = 0, sap_id: int = 0):
        self.Name = name
        self.Id = id
        self.SapId = sap_id

    def __repr__(self):
        return f"Supplier(Name='{self.Name}', Id={self.Id})"

    # 重载小于操作符
    def __lt__(self, other):
        if isinstance(other, str):
            # 如果另一个操作数是字符串,则与自己的Name属性进行比较
            return self.Name.lower() < other.lower()
        elif isinstance(other, Supplier):
            # 如果另一个操作数是Supplier对象,则与另一个Supplier的Name属性进行比较
            return self.Name.lower() < other.Name.lower()
        # 处理其他类型或抛出错误,这里简化为默认False
        return NotImplemented # 或者 raise TypeError(f"Cannot compare Supplier with {type(other)}")

    # 重载等于操作符 (推荐实现,确保精确匹配)
    def __eq__(self, other):
        if isinstance(other, str):
            return self.Name.lower() == other.lower()
        elif isinstance(other, Supplier):
            return self.Name.lower() == other.Name.lower()
        return NotImplemented

    # 如果实现了__eq__,通常也建议实现__hash__,除非明确不希望对象可哈希
    # def __hash__(self):
    #     return hash(self.Name.lower())

class Data:
    def __init__(self):
        # 此时SortedList不再需要key函数,因为它存储的对象本身就可比较了
        self.suppliers = SortedList()

    def add_supplier(self, supplier: Supplier):
        self.suppliers.add(supplier)

    def find_supplier(self, name: str):
        # 直接传入字符串进行二分查找
        index = self.suppliers.bisect_left(name)

        # 检查找到的索引是否有效,并且对应元素的名称是否与搜索名称匹配
        if index != len(self.suppliers) and self.suppliers[index].Name.lower() == name.lower():
            return self.suppliers[index]

        return None

# 示例用法
data_store = Data()
data_store.add_supplier(Supplier("Banana", 102, 2002))
data_store.add_supplier(Supplier("Apple", 101, 2001))
data_store.add_supplier(Supplier("Cherry", 103, 2003))

print("排序后的供应商列表:", data_store.suppliers)
# 预期输出: SortedList([Supplier(Name='Apple', Id=101), Supplier(Name='Banana', Id=102), Supplier(Name='Cherry', Id=103)])

found_supplier = data_store.find_supplier("Apple")
print("查找 'Apple':", found_supplier)
# 预期输出: 查找 'Apple': Supplier(Name='Apple', Id=101)

not_found_supplier = data_store.find_supplier("Grape")
print("查找 'Grape':", not_found_supplier)
# 预期输出: 查找 'Grape': None

found_supplier_case_insensitive = data_store.find_supplier("apple")
print("查找 'apple' (不区分大小写):", found_supplier_case_insensitive)
# 预期输出: 查找 'apple' (不区分大小写): Supplier(Name='Apple', Id=101)

在这个优化后的方案中:

  1. Supplier 类重载 __lt__ 方法
    • 当 other 是 str 类型时,它会将 self.Name.lower() 与 other.lower() 进行比较。
    • 当 other 是 Supplier 类型时,它会将 self.Name.lower() 与 other.Name.lower() 进行比较。
    • 通过这种方式,Supplier 对象自身变得可比较,并且能够理解与字符串的比较。
  2. SortedList 初始化简化:Data 类的 __init__ 方法中,self.suppliers 不再需要 key=lambda x: x.Name.lower() 参数,因为 Supplier 对象自身已经定义了排序逻辑。SortedList 会直接使用 Supplier 对象之间定义的 __lt__ 进行排序。
  3. find_supplier 方法简洁:bisect_left 可以直接传入搜索字符串 name,而不再需要创建临时 Supplier 对象。SortedList 内部会利用 Supplier 对象中重载的 __lt__ 方法来正确执行二分查找。
  4. __eq__ 方法的实现:为了确保在查找后进行精确匹配(self.suppliers[index].Name.lower() == name.lower())时,比较行为符合预期,我们同样重载了 __eq__ 方法。这对于 SortedList 的其他操作(如 remove)也可能很重要。

注意事项与总结

  • 一致性:重载比较方法时,确保它们之间的一致性至关重要。例如,如果 a
  • 处理不同类型:在 __lt__ 和 __eq__ 方法中,通过 isinstance 检查 other 的类型是一种健壮的做法,可以避免不必要的 TypeError。对于无法处理的类型,返回 NotImplemented 是 Python 推荐的做法,它允许解释器尝试反向操作或调用其他比较方法。
  • 排序键的单一性:这种方法将排序逻辑(基于 Name 属性)硬编码到 Supplier 类中。如果你的 SortedList 需要根据 Supplier 对象的不同属性进行排序(例如,有时按 Name,有时按 Id),那么这种方法可能不适用。在这种情况下,使用 SortedList(key=...) 并在查找时创建临时对象(或手动实现二分查找)可能是更灵活的选择。
  • 性能:重载比较方法通常比创建临时对象更高效,因为它避免了每次查找时的对象实例化开销。
  • 可读性与维护性:此方案使代码更具可读性,将对象的比较逻辑封装在对象自身内部,符合面向对象的原则,降低了 Data 类中查找方法的复杂性。

通过重载自定义类的富比较方法,我们为 SortedList 中的复杂对象提供了更优雅、高效的搜索机制,使代码更加清晰和易于维护。

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
数据类型有哪几种
数据类型有哪几种

数据类型有整型、浮点型、字符型、字符串型、布尔型、数组、结构体和枚举等。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

309

2023.10.31

php数据类型
php数据类型

本专题整合了php数据类型相关内容,阅读专题下面的文章了解更多详细内容。

222

2025.10.31

go语言 面向对象
go语言 面向对象

本专题整合了go语言面向对象相关内容,阅读专题下面的文章了解更多详细内容。

56

2025.09.05

java面向对象
java面向对象

本专题整合了java面向对象相关内容,阅读专题下面的文章了解更多详细内容。

52

2025.11.27

js 字符串转数组
js 字符串转数组

js字符串转数组的方法:1、使用“split()”方法;2、使用“Array.from()”方法;3、使用for循环遍历;4、使用“Array.split()”方法。本专题为大家提供js字符串转数组的相关的文章、下载、课程内容,供大家免费下载体验。

298

2023.08.03

js截取字符串的方法
js截取字符串的方法

js截取字符串的方法有substring()方法、substr()方法、slice()方法、split()方法和slice()方法。本专题为大家提供字符串相关的文章、下载、课程内容,供大家免费下载体验。

212

2023.09.04

java基础知识汇总
java基础知识汇总

java基础知识有Java的历史和特点、Java的开发环境、Java的基本数据类型、变量和常量、运算符和表达式、控制语句、数组和字符串等等知识点。想要知道更多关于java基础知识的朋友,请阅读本专题下面的的有关文章,欢迎大家来php中文网学习。

1501

2023.10.24

字符串介绍
字符串介绍

字符串是一种数据类型,它可以是任何文本,包括字母、数字、符号等。字符串可以由不同的字符组成,例如空格、标点符号、数字等。在编程中,字符串通常用引号括起来,如单引号、双引号或反引号。想了解更多字符串的相关内容,可以阅读本专题下面的文章。

624

2023.11.24

俄罗斯Yandex引擎入口
俄罗斯Yandex引擎入口

2026年俄罗斯Yandex搜索引擎最新入口汇总,涵盖免登录、多语言支持、无广告视频播放及本地化服务等核心功能。阅读专题下面的文章了解更多详细内容。

158

2026.01.28

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
最新Python教程 从入门到精通
最新Python教程 从入门到精通

共4课时 | 22.3万人学习

Django 教程
Django 教程

共28课时 | 3.6万人学习

SciPy 教程
SciPy 教程

共10课时 | 1.3万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号