Python - удаление элементов из списков - PullRequest
9 голосов
/ 16 октября 2010
# I have 3 lists:
L1 = [1, 2, 3, 4, 5, 6, 7, 8, 9]
L2 = [4, 7, 8]
L3 = [5, 2, 9]
# I want to create another that is L1 minus L2's memebers and L3's memebers, so:
L4 = (L1 - L2) - L3  # Of course this isn't going to work

Мне интересно, каков «правильный» способ сделать это.Я могу сделать это разными способами, но руководство по стилю в Python говорит, что должен быть только 1 правильный способ выполнения каждой вещи.Я никогда не знал, что это было.

Ответы [ 6 ]

10 голосов
/ 16 октября 2010

Вот некоторые попытки:

L4 = [ n for n in L1 if (n not in L2) and (n not in L3) ]  # parens for clarity

tmpset = set( L2 + L3 )
L4 = [ n for n in L1 if n not in tmpset ]

Теперь, когда у меня был момент, чтобы подумать, я понимаю, что вещь L2 + L3 создает временный список, который немедленно отбрасывается.Итак, еще лучший способ:

tmpset = set(L2)
tmpset.update(L3)
L4 = [ n for n in L1 if n not in tmpset ]

Обновление: Я вижу, что некоторые экстравагантные заявления касаются производительности, и я хочу утверждать, что мое решение уже было настолько быстрым, насколько это возможно.Создание промежуточных результатов, будь то промежуточные списки или промежуточные итераторы, которые затем необходимо вызывать повторно, всегда будет медленнее, чем просто предоставление L2 и L3 для набора, который будет повторяться напрямую, как я делал здесь.

$ python -m timeit \
  -s 'L1=range(300);L2=range(30,70,2);L3=range(120,220,2)' \
  'ts = set(L2); ts.update(L3); L4 = [ n for n in L1 if n not in ts ]'
10000 loops, best of 3: 39.7 usec per loop

Все другие альтернативы (о которых я могу думать) обязательно будут медленнее, чем эта.Например, выполнение самих циклов вместо того, чтобы позволить конструктору set() делать их, увеличивает затраты:

$ python -m timeit \
  -s 'L1=range(300);L2=range(30,70,2);L3=range(120,220,2)' \
  'unwanted = frozenset(item for lst in (L2, L3) for item in lst); L4 = [ n for n in L1 if n not in unwanted ]'
10000 loops, best of 3: 46.4 usec per loop

Использование итераторов приведет к тому, что все сохраняемые состояния и обратные вызовы, которые они включают, очевидно, будут дажедороже:

$ python -m timeit \
  -s 'L1=range(300);L2=range(30,70,2);L3=range(120,220,2);from itertools import ifilterfalse, chain' \
  'L4 = list(ifilterfalse(frozenset(chain(L2, L3)).__contains__, L1))' 
10000 loops, best of 3: 47.1 usec per loop

Поэтому я считаю, что ответ, который я дал прошлой ночью, все еще далек (для значений «далеко и далеко» больше, чем около 5 мкс, очевидно) лучший, если только вопрос не будетиметь дубликаты в L1 и хочет, чтобы они удалялись один раз за каждый раз, когда дубликат появляется в одном из других списков.

6 голосов
/ 16 октября 2010

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))
0 голосов
/ 16 октября 2010

Я думаю, что интуитивный ответ слишком длинный для такой простой проблемы, и в Python уже есть встроенная функция для объединения двух списков в качестве генератора.

Процедура следующая:

  1. Используйте itertools.chain для связывания L2 и L3 без создания копии, потребляющей память
  2. Создайте набор из этого (в этом случае подойдет морозозет, потому что мы не меняем его после создания)
  3. Используйте понимание списка, чтобы отфильтровать элементы, которые находятся в L1, а также в L2 или L3. Так как поиск set / frozenset (x in someset) равен O (1), это будет очень быстро.

А теперь код:

L1 = [1, 2, 3, 4, 5, 6, 7, 8, 9]
L2 = [4, 7, 8]
L3 = [5, 2, 9]

from itertools import chain
tmp = frozenset(chain(L2, L3))
L4 = [x for x in L1 if x not in tmp] # [1, 3, 6]

Это должно быть одно из самых быстрых, простых и наименее потребляющих память решений.

0 голосов
/ 16 октября 2010

Это может быть менее питонно, чем ответ со списком, но выглядит проще:

l1 = [ ... ]
l2 = [ ... ]

diff = list(l1) # this copies the list
for element in l2:
    diff.remove(element)

Преимущество здесь в том, что мы сохраняем порядок списка, и если имеется повторяющихся элементов , мы удаляем только один раз при каждом его появлении в l2.

0 голосов
/ 16 октября 2010

Выполнение таких операций в списках может очень скоро снизить производительность вашей программы.Что происходит, с каждым удалением, операции List делают новый malloc и перемещают элементы.Это может быть дорого, если у вас очень большой список или нет.Поэтому я хотел бы предложить это -

Я предполагаю, что в вашем списке есть уникальные элементы.В противном случае вам нужно поддерживать список в вашем файле с дублирующимися значениями.В любом случае для данных, которые вы предоставили, здесь это -

МЕТОД 1

d = dict()
for x in L1: d[x] = True

# Check if L2 data is in 'd'
for x in L2:
    if x in d:
        d[x] = False

for x in L3:
    if x in d:
        d[x] = False

# Finally retrieve all keys with value as True.
final_list = [x for x in d if d[x]]

МЕТОД 2 Если все это выглядит слишком много кода,Тогда вы можете попробовать использовать set.Но при этом ваш список потеряет все повторяющиеся элементы.

final_set  = set.difference(set(L1),set(L2),set(L3))
final_list = list(final_set)
0 голосов
/ 16 октября 2010

Предполагая, что ваши индивидуальные списки не будут содержать дубликаты .... Используйте Set и Difference

L1 = [1, 2, 3, 4, 5, 6, 7, 8, 9]
L2 = [4, 7, 8]
L3 = [5, 2, 9]
print(list(set(L1) - set(L2) - set(L3)))
...