С точки зрения сложности O()
- это одно и то же, потому что оба подхода делают одно и то же - вставляют n
элементы в набор.
Различие заключается в реализации: одно явное преимущество инициализациииз итерируемого является то, что вы сохраняете много вызовов функций уровня Python - инициализация из итерируемого выполняется полностью на уровне 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
.При инициализации из итерируемого эта функция вызывается в узком цикле 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
вызывает поиск атрибута, который преобразуется в функцию C set_add
,что в свою очередь вызывает set_add_key
.Поскольку само добавление элементов относительно быстрое (реализация Python для хэш-таблицы очень эффективна), все эти дополнительные вызовы накапливаются.