update ::: В посте содержится ссылка на ложные утверждения о худших характеристиках наборов по сравнению с морозозонцами. Я утверждаю, что в этом случае все еще целесообразно использовать frozenset, даже если нет необходимости хэшировать сам набор, просто потому, что он более корректен семантически. Хотя на практике я не смог бы набрать лишние 6 символов. Я не чувствую мотивации просматривать и редактировать пост, поэтому просто предупреждаю, что ссылка «обвинения» ссылается на некоторые неправильно выполненные тесты. Кровавые подробности хешируются в комментариях. ::: обновление
Второй кусок кода , опубликованный Брэндоном Крейгом Роудсом, довольно хорош, но, поскольку он не ответил на мое предложение об использовании фрозенсе (ну, во всяком случае, не тогда, когда я начал писать это), Я собираюсь опубликовать это сам.
Вся основа рассматриваемого предприятия заключается в проверке того, находятся ли каждое из ряда значений (L1
) в другом наборе значений; этот набор значений является содержимым L2
и L3
. Использование слова «set» в этом предложении говорит о том, что, хотя L2
и L3
являются list
s, нам не очень важны их свойства, подобные списку, например, порядок их значений. или сколько из каждого они содержат. Мы просто заботимся о множестве (вот оно снова) значений, которые они вместе содержат.
Если этот набор значений хранится в виде списка, вы должны просмотреть элементы списка один за другим, проверяя каждый из них. Это относительно много времени и плохая семантика: опять же, это «набор» значений, а не список. Таким образом, в Python есть эти аккуратные наборы типов, которые содержат множество уникальных значений и могут быстро сказать вам, есть ли какое-то значение в них или нет. Это работает почти так же, как и типы dict
в Python, когда вы ищете ключ.
Разница между наборами и frozensets заключается в том, что наборы являются изменяемыми, что означает, что они могут быть изменены после создания. Документация по обоим типам здесь .
Поскольку набор, который мы должны создать, объединение значений, хранящихся в L2
и L3
, не будет изменено после создания, семантически целесообразно использовать неизменный тип данных. Это также предположительно имеет некоторые преимущества в производительности. Ну, это имеет смысл, что это будет иметь некоторое преимущество; в противном случае, почему бы Python был frozenset
встроенным?
обновление ...
Брэндон ответил на этот вопрос: реальное преимущество замороженных наборов состоит в том, что их неизменность позволяет им быть хешируемыми , что позволяет им быть ключами словаря или членами других наборов.
Я провел несколько неофициальных временных тестов, сравнивающих скорость создания и поиска на относительно больших (3000 элементов) замороженных и изменяемых наборах; не было большой разницы. Это противоречит приведенной выше ссылке, но поддерживает то, что Брэндон говорит о том, что они идентичны, но с точки зрения изменчивости.
... обновление
Теперь, поскольку frozensets являются неизменяемыми, у них нет метода обновления. Брэндон использовал метод set.update
, чтобы избежать создания, а затем отбрасывать временный список в пути, чтобы установить создание; Я собираюсь пойти другим путем.
items = (item for lst in (L2, L3) for item in lst)
Это генераторное выражение делает items
итератором, последовательно, по содержимому L2
и L3
. Не только это, но и делает это без создания целого списка, полного промежуточных объектов. Использование вложенных for
выражений в генераторах немного сбивает с толку, но мне удается их отсортировать, помня, что они вложены в том же порядке, в котором они были бы, если бы вы писали фактические для циклов, например,
def get_items(lists):
for lst in lists:
for item in lst:
yield item
То, что функция генератора эквивалентна выражению генератора, которое мы присвоили items
. Ну, разве что это параметризованное определение функции вместо прямого присвоения переменной.
В любом случае, достаточно отступления.Большая проблема с генераторами в том, что они на самом деле ничего не делают.Ну, по крайней мере, не сразу: они просто настроили работу, которая будет сделана позже, когда выражение генератора будет повторено .Формально это называется ленивым .Мы собираемся сделать это (ну, в любом случае), передав items
функции frozenset
, которая перебирает ее и возвращает морозно-холодный морозозет.
unwanted = frozenset(items)
Вы могли бы на самом делеобъединить последние две строки, поместив выражение генератора прямо в вызове к frozenset
:
unwanted = frozenset(item for lst in (L2, L3) for item in lst)
Этот аккуратный синтаксический прием работает до тех пор, пока итератор создается выражением генератораявляется единственным параметром функции, которую вы вызываете.В противном случае вы должны записать его в обычном отдельном наборе скобок, как если бы вы передавали кортеж в качестве аргумента функции.
Теперь мы можем построить новый список так же, как это сделал Брэндон, с список понимания .Они используют тот же синтаксис, что и выражения генератора, и делают в основном то же самое, за исключением того, что они рвение вместо ленивый (опять-таки, это настоящие технические термины), поэтому они получают правоработать с элементами и создавать из них список.
L4 = [item for item in L1 if item not in unwanted]
Это эквивалентно передаче выражения генератора в list
, например,
L4 = list(item for item in L1 if item not in unwanted)
, но более идиоматично.
Таким образом, будет создан список L4
, содержащий элементы L1
, которых не было ни в L2
, ни L3
, с сохранением порядка, в котором они были изначально, и количества их, которыетам было.
Если вы просто хотите узнать, какие значения находятся в L1
, но не в L2
или L3
, это гораздо проще: вы просто создаете этоset:
L1_unique_values = set(L1) - unwanted
Вы можете сделать из него список, , как и st0le , но на самом деле это может быть не то, что вы хотите.Если вы действительно хотите установить значений, которые можно найти только в L1
, у вас может быть очень веская причина оставить этот набор как set
, или дажеfrozenset
:
L1_unique_values = frozenset(L1) - unwanted
... Annnnd , теперь для чего-то совершенно другого:
from itertools import ifilterfalse, chain
L4 = list(ifilterfalse(frozenset(chain(L2, L3)).__contains__, L1))