Достаточно общая операция заключается в фильтрации одного list
на основе другого list
. Люди быстро находят, что это:
[x for x in list_1 if x in list_2]
медленный для больших входов - это O (n * m). Тьфу. Как мы ускоряем это? Используйте set
для поиска фильтров O (1):
s = set(list_2)
[x for x in list_1 if x in s]
Это дает хорошее общее поведение O (n). Я часто вижу, что даже ветеран-кодеры попадают в The Trap ™:
[x for x in list_1 if x in set(list_2)]
Ack! Это снова O (n * m), поскольку python создает set(list_2)
каждый раз, а не только один раз.
Я думал, что это конец истории - python не может оптимизировать его, чтобы только создать set
один раз. Просто знай о ловушке. Должен жить с этим. Хм.
#python 3.3.2+
list_2 = list(range(20)) #small for demonstration purposes
s = set(list_2)
list_1 = list(range(100000))
def f():
return [x for x in list_1 if x in s]
def g():
return [x for x in list_1 if x in set(list_2)]
def h():
return [x for x in list_1 if x in {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19}]
%timeit f()
100 loops, best of 3: 7.31 ms per loop
%timeit g()
10 loops, best of 3: 77.4 ms per loop
%timeit h()
100 loops, best of 3: 6.66 ms per loop
Да, python (3.3) может оптимизировать заданный литерал. Он даже быстрее, чем f()
в этом случае, по-видимому, потому, что он заменяет a LOAD_GLOBAL
на LOAD_FAST
.
#python 2.7.5+
%timeit h()
10 loops, best of 3: 72.5 ms per loop
Python 2, в частности, не делает эту оптимизацию. Я попытался еще раз изучить, что делает python3, но, к сожалению, dis.dis
не может исследовать внутренности выражений понимания. В основном все интересное превращается в MAKE_FUNCTION
.
Итак, теперь мне интересно - почему python 3.x оптимизирует заданный литерал только для сборки один раз, но не set(list_2)
?