【问题标题】:Huge difference in timing between sorting a set vs sorting a list in Python在 Python 中对集合进行排序与​​对列表进行排序之间在时间上的巨大差异
【发布时间】:2015-02-21 02:56:43
【问题描述】:

我想知道我应该将我的数据结构作为一个集合还是一个列表。大多数情况下我会做集合操作,但最后我需要对它进行排序。

我想知道是否应该先将集合设为列表,然后使用sorted(list(my_set)),还是直接对集合进行排序sorted(my_set)。可以说,我可能会考虑一个通用的“列出”阶段,因为在那个时间点有一个有序的迭代可能是有意义的。

所以我决定测试一下,希望列表更快。

基准测试:

import time
def sorter(x):
    t1 = time.time()
    for i in range(1000000):
        sorted(x)
    return time.time() - t1

数据:

one = range(1000)
a1 = list(one)
b1 = set(one)
sorter(a1)
# time: 16.5 s 
sorter(b1)
# time: 20.7 s

然后我意识到这可能与元素已经就位这一事实有关,并记住了this amazing question & answer

然后,我尝试了一些随机数据:

two = numpy.random.randint(1, 1000, 1000)
a2 = list(two)
b2 = set(two)

有了结果:

sorter(a2)
# time: 4min 49s
sorter(b2)
# time: 18.9 s

差别很大,怎么回事?

奖励:它甚至在一分钟的时间出现,sorted(set(a_list))sorted(a_list) 快得多。

确实,在第二种情况下,可能会有重复的被过滤掉,从而加快排序。

【问题讨论】:

  • 一个集合将主要按哈希键排序,在整数的情况下,哈希键就是值本身。 Python 中的 Timsort 算法擅长识别已经排序的序列。
  • b2 可能会明显短于 a2。这并不能解释整个效果,但重要的是要注意,当您为这两个操作计时时,您没有使用可比较的输入大小
  • @PascalvKooten 我不是基准测试专家,但我希望如果您想更公平地对集合和列表进行时间排序,您可以随机排列一个范围(1000),然后取结果作为集合或列表。这至少会让你从同一个 N 开始。
  • @PascalvKooten 你想要一个随机的 unique 元素样本。 numpy.random.randint 不保证这一点。
  • 根据我的测试,使用非平凡的数据类型(例如 (int, int))会逆转此处看到的趋势,尽管使用中间集只会增加轻微的低效率(~10%)。我怀疑为什么对整数使用中间集更快的原因是因为集合构造过程会自动将每个整数以正确的顺序(或非常接近)而不对它们进行排序,因为 Python 使用的微不足道的哈希.

标签: list python-2.7 sorting set


【解决方案1】:

我稍微扩展了您的代码,希望这能让您深入了解正在发生的事情:

import numpy
import uuid
import random
import time

def sorter(x):
    t1 = time.time()
    for i in range(10000):
        sorted(x)
    return time.time() - t1

def pr(name, x):
    print('sorter {:<12s} {:<11} (length {:>4})'.format(
        name, '{:.8}'.format(sorter(x)), len(x)))

a2sizes = []
b2sizes = []

for x in range(1000):
    two = numpy.random.randint(1, 1000, 1000)
    a2 = list(two)
    b2 = set(two)
    a2sizes.append(len(a2))
    b2sizes.append(len(b2))

print 'average number of elements in a2', sum(a2sizes)/len(a2sizes)
n = sum(b2sizes)/len(b2sizes)
print 'average number of elements in b2', n

打印出来:

average number of elements in a2 1000
average number of elements in b2 632

这是因为随机数范围内的冲突

print
pr('a2', a2)
# making a list of set gives you already sorted elements
y = list(b2)
pr('y', y)
random.shuffle(y)
pr('shuffled y ', y)
pr('b2', b2)

作为输出:

sorter a2           2.492537    (length 1000)
sorter b2           0.25028086  (length  633)
sorter y            0.19689608  (length  633)
sorter shuffled y   1.4935901   (length  633)

b2 会更快,因为元素更少是合乎逻辑的,但是如果你首先列出集合,这会更快,这一定是有原因的。如果您重新洗牌该列表再次合乎逻辑,并且在补偿列表的长度时,洗牌的结果与 a2 的结果相当接近,那么它会再次变慢。

所以让我们尝试在列表中添加其他内容:

b3 = set()
for x in range(1000):
    b3.add(uuid.uuid4())

print '\nuuid elements', len(b3)

a3 = list(b3)
pr('a3', a3)
random.shuffle(a3)
pr('shuffled a3', a3)
pr('b3', b3)

给出(如果元素少于 1000 个,我会相当惊讶):

uuid elements 1000
sorter a3           32.437758   (length 1000)
sorter shuffled a3  32.178433   (length 1000)
sorter b3           32.163802   (length 1000)

所以它一定与集合中有数字有关:

previous = -1
ordered = True
for popped in b2:
    if popped < previous:
        print 'popped', popped, previous
        ordered = False
    previous = popped

print '\nOrdered', ordered

给你:

Ordered True

set 有一个 pop() 函数,而不是迭代,您可以尝试使用:

pop()

从集合中删除并返回任意元素。如果集合为空,则引发 KeyError。

所以让我们任意从集合b2 中检索元素,看看是否有什么特别之处:

previous = -1
ordered = True
while(b2):
    popped = b2.pop()
    if popped < previous:
        print 'popped', popped, previous
        ordered = False
    previous = popped

print '\nOrdered', ordered

给出相同的结果:

Ordered True

因此,任意检索一组数字的元素会按顺序检索这些数字,独立于这些数字的放入顺序。 由于迭代是列表制作一次检索一个元素以追加到列表的方式,list(b2) 的结果是一个有序列表,使用 Python 中使用的Timsort 算法可以快速排序。

【讨论】:

    猜你喜欢
    • 2020-09-16
    • 1970-01-01
    • 2017-08-14
    • 2012-02-08
    • 1970-01-01
    • 2021-12-20
    • 2013-10-12
    • 1970-01-01
    相关资源
    最近更新 更多