Python находит элементы в одном списке, которых нет в другом [дубликат]

137

Мне нужно сравнить два списка, чтобы создать новый список конкретных элементов, найденных в одном списке, но не найденных в другом. Например:

main_list=[]
list_1=["a", "b", "c", "d", "e"]
list_2=["a", "f", "c", "m"] 

Я хочу перебрать list_1 и добавить в main_list все элементы из list_2, которых нет в list_1.

Результат должен быть:

main_list=["f", "m"]

Как я могу это сделать с помощью Python?

CosimoCD
источник
2
Вы ищете элементы, list_2которые нигде не появляются, list_1или элементы list_2, которых нет в том же индексе list_1?
Патрик Хо,

Ответы:

98

TL; DR:
РЕШЕНИЕ (1)

import numpy as np
main_list = np.setdiff1d(list_2,list_1)
# yields the elements in `list_2` that are NOT in `list_1`

РЕШЕНИЕ (2) Вам нужен отсортированный список

def setdiff_sorted(array1,array2,assume_unique=False):
    ans = np.setdiff1d(array1,array2,assume_unique).tolist()
    if assume_unique:
        return sorted(ans)
    return ans
main_list = setdiff_sorted(list_2,list_1)




ПОЯСНЕНИЕ:
(1) Вы можете использовать NumPy - х setdiff1d( array1, array2, assume_unique= False).

assume_uniqueспрашивает пользователя, УЖЕ УНИКАЛЬНЫЕ массивы.
Если False, то сначала определяются уникальные элементы.
Если Trueфункция будет считать, что элементы уже уникальны, и функция пропустит определение уникальных элементов.

Это дает уникальные значения array1, которых нет в array2. assume_uniqueэто Falseпо умолчанию.

Если вас интересуют уникальные элементы (на основе ответа Chinny84 ), просто используйте (где assume_unique=False=> значение по умолчанию):

import numpy as np
list_1 = ["a", "b", "c", "d", "e"]
list_2 = ["a", "f", "c", "m"] 
main_list = np.setdiff1d(list_2,list_1)
# yields the elements in `list_2` that are NOT in `list_1`


(2) Для тех, кто хочет отсортировать ответы, я создал специальную функцию:

import numpy as np
def setdiff_sorted(array1,array2,assume_unique=False):
    ans = np.setdiff1d(array1,array2,assume_unique).tolist()
    if assume_unique:
        return sorted(ans)
    return ans

Чтобы получить ответ, запустите:

main_list = setdiff_sorted(list_2,list_1)

ПОБОЧНЫЕ ЗАМЕЧАНИЯ:
(a) Решение 2 (пользовательская функция setdiff_sorted) возвращает список (по сравнению с массивом в решении 1).

(b) Если вы не уверены, уникальны ли элементы, просто используйте настройку NumPy по умолчанию setdiff1dв обоих решениях A и B. Что может быть примером сложности? См. Примечание (c).

(c) Все будет по-другому, если любой из двух списков не уникален.
Скажем list_2не уникальна: list2 = ["a", "f", "c", "m", "m"]. Сохранить list1как есть: list_1 = ["a", "b", "c", "d", "e"]
установка значения assume_uniqueурожайности по умолчанию ["f", "m"](в обоих решениях). ОДНАКО, если вы установите assume_unique=True, оба решения дают ["f", "m", "m"]. Зачем? Это потому, что пользователь ПРЕДПОЛОЖИЛ, что элементы уникальны). Значит, ЛУЧШЕ СОХРАНИТЬassume_uniqueзначение по умолчанию. Обратите внимание, что оба ответа отсортированы.

jcoderepo
источник
Если ваши списки уже упорядочены, это также вернет упорядоченный список. Собственное решение преобразования в наборы с последующим получением разницы (решения, показанные ниже) возвращает неупорядоченный список, что может затруднить визуальную проверку результатов.
Doubledown
1
Привет, @Doubledown! Ваша проблема была рассмотрена в отредактированном сообщении. Надеюсь это поможет!
jcoderepo
183

Можно использовать наборы:

main_list = list(set(list_2) - set(list_1))

Вывод:

>>> list_1=["a", "b", "c", "d", "e"]
>>> list_2=["a", "f", "c", "m"]
>>> set(list_2) - set(list_1)
set(['m', 'f'])
>>> list(set(list_2) - set(list_1))
['m', 'f']

Согласно комментарию @JonClements, вот более аккуратная версия:

>>> list_1=["a", "b", "c", "d", "e"]
>>> list_2=["a", "f", "c", "m"]
>>> list(set(list_2).difference(list_1))
['m', 'f']
nrlakin
источник
2
Это хорошо, если мы заботимся только об uniqueэлементах, но что, если у нас их несколько, m'sнапример, это не подберет его.
Chinny84
Это правда. Я предположил, что плакат искал уникальные элементы. Я полагаю, это зависит от того, что он подразумевает под «конкретным».
nrlakin
Действительно ps Я не голосовал против вашего ответа, особенно на неясный исходный вопрос.
Chinny84
13
Вы могли бы написать это так, чтобы list(set(list_2).difference(list_1))избежать явного setпреобразования ...
Джон Клементс
Не беспокойся! Спасибо @leaf за помощь в форматировании.
nrlakin
61

Не уверен, почему приведенные выше объяснения настолько сложны, когда у вас есть собственные методы:

main_list = list(set(list_2)-set(list_1))
A.Kot
источник
6
Причиной может быть сохранение порядка,
Кейт
57

Используйте понимание списка следующим образом:

main_list = [item for item in list_2 if item not in list_1]

Вывод:

>>> list_1 = ["a", "b", "c", "d", "e"]
>>> list_2 = ["a", "f", "c", "m"] 
>>> 
>>> main_list = [item for item in list_2 if item not in list_1]
>>> main_list
['f', 'm']

Редактировать:

Как упоминалось в комментариях ниже, с большими списками вышесказанное не является идеальным решением. В таком случае лучшим вариантом будет преобразование list_1в setпервое:

set_1 = set(list_1)  # this reduces the lookup time from O(n) to O(1)
main_list = [item for item in list_2 if item not in set_1]
ettanany
источник
3
Примечание. Для большего размера list_1вам нужно предварительно преобразовать его в set/ frozenset, например set_1 = frozenset(list_1), затем main_list = [item for item in list_2 if item not in set_1]сократить время проверки с O(n)каждого элемента до (примерно) O(1).
ShadowRanger
@ettanany Пожалуйста, будьте осторожны, если вы попробуете решение, опубликованное ettanany. Я попробовал решение ettanany как есть, и оно действительно очень медленное для большего списка. Можете ли вы обновить ответ, чтобы включить предложение shadowranger?
Doubledown
Можно ли получить индекс вместо строки?
JareBear,
@JareBear Вы можете использовать enumerate()для этого:[index for (index, item) in enumerate(list_2) if item not in list_1]
ettanany
@ ettanany большое спасибо !! Я сделаю это как можно скорее, я это сделал. Но ваш код выглядит намного чище.
JareBear,
6

Если вам нужно однострочное решение (без учета импорта), которое требует O(max(n, m))работы только для входных данных длины nи mне O(n * m)работает, вы можете сделать это с помощью itertoolsмодуля :

from itertools import filterfalse

main_list = list(filterfalse(set(list_1).__contains__, list_2))

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

Это дает те же результаты в одной строке, что и:

main_list = [x for x in list_2 if x not in list_1]

со скоростью:

set_1 = set(list_1)
main_list = [x for x in list_2 if x not in set_1]

Конечно, если сравнения должны быть позиционными, поэтому:

list_1 = [1, 2, 3]
list_2 = [2, 3, 4]

должен производить:

main_list = [2, 3, 4]

(поскольку ни одно значение в не list_2имеет совпадения с тем же индексом в list_1), вам обязательно следует пойти с ответом Патрика , который не включает временных lists или sets (даже если sets примерно O(1), они имеют более высокий «постоянный» коэффициент на проверку, чем простое равенство проверяет) и требует O(min(n, m))работы, меньше, чем любой другой ответ, и если ваша проблема зависит от позиции, это единственное правильное решение, когда совпадающие элементы появляются с несоответствующими смещениями.

†: способ сделать то же самое с пониманием списка как однострочным - это злоупотребить вложенным циклом для создания и кеширования значения (значений) в "внешнем" цикле, например:

main_list = [x for set_1 in (set(list_1),) for x in list_2 if x not in set_1]

что также дает незначительное преимущество в производительности на Python 3 (потому что теперь set_1локальная область видимости в коде понимания, а не поиск из вложенной области для каждой проверки; на Python 2 это не имеет значения, потому что Python 2 не использует замыкания для списки; они работают в той же области, в которой используются).

ShadowRanger
источник
4
main_list=[]
list_1=["a", "b", "c", "d", "e"]
list_2=["a", "f", "c", "m"]

for i in list_2:
    if i not in list_1:
        main_list.append(i)

print(main_list)

вывод:

['f', 'm']
Inconnu
источник
Как и решение на основе эквивалентного списка , это будет медленным, если list_1оно велико, и list_2имеет нетривиальный размер, потому что оно включает len(list_2) O(n)сканирование list_1, создание O(n * m)(где nи m- длины list_2и list_1соответственно). Если вы конвертируете list_1в set/ frozensetзаранее, проверки содержания могут быть выполнены в O(1), делая общую работу O(n)над длиной list_2(технически O(max(n, m)), поскольку вы действительно O(m)работаете над созданием set).
ShadowRanger
1

Я бы zipсобрал списки, чтобы сравнить их элемент за элементом.

main_list = [b for a, b in zip(list1, list2) if a!= b]
Патрик Хо
источник
Если OP хочет сравнить элемент за элементом (неясно, пример может быть любым), это намного эффективнее, чем другие ответы, поскольку это один дешевый проход по обоим lists с одним созданным новым list, без дополнительных временных модулей , никаких дорогостоящих проверок содержания и т. д.
ShadowRanger
1
@ShadowRanger, это будет работать только для элементарной разницы, которая является ключевым моментом,
префект
@fordprefect: Ага. Мой собственный ответ касается различий, не зависящих от позиции.
ShadowRanger 06
1

Я использовал два метода и обнаружил, что один из них полезнее другого. Вот мой ответ:

Мои входные данные:

crkmod_mpp = ['M13','M18','M19','M24']
testmod_mpp = ['M13','M14','M15','M16','M17','M18','M19','M20','M21','M22','M23','M24']

Метод 1: np.setdiff1dмне нравится этот подход по сравнению с другим, потому что он сохраняет позицию

test= list(np.setdiff1d(testmod_mpp,crkmod_mpp))
print(test)
['M15', 'M16', 'M22', 'M23', 'M20', 'M14', 'M17', 'M21']

Метод 2: Хотя он дает тот же ответ, что и метод 1, но нарушает порядок

test = list(set(testmod_mpp).difference(set(crkmod_mpp)))
print(test)
['POA23', 'POA15', 'POA17', 'POA16', 'POA22', 'POA18', 'POA24', 'POA21']

Method1 полностью np.setdiff1dсоответствует моим требованиям. Это ответ для информации.

Msquare
источник
0

Если нужно учитывать количество повторов, вам, вероятно, нужно использовать что-то вроде collections.Counter:

list_1=["a", "b", "c", "d", "e"]
list_2=["a", "f", "c", "m"] 
from collections import Counter
cnt1 = Counter(list_1)
cnt2 = Counter(list_2)
final = [key for key, counts in cnt2.items() if cnt1.get(key, 0) != counts]

>>> final
['f', 'm']

Как и было обещано, это также может обрабатывать различное количество вхождений как "разницу":

list_1=["a", "b", "c", "d", "e", 'a']
cnt1 = Counter(list_1)
cnt2 = Counter(list_2)
final = [key for key, counts in cnt2.items() if cnt1.get(key, 0) != counts]

>>> final
['a', 'f', 'm']
MSeifert
источник
-1

Из ser1 удалите элементы, присутствующие в ser2.

вход

ser1 = pd.Series ([1, 2, 3, 4, 5]) ser2 = pd.Series ([4, 5, 6, 7, 8])

Решение

ser1 [~ ser1.isin (SER2)]

Аднан
источник
Добро пожаловать в Stack Overflow. На этот вопрос есть еще восемь ответов, один из которых был принят исходным автором. Пожалуйста, опишите, как ваш ответ улучшает то, что уже было представлено.
chb