原文towardsdatascience.com/stop-manually-sorting-your-list-in-python-if-performance-is-concerned-fad2832e4fe5https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/0077e7ca55eaaea2660aa3f5182189d0.png由作者在 Canva 中创建的图片至少对我来说大多数时候我使用 Python 编码时都在处理集合类型。这包括原生的 Python 列表、集合和字典以及一些第三方集合类型如 Numpy 数组和 Tensorflow 张量。后者通常要快得多因为它们是用 CPython 或其他与 C 相关的扩展实现的。然而有时使用它们会感到过于复杂。特别是当我们开发一个与数据科学无关的应用程序时这些库可能过于庞大只是为了获得更好的性能。在这篇文章中我将介绍一个名为 Sorted Containers 的库它在需要排序的集合类型时提供了最佳实践。我将介绍这个库的两个主要类型Sorted List 和 Sorted Set。我将给出一些实际例子来帮助理解。在所有事情之前请确保您已经使用pip安装了库。pip install sortedcontainers1. Sorted List – 实时排行榜https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/e15ddfb45f221a8df2fd5029c005865b.png由作者在 Canva 中创建的图片1.1 纯 Python 代码实现正如提到的我将使用一些实际用例来展示这个库而不是重复文档。假设我们正在开发一个维护用户列表及其分数的过程。无论分数是什么它可能是游戏或其他东西。让我们不使用排序容器来实现这个功能看看它是什么样子。在所有事情之前我们需要创建一个包含名称和分数的元组列表。leaderboard[(Alice,50),(Bob,40),(Chris,60)]现在让我们编写一个函数来添加新的名称和分数。defadd_score_plain(leaderboard,player,score):leaderboard.append((player,score))leaderboard.sort(keylambdax:x[1],reverseTrue)第一行只是将元组追加到列表中。然而我们需要确保列表根据分数排序。分数在元组中所以最容易排序列表的方法是使用 lambda 函数。在 lambda 函数中我们根据第二个元素对列表进行排序并且应该是降序的所以reverseTrue。现在我们需要以美观的格式输出排行榜。下面的函数将完成这个任务。defprint_leaderboard_plain(leaderboard):print(Leaderboard:)forrank,(player,score)inenumerate(leaderboard,start1):print(f{rank}.{player}:{score}points)这里的一个小技巧是使用enumerate来获取排名编号。然而排行榜应该从 1 开始而不是 0所以我们可以设置start1。然后让我们添加一个新的名称和分数并按如下方式测试输出。# Test codeadd_score_plain(leaderboard,David,55)print_leaderboard_plain(leaderboard)https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/d0594b13476c62571418a7227c1fea14.png作者截图1.2 使用 Sorted List现在让我们看看如何使用 Sorted Container 实现相同的功能。具体来说我们需要使用这个库中的SortedList模块。在我们开始之前我们需要了解SortedList模块的特性。让我们将SortedList的实例称为“排序列表”以便更容易理解。排序列表将自动对其所有项目进行排序包括刚刚添加的项目。然而顺序是升序的。排序标准是直观的。可以是数字或字符串文本的 ASCII 码。当项目是元组时只考虑第一个元素。基于上述特性我们需要以下方式设计我们的排行榜。fromsortedcontainersimportSortedList leaderboardSortedList([(-50,Alice),(-40,Bob),(-60,Chris)])上述代码将导入SortedList并用它来初始化排序列表。要使用排序列表的特性我们需要将分数放在左边。因此它将作为排序列表的依据。此外我们故意在分数上加上负号这样它们将以“降序”排序。当然为了确保未来可以正确添加项目add_score_sorted()方法需要如下所示。defadd_score_sorted(leaderboard,player,score):leaderboard.add((-score,player))请注意我们不需要像在纯 Python 版本中那样添加另一个步骤来对列表进行排序因为排序列表将保证项目处于排序状态。然后print_leaderboard_sorted()方法也需要考虑负号所以输出将会是合理且易读的。defprint_leaderboard_sorted(leaderboard):print(Leaderboard:)forrank,(score,player)inenumerate(leaderboard,start1):print(f{rank}.{player}:{-score}points)测试代码将与纯 Python 版本完全相同。以下是输出也是相同的。没有问题。https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/2868950d2a6029108b6ea5ec3fa3fb72.png作者截图1.3 评估收益你可能会问这样做具体有什么好处确实排序列表版本的代码比纯版本少一行但这并不是唯一的优点。此外排序列表版本中的权衡比如负分数降低了可读性。实际上另一个主要的好处是性能。让我展示一下。让我们在 Python 中原生导入time模块。然后我们可以为性能测试添加一些参数。importtimefromsortedcontainersimportSortedList# Performance test parametersrandom_scores[(fPlayer{i},i%10000)foriinrange(10000)]在上述代码中我们生成了 10,000 个随机分数稍后将被插入到排行榜列表中。现在让我们将纯版本和排序列表版本放在一起并捕捉添加新项目时的耗时。# Plain Python list testplain_leaderboard[(Alice,50),(Bob,40),(Charlie,60)]start_timetime.time()forplayer,scoreinrandom_scores:add_score_plain(plain_leaderboard,player,score)plain_timetime.time()-start_time# SortedList testsorted_leaderboardSortedList([(-50,Alice),(-40,Bob),(-60,Charlie)])start_timetime.time()forplayer,scoreinrandom_scores:add_score_sorted(sorted_leaderboard,player,score)sorted_timetime.time()-start_time上述代码分别初始化了普通列表和排序列表然后使用 for 循环将项目添加到列表中。现在让我们按照以下方式输出耗时。print(fPlain list time:{plain_time:.4f}seconds)print(fSortedList time:{sorted_time:.4f}seconds)https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/18b8789870d56980e019fddd8b66b3a8.png作者截图它显示排序列表的性能比普通代码好 100 倍。实际上我们只测试了十万随机分数。如果需要排序的元素更多性能差异将更大。2. 排序列表 – 事件日志管理系统https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/0b528b2367cbbe4214406b404eb28c5f.png作者在 Canva 中创建的图像现在让我们来看另一个例子并利用这个例子来了解 Sorted Container 库的另一个重要特性。那就是 Sorted Set。假设我们正在开发一个事件日志管理系统。我们有许多时间戳需要管理。排序集合类似于 Python Set。例如它可以包含唯一的值。然而主要区别在于它是排序的。2.1 它就像 Python Set但又不完全一样让我们用一些时间戳初始化一个排序集合。fromsortedcontainersimportSortedSet event_timestampsSortedSet([2024-08-19 10:00:00,2024-08-19 11:00:00,2024-08-19 09:30:00])print(event_timestamps)https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/ddc1c4f54ff8f858bc228bab7e89aee5.png作者截图请注意输出显示时间戳是自动排序的尽管我们没有以排序的方式初始化它。现在让我们尝试添加两个新的时间戳。event_timestamps.add(2024-08-19 09:30:00)# Duplicate, will be ignoredevent_timestamps.add(2024-08-19 10:30:00)# New event, will be addedprint(event_timestamps)https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/cc547fd258a2f35e12af2f57bed2a5be.png作者截图可以看到重复的时间戳被忽略了而新的时间戳按预期添加。当然新的时间戳也是按正确顺序排列的。2.2 Sorted Set 的使用我们已经初始化了一个排序集合并展示了其特性。现在让我们看看一些其他方便的特性我们可以使用。假设我们想要给定时间戳之后的下一个事件时间戳。而不用将时间戳与每个单独的元素进行比较我们可以使用排序列表中的bisect_right()方法。以下是获取下一个事件的该方法实现。defget_next_event(event_timestamps,current_time):indexevent_timestamps.bisect_right(current_time)ifindexlen(event_timestamps):returnevent_timestamps[index]else:returnNoneprint(Next event after 10:25 is,get_next_event(event_timestamps,2024-08-19 10:25:00))在上面的代码中我们尝试获取我们正在寻找的目标事件时间戳的索引。如果索引有效我们返回时间戳。否则返回 none。请注意时间戳2024–08–19 10:25:00在排序集合中不存在。排序集合可以处理这种情况并给出预期的结果而不会出现任何问题。https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/6dac62309fd1fcab3b47310aaf2071d2.png作者截图同样还有一个bisect_left()方法来获取给定值之前的项目。除了这些排序集合还允许我们获取两个边界之间的项目范围。这是通过irange()方法实现的。eventsevent_timestamps.irange(2024-08-19 09:55:00,2024-08-19 10:30:00)print(Events between 09:30 and 11:00 are,list(events))在上面的代码中我们以开始时间和结束时间作为参数。irange()方法将返回我们需要的事件的 Python 迭代器。这是一个非常巧妙的设计因为迭代器是一个“懒惰”的数据结构在我们尝试从其中填充项目之前它不会消耗很多资源。在这个演示中我们可以将其转换为列表这样我们就可以轻松地验证结果。https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/3cceb28d57932579c6f80875839abddf.png作者截图摘要https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/9016c1a1a90a2f5032e3c5491242f2b2.png作者在 Canva 中创建的图像在本文中我介绍了一个名为 Sorted Containers 的库。它是 Python 中最受欢迎的模块之一能够有效地处理数据结构。在某些场景下它提供的性能比 Python 原生解决方案要好得多尤其是在我们使用 Python 列表或其他内置集合类型时。此外它还解锁了一些管理并交互集合类型对象的新方法。我希望这能帮助你在软件开发/应用开发过程中。