#python #set #time-complexity
#python #набор #временная сложность
Вопрос:
Быстрее ли в python а) Создавать набор из списка из n элементов б) вставлять n элементов в набор?
Я нашел эту страницу (http://wiki .python.org/moin/TimeComplexity ) но у него не было достаточной информации, чтобы сделать вывод, что было быстрее.
Кажется, что вставка элементов по одному в худшем случае может занять O (n * n) времени (учитывая, что он использует dicts) и O (n * 1) в среднем случае. Обеспечивает ли инициализация набора с помощью списка какое-либо улучшение производительности?
Комментарии:
1. Кажется, вы хотите сравнить построение набора из списка с вставкой n элементов в set, но первая строка вопроса означает что-то другое.
2. Упс! спасибо, что указал на это, сатиш.
3. Это легко выяснить самостоятельно с помощью
timeit
.
Ответ №1:
С точки зрения O()
сложности — это определенно одно и то же, потому что оба подхода выполняют абсолютно одинаковое — вставляют n
элементы в набор.
Разница заключается в реализации: Одним из явных преимуществ инициализации с помощью iterable является то, что вы экономите много вызовов функций на уровне Python — инициализация с помощью iterable выполняется полностью на уровне C (**).
Действительно, некоторые тесты в списке из 5 000 000 случайных целых чисел показывают, что добавление одного за другим происходит медленнее:
lst = [random.random() for i in xrange(5000000)]
set1 = set(lst) # takes 2.4 seconds
set2 = set() # takes 3.37 seconds
for item in lst:
set2.add(item)
(**) Заглядывая внутрь кода наборов ( Objects/setobject.c
), в конечном итоге вставка элемента сводится к вызову set_add_key
. При инициализации из iterable эта функция вызывается в тесном цикле C:
while ((key = PyIter_Next(it)) != NULL) {
if (set_add_key(so, key) == -1) {
Py_DECREF(it);
Py_DECREF(key);
return -1;
}
Py_DECREF(key);
}
С другой стороны, каждый вызов set.add
вызывает поиск атрибута, который преобразуется в set_add
функцию C, которая, в свою очередь, вызывает set_add_key
. Поскольку само добавление элемента происходит относительно быстро (реализация хэш-таблицы в Python очень эффективна), все эти дополнительные вызовы накапливаются.
Комментарии:
1. Цикл Python намного ближе по производительности, чем я ожидал, и вы можете стать еще ближе, создав локальную переменную, содержащую ссылку на
set.add
, и вызывая ее в цикле, избегая поиска атрибута. В моих тестах это было всего на 15% медленнее, чем при использованииset()
конструктора!
Ответ №2:
$ python -V
Python 2.5.2
$ python -m timeit -s "l = range(1000)" "set(l)"
10000 loops, best of 3: 64.6 usec per loop
$ python -m timeit -s "l = range(1000)" "s = set()" "for i in l:s.add(i)"
1000 loops, best of 3: 307 usec per loop
Комментарии:
1. Вы можете ускорить выполнение 2-й команды в 2 раза на
s_add = s.add
:python -m timeit -s "l = range(1000)" "s = set(); s_add=s.add" "for i in l:s_add(i)"
Ответ №3:
На моем Thinkpad:
In [37]: timeit.timeit('for a in x: y.add(a)',
'y=set(); x=range(10000)', number=10000)
Out[37]: 12.18006706237793
In [38]: timeit.timeit('y=set(x)', 'y=set(); x=range(10000)', number=10000)
Out[38]: 3.8137960433959961
Ответ №4:
Вот результаты выполнения сравнения с использованием timeit
. Кажется, инициализация набора с использованием списка выполняется быстрее, любопытно узнать, почему это так:
from timeit import timeit
timeit("set(a)","a=range(10)")
# 0.9944498532640864
timeit("for i in a:x.add(i)","a=range(10);x=set()")
# 1.6878826778265648
Версия Python: 2.7