Огромная разница во времени между сортировкой набора и сортировкой списка в Python

Мне было интересно, должна ли я иметь свою структуру данных в виде набора или списка. В основном я буду делать операции над множествами, но в конце мне нужно будет их отсортировать.

Я задавался вопросом, должен ли я сначала составить список, а затем использовать sorted(list(my_set))или сразу же отсортировать набор sorted(my_set), Возможно, я мог бы рассмотреть общую фазу "для составления списка", так как в любом случае имеет смысл иметь упорядоченную итерацию в тот момент времени.

Поэтому я решил проверить это, ожидая, что список будет быстрее.

Benchmarker:

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

Затем я понял, что это может быть связано с тем, что элементы уже на месте, и вспомнил этот удивительный вопрос и ответ.

Затем я попробовал некоторые случайные данные:

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),

Действительно, во втором случае могут быть дубликаты, которые будут отфильтрованы, что ускорит сортировку.

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

Вместо итерации набор имеет 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) упорядоченный список, который сортируется довольно быстро с использованием алгоритма Timsort, используемого в Python.

Другие вопросы по тегам