2015-10-16 1 views
2

Я создаю классический класс «набор» для практики, и первое, что я хочу сделать, это удалить все дубликаты. Я знаю, что я мог бы сделать это легко со словарными клавишами, но я хотел попытаться улучшить понимание своего списка. Эти две функции должны делать то же самое, но второе не работает. Зачем?List remove() метод в выражении выражения выражения выражения

for element in elements: 
      if elements.count(element) > 1: 
       elements.remove(element) 
     print(elements) 

Второе:

self.elements = [elements.remove(element) for element in elements 
       if elements.count(element) > 1] 
+5

Ни одна из версий вашего кода не сделает то, что вы хотите. Мутировав список, когда вы повторяете его, пропустите некоторые значения! – Blckknght

+0

Возможный дубликат [Удалить элементы из списка при повторении в Python] (http://stackoverflow.com/questions/1207406/remove-items-from-a-list-while-iterating-in-python) – Makoto

+0

Что относительно списка (набор (элементы))? – rebeling

ответ

4

Не перебрать и удалить из того же списка, вы должны также использовать Counter Dict для подсчета вхождений каждого элемента, если ваши объекты hashable:

from collections import Counter 
cn = Counter(elements) 
# elements[:] changes original list 
elements[:] = (ele for ele in elements if ch[ele] < 2) 

В вашем втором коде, потому что list.remove является Inplace операция будет просто добавить None's в ваш список в любое время if elements.count(element) > 1 is True или ничего не делать, поэтому два примера кода совершенно разные.

Первый код, если он работает, работает только случайно. Когда вы удаляете элемент из своего списка, то, что указатель указывал на ранее, может измениться, чтобы вы удалили из списка неправильные элементы.

Пример того, что ваш второй код делает и почему ваш первый неправильный подход:

In [20]: l = [2,3,1,4,1,5] 

In [21]: l = [l.remove(i) if i > 1 else i for i in l] 

In [22]: l 
Out[22]: [None, 1, None, None] 

Поскольку вы изменили значения указателей вы в конечном итоге удаление второго 1 и с несколько Нет добавляется потому, что как и все функции, которые работают inplace или не указывают возвращаемое значение в python, они возвращают None по умолчанию.

Если вы действительно хотите, чтобы получить уникальный набор всех элементов, а не только сохранить уникальные элементы, является то, что ваш код, кажется, пытается, а также поддерживать порядок, collections.OrderedDict ДИКТ будет делать то, что вам нужно:

from collections import OrderedDict 
elements[:] = collections.OrderedDict.fromkeys(elements) 
+0

Спасибо, это имеет смысл! – flybonzai

+0

Нет проблемы, подход Counter dict также делает ваш код 'O (n)' в отличие от 'O (n^2)', поскольку мы делаем только один проход, чтобы получить подсчеты, а затем еще один проход для фильтрации исходного списка –

+0

Код 'Counter' не делает то, что хочет пользователь. Это исключает * все копии * дублированных значений, а не всех, кроме одного. – Blckknght

1

В коде есть две проблемы. Первая проблема заключается в том, о чем вы явно спрашиваете: версия для ознакомления в списке будет назначать целую пучку значений None для self.elements. Значения None - это просто возвращаемые значения от ваших вызовов до list.remove. Он изменяет список на месте и не имеет ничего полезного для возврата (поэтому он возвращает None).

Осмысление [element for element in elements if elements.count(element) == 1 or elements.remove(element)] будет работать так же, как ваш другой код (с None является falsey и or короткого замыкания), но она по-прежнему работает на второй вопрос. (Это также немного уродливый взлом: новый список, созданный постиганием, будет иметь такое же содержимое, как elements, так как remove изменяет elements на месте, и это довольно запутанно. Написание трудно понять код, как правило, не очень хорошая идея.)

Вторая проблема заключается в том, что изменение списка во время итерации по нему может вызвать проблемы. Список итераторов работает по индексу. Первый элемент, полученный итератором, - это индекс 0, второй - индекс 1 и т. Д. Если вы измените список, удалив элемент в начале списка, вы переместите индексы всех последующих элементов.

Итак, скажем, вы удаляете первый элемент (из индекса 0) сразу после того, как ваш итератор показал его вам.В списке будут перенесены все более поздние значения, но итератор об этом не узнает. Он по-прежнему будет давать элемент в индексе 1 далее, даже если это был элемент в индексе 2 (до изменения списка). Элемент первоначально в индексе 1 (и при индексе 0 после удаления предыдущего элемента) будет пропущен итерацией.

Вот простой пример этого вопроса, в котором значение 2, 5 и 8 не будет печататься:

L = list(range(10)) # [0,1,2,3,4,5,6,7,8,9] 
for x in L: 
    print(x) 
    if x % 3 == 1: # true for 1,4, and 7 
     L.remove(x) 

В примере, логика для удаления значений довольно проста, и мы никогда не пропустить значение, которое мы обычно хотели бы удалить (поэтому L имеет ожидаемое значение [0,2,3,5,6,8,9] в конце), другой код может работать не так хорошо.

Способ избежать этой проблемы - перебрать копию списка при изменении оригинала. В этой ситуации мы также должны count в оригинале, а не копии:

for element in elements[:]: # copy list with a slice here! 
    if elements.count(element) > 1: 
     elements.remove(element) # modify the original list 

Это довольно неэффективно, хотя, так как удаление элемента из списка (в положении, чем в конце) необходимо найдите время, чтобы сдвинуть все более поздние значения на одну позицию. Подсчет также медленный, так как вам нужно перебрать весь список для каждого элемента. Это гораздо более эффективно отслеживать уникальные предметы, которые вы видели до сих пор, и пропустить дублированные элементы, когда вы видите их позже:

seen = set() 
results = [] 
for element in elements: 
    if element not in seen: 
     seen.add(element) 
     results.append(element) 

Вы можете даже создать несколько неудобного список понимание (с побочными эффектами) из этот код:

seen = set() 
results = [element for element in elements 
      if not (element in seen or seen.add(element))] 

лучший подход, как правило, для объединения дедуплицирующей логики в функцию генератора (например, в unique_everseen recipe в документации itertools), а затем вызвать его с list(dedupe(elements)).

Смежные вопросы