Удалить первые обнаруженные элементы из списка

У меня есть два списка Python с одинаковым количеством элементов. Элементы первого списка уникальны, а во втором - не обязательно. Например,

list1 = ['e1', 'e2', 'e3', 'e4', 'e5', 'e6', 'e7']
list2 = ['h1', 'h2', 'h1', 'h3', 'h1', 'h2', 'h4']

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

>>>list1
['e3', 'e5', 'e6']
>>>list2
['h1', 'h1', 'h2']

Таким образом, элемент "e1" был удален, потому что его соответствующий "h1" был встречен впервые, "e2" был удален, потому что "h2" был замечен впервые, "e3" остался, потому что "h1 'уже был замечен," e4 "был удален, потому что" h3 "был замечен впервые," e5" остался, потому что "h1" уже был замечен, "e6" оставлен, потому что "h2" уже был замечен, а "e7 "'был удален, потому что" h4" был замечен в первый раз.

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

Ответ 1

Просто используйте объект set для поиска, если текущее значение уже видно, например

>>> list1 = ['e1', 'e2', 'e3', 'e4', 'e5', 'e6', 'e7']
>>> list2 = ['h1', 'h2', 'h1', 'h3', 'h1', 'h2', 'h4']
>>>
>>> def filterer(l1, l2):
...     r1 = []
...     r2 = []
...     seen = set()
...     for e1, e2 in zip(l1, l2):
...         if e2 not in seen:
...             seen.add(e2)
...         else:
...             r1.append(e1)
...             r2.append(e2)
...     return r1, r2
...
>>> list1, list2 = filterer(list1, list2)
>>> list1
['e3', 'e5', 'e6']
>>> list2
['h1', 'h1', 'h2']

Если вы собираетесь использовать элементы один за другим, и если входные списки довольно большие, я бы рекомендовал сделать генератор, как этот

>>> def filterer(l1, l2):
...     seen = set()
...     for e1, e2 in zip(l1, l2):
...         if e2 not in seen:
...             seen.add(e2)
...         else:
...             yield e1, e2
...
>>> list(filterer(list1, list2))
[('e3', 'h1'), ('e5', 'h1'), ('e6', 'h2')]
>>>
>>> zip(*filterer(list1, list2))
[('e3', 'e5', 'e6'), ('h1', 'h1', 'h2')]

Ответ 2

Я мог бы играть в гольф здесь, но я считаю это интересным:

list1_new = [x for i, x in enumerate(list1) if list2[i] in list2[:i]]
print(list1_new)
# prints ['e3', 'e5', 'e6']

Что происходит здесь, если вы не знакомы со списком, понимаете следующее (читаете его с конца):

  • Я проверяю, существует ли элемент i of list2 в разрезе list2, который включает в себя все предыдущие элементы list2[:i].
  • если это произойдет, я захватил соответствующий элемент из list1 (x) и сохранил его в новом списке, который я создаю list1_new

Ответ 3

Эффективным способом было бы использовать set, который содержит все уже увиденные ключи. A set гарантирует вам средний поиск O(1).

Итак, что-то вроде этого должно работать:

s = set()
result1 = []
result2 = []
for x, y in zip(list1, list2):
    if y in s:
        result1.append(x)
        result2.append(y)
    else:
        s.add(y)

Обратите внимание, что это создаст новый список. Однако не должно быть большой проблемой, поскольку Python фактически не копирует строки, а создает только указатель на исходную строку.

Ответ 4

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

seen= set()
index= 0
while index < len(list1):
    i1, i2= list1[index], list2[index]
    if i2 in seen:
        index+= 1
    else:
        seen.add(i2)
        del list1[index]
        del list2[index]

Ответ 5

Из комментария:

Я надеялся избежать этого и редактировать списки на месте

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

seen = set()
toidx = 0
for first, second in itertools.izip(list1, list2):
    if second in seen:
        list1[toidx] = first
        list2[toidx] = second
        toidx += 1
    else:
        seen.add(second)
del seen
del list1[toidx:]
del list2[toidx:]

Поклонники С++ признают это как стирание-удаление идиомы.

del может сделать копию части списка, которую вы храните, но по крайней мере он делает их по одному, вместо того, чтобы одновременно иметь все пять коллекций в памяти (два входных списка, два вывода списки и набор seen).

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

Списки могут содержать тысячи элементов

Если вы используете реальный компьютер, а не какую-то микроскопическую машину, вытравленную на голову булавки, то тысячи элементов - ничто. Для списка требуется приблизительно 8 байтов на элемент. Для хранения одного и того же объекта в нескольких списках не требуется копия объекта. Таким образом, использование двух дополнительных списков для выходов займет примерно 16 байтов на пару входов: 160 КБ для 10 тыс. Элементов. Для масштаба браузер, на который я пишу этот ответ, в настоящее время использует 1 ГБ ОЗУ. Отключение SO во время его работы - это гораздо более оптимизация памяти, чем изменение списков на месте; -)

Сокращение использования памяти может помочь в производительности кэша. И если у вас есть сотни миллионов элементов, то модификация на месте может быть разницей между вашим запущенным кодом или сбоем.

Ответ 6

Вы пытаетесь:

>>> list1 = ['e1', 'e2', 'e3', 'e4', 'e5', 'e6', 'e7']
>>> list2 = ['h1', 'h2', 'h1', 'h3', 'h1', 'h2', 'h4']
>>> repeat = list(set([x for x in list2 if list2.count(x) > 1]))
>>> print repeat 
['h2', 'h1']
>>> l1=[]
>>> l2=[]
>>> for single_data in repeat:
    indices = [i for i, x in enumerate(list2) if x == single_data]
    del indices[0]
    for index in indices:
        l1.append(list1[index])
        l2.append(list2[index])


>>> print l1
['e6', 'e3', 'e5']
>>> print l2
['h2', 'h1', 'h1']

Ответ 7

Здесь:

list1 = ['e1', 'e2', 'e3', 'e4', 'e5', 'e6', 'e7']
list2 = ['h1', 'h2', 'h1', 'h3', 'h1', 'h2', 'h4']
seen = []
output = []
for index in range(len(list1)):
    if list2[index] not in seen:
        seen.append(list2[index])
    else:
        output.append(list1[index])

print output