2010-10-16 2 views
8
# 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 говорит, что должно быть только один правильный способ сделать все. Я никогда не знал, что это было.Python - удаление элементов из списков

+3

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

+1

Кроме того, можно ли предположить, что все элементы в списках будут хешироваться все время? Если нет, или иногда нет, это было бы очень значительным. – martineau

+1

Почему вы не используете наборы для начала? Тогда ваша «арифметика» будет работать. – poke

ответ

10

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

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μsec, очевидно) лучше, если спрашивающий не будет иметь дубликаты в L1 и хочет они удаляются один раз каждый раз, каждый раз, когда дубликат появляется в одном из других списков ,

+0

Возможно, вы сможете повысить производительность, создав замороженный набор из цепочки из двух итераторов списков. – intuited

+0

Нет, замороженные наборы имеют точно такую ​​же скорость, что и обычные, но обычно требуют дополнительных затрат для создания, потому что вы должны создать промежуточный результат или цикл самостоятельно, если, как здесь, вы создаете их из нескольких входных итераций. –

0

Предполагая, что ваши индивидуальные списки не будут содержать дубликаты .... Используйте 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))) 
+2

Это приведет к потере порядка. –

+1

Да, ключевое различие между списком и набором ... – mepcotterell

+1

Если заказ/дубликаты НЕ являются проблемой, это самый чистый вариант, IMO –

0

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

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

МЕТОД 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

Понимание списка не позволяет удалить операции, которые являются дорогостоящими. – aaronasterling

+0

#aaron да, я знаю. Я имел в виду решение, отправленное Сантьяго. –

+1

Эй, вы в основном используете словарь в качестве набора. У них есть целый другой тип данных для этого: http://docs.python.org/library/stdtypes.html#types-set – intuited

0

Это может быть менее pythonesque, чем список-понимания ответа, но имеет более простой взгляд на него:

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

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

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

+1

Это невероятно дорого и, наоборот, больше сложно смотреть, чем простое понимание. – aaronasterling

+0

A проблема проблема, похоже. Мне очень нравятся списки понятий, я на самом деле склоняюсь к их чрезмерному использованию, но я не думаю, что «n для n в L, если n не в ...» приятно в глазу. Так или иначе, я соглашусь, вычислительно дорого. – slezica

6

update ::: сообщение содержит ссылку на ложные утверждения о низком исполнении наборов по сравнению с фризонсетцами. Я утверждаю, что в этом случае по-прежнему разумно использовать фризонсет, хотя нет необходимости хешировать сам набор, только потому, что он более корректен семантически. Хотя на практике я, возможно, не стал бы набирать лишних 6 символов. Я не чувствую себя мотивированным, чтобы пройти и отредактировать сообщение, поэтому просто сообщайте, что «обвинения» ссылаются на некоторые неправильные тесты. Подробности в деталях хэшируются в комментариях. ::: обновление

Второй кусок кода posted Брэндон Крэйг Родос довольно хорошо, но он не ответил на мое предложение об использовании frozenset (ну, не тогда, когда я начал писать это, во всяком случае) , Я собираюсь идти вперед и публиковать его сам.

Вся основа взятого на себя обязательства состоит в том, чтобы проверить, соответствуют ли каждая из ряда значений (L1) другим набором значений; этот набор значений - это содержимое L2 и L3. Использование слова «set» в этом предложении говорит: хотя L2 и L3 равны list, нам не очень нравятся их свойства, подобные спискам, такие как порядок, в котором находятся их значения, или сколько из них содержат. Мы просто заботимся о набор (там он снова) значений, которые они в совокупности содержат.

Если этот набор значений хранится как список, вам необходимо пройти через элементы списка один за другим, проверяя каждый из них. Это относительно много времени, и это плохая семантика: опять же, это «набор» значений, а не список. Таким образом, Python имеет эти опрятные типы наборов, которые содержат кучу уникальных значений и могут быстро сказать вам, есть ли в них какое-то значение или нет. Это работает почти так же, как и типы python dict, когда вы просматриваете ключ.

Разница между множествами и frozensets является то, что наборы изменчивы, что означает, что они могут быть изменены после создания. Документация по обоим типам - here.

Поскольку набор, который нам нужно создать, объединение значений, хранящихся в L2 и L3, не будет изменяться после его создания, он семантически подходит для использования неизменяемого типа данных. Это также имеет . Ну, имеет смысл, что у него будет какое-то преимущество; в противном случае, почему Python имеет frozenset как встроенный?

обновление ...

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

Я провел несколько неофициальных тестов времени, сравнивая скорость создания и поиска на относительно больших (3000-элементных) замороженных и изменяемых наборах; не было большой разницы. Это противоречит вышеуказанной ссылке, но поддерживает то, что Брэндон говорит о том, что они идентичны, но для аспекта изменчивости.

... обновление

Теперь, потому что frozensets неизменны, они не имеют способ обновления. Брэндон использовал метод set.update, чтобы избежать создания и затем отбрасывания временного списка в пути для установки создания; Я собираюсь использовать другой подход.

items = (item for lst in (L2, L3) for item in lst) 

Это делает generator expressionitems итератора над, последовательно, содержимое L2 и L3. Не только это, но и делает это без создания целого списка, заполненного промежуточными объектами. Использование вложенных выражений for в генераторах немного запутанно, но мне удается сохранить его в порядке, помня, что они гнездятся в том же порядке, что и если бы вы написали фактические для циклов, например.

def get_items(lists): 
    for lst in lists: 
     for item in lst: 
      yield item 

Это generator function эквивалентно выражению генератора, который мы присвоенного items. Ну, за исключением того, что это определение параметризованной функции вместо прямого назначения переменной.

В любом случае, достаточно отступления. Большое дело с генераторами в том, что они фактически ничего не делают. Ну, по крайней мере, не сразу: они просто настраивают работу, которую нужно выполнить позже, когда выражение генератора повторено. Это формально обозначается как ленивый. Мы собираемся сделать это (ну, во всяком случае, я), передав items функции frozenset, которая выполняет итерацию над ней и возвращает морозный холодный морозильник.

unwanted = frozenset(items) 

Вы могли бы на самом деле объединить две последние строки, поставив выражение генератора прямо в вызове frozenset:

unwanted = frozenset(item for lst in (L2, L3) for item in lst) 

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

Теперь мы можем построить новый список так же, как это сделал Брэндон, с list comprehension. Они используют тот же синтаксис, что и выражения генератора, и делают в основном одно и то же, за исключением того, что они являются eager вместо lazy (опять же, это настоящие технические термины), поэтому они получают право на работу с итерацией по элементам и создание список из них.

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, это намного проще: просто создать этот набор:

L1_unique_values = set(L1) - unwanted 

Вы можете составить список из из этого, as does 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

+1 Очень информативный. Самое последнее дополнение (с itertools) очень приятно. Я бы сказал, что вы заработали свой кандидат в списках фильтрации на основе включения в набор списков. – aaronasterling

+0

@aaron: Это заняло годы учебы, но это того стоило. – intuited

+0

Я что-то упустил или это ваше выражение генератора просто 'itertools.chain'? Если да, просто используйте это (вы можете сохранить объяснение генераторов и выражений genrator, хотя люди должны узнать о них). – delnan

0

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

Процедура выглядит следующим образом:

  1. Использование itertools.chain к цепи L2 и L3 без создания памяти, потребляя копию
  2. Создать набор от этого (в данном случае, frozenset будет делать, потому что мы не 't изменить его после создания)
  3. Использовать список, чтобы отфильтровать элементы, которые находятся в L1, а также в L2 или L3. В зависимости от установки/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

Это не быстрый; проверьте тесты в моем посте. Помещение итератора между набором и уже истребимыми списками просто замедляет работу. –

+0

@Brandon Крейг Роудс: Хорошо, скажем, «одно из самых быстрых решений». Спасибо, что опубликовали результаты тестов. – AndiDog

+0

Действительно - ваши решения, безусловно, один из самых быстрых и, безусловно, один из классов решений O (* n * log * m *), которые заслуживает этой проблемы. Я просто хотел удостовериться, что программисты понимают, что итераторы - это не пыль пикселов, которые как-то быстрее, чем петля над контейнером; каждый элемент, возвращаемый итератором, требует, чтобы его область была повторно активирована, а его код перезапущен, поэтому их преимущества не предоставляются бесплатно. –

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