
本文探讨了如何在 Python 的 `sortedcontainers.SortedList` 中高效搜索自定义类的对象,特别是当 `SortedList` 内部存储的是复杂对象,而搜索条件是其某个特定属性时。通过重载自定义类的富比较方法(如 `__lt__`),我们能够使对象直接与搜索值(例如字符串)进行比较,从而简化 `bisect_left` 的使用,避免了创建临时对象或编写复杂的自定义二分查找逻辑,显著提升了代码的简洁性和可维护性。
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 属性设置为搜索名称,然后用这个临时对象进行查找:
# 在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__ 方法,我们可以定义 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)在这个优化后的方案中:
通过重载自定义类的富比较方法,我们为 SortedList 中的复杂对象提供了更优雅、高效的搜索机制,使代码更加清晰和易于维护。
以上就是深入理解 SortedList 中自定义对象的高效搜索的详细内容,更多请关注php中文网其它相关文章!
                        
                        每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
                Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号