List 在Python中对集合排序与对列表排序在时间上的巨大差异

List 在Python中对集合排序与对列表排序在时间上的巨大差异,list,python-2.7,sorting,set,List,Python 2.7,Sorting,Set,我想知道我的数据结构应该是集合还是列表。大多数情况下,我会进行set操作,但最后我需要对其进行排序 我想知道是应该先将集合设置为列表,然后使用排序(list(my_set)),还是直接对集合进行排序排序(my_set)。可以说,我可以考虑一个一般的“列表”阶段,因为在那个时间点有一个有序的迭代可能是有意义的。 所以我决定测试它,希望列表更快 基准测试者: import time def sorter(x): t1 = time.time() for i in range(1000

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

我想知道是应该先将集合设置为列表,然后使用
排序(list(my_set))
,还是直接对集合进行排序
排序(my_set)
。可以说,我可以考虑一个一般的“列表”阶段,因为在那个时间点有一个有序的迭代可能是有意义的。 所以我决定测试它,希望列表更快

基准测试者:

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

然后我意识到这可能与元素已经就位并被记住这一事实有关

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

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)
要快得多


事实上,在第二种情况下,可能会有重复项被过滤,从而加快排序速度。

我对您的代码进行了一些扩展,希望这能让您深入了解正在发生的事情:

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
这是因为在随机数范围内发生了碰撞

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,我会感到相当惊讶):

因此,它一定与集合中的数字有关:

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

print '\nOrdered', ordered
a没有迭代,而是有一个
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
因此,任意检索数字集合的元素会按顺序检索这些数字,独立于这些数字的排列方式。
由于迭代是列表生成一次检索一个元素以附加到列表中的方式,因此
list(b2)
的结果是一个有序的列表,并且使用Python中使用的算法可以非常快地进行排序。

一个集合将主要通过哈希键进行排序,对于整数来说,哈希键就是值本身。Python中的Timsort算法擅长识别已排序的序列。b2可能比a2短得多。这并不能解释整个效果,但需要注意的是,在对这两种输入进行计时时,您没有使用可比较的输入大小operations@PascalvKooten我不是基准测试方面的专家,但我希望如果你想更公平地对集合和列表排序,你可以调整范围(1000),然后将结果作为集合或列表。这至少会让你从同一个N.@PascalvKooten开始,你想要一个独特元素的随机样本
numpy.random.randint
不能保证这一点。根据我的测试,使用非平凡的数据类型,如
(int,int)
可以逆转这里看到的趋势,尽管使用中间集只会增加一点低效率(~10%)。我怀疑,使用中间集对整数更快的原因是,由于Python使用的是简单的散列,所以集合构造过程会自动将每个整数按正确的顺序(或几乎如此)排列,而不进行排序。
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
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