Перевод статьи «I Want to Remove Duplicates from a Python List • How Do I Do It?».
Итак, для начала нам нужен список — в идеале, с повторяющимися значениями. Допустим, у нас есть онлайн-очередь. Но некоторые люди вписывают свои имена в очередь более одного раза:
queue = ["James", "Kate", "Andy", "James", "Isabelle", "Kate"]
Обратите внимание, насколько Джеймс и Кейт хотели быть уверены, что они в очереди: они вписали свои имена дважды.
Удаление дубликатов: неэлегантный способ
Сначала я не хотел включать этот раздел, но, как видите, передумал. Можно придумать несколько алгоритмов для выполнения этой задачи «вручную». Это всего несколько строк кода. Вот один из вариантов:
queue = ["James", "Kate", "Andy", "James", "Isabelle", "Kate"]
queue_unique = []
for index, name in enumerate(queue):
if name not in queue[index + 1:]:
queue_unique.append(name)
queue_unique
# ['Andy', 'James', 'Isabelle', 'Kate']
У вас есть пустой список queue_unique, готовый для сбора уникальных имен. Затем вы выполняете итерацию с помощью enumerate() и добавляете имена в queue_unique, если они не появляются в остальной части исходного списка. Обратите внимание, что я использую срез в операторе if, чтобы выбирать значения списка от index + 1 до конца списка.
Позвольте показать вам еще один вариант. Я разберу результаты этих двух версий позже.
queue = ['James', 'Kate', 'Andy', 'James', 'Isabelle', 'Kate']
queue.reverse()
queue
# ['Kate', 'Isabelle', 'James', 'Andy', 'Kate', 'James']
queue_unique = queue.copy()
for index, name in enumerate(queue):
if name in queue[index + 1:]:
queue_unique.remove(name)
queue_unique.reverse()
queue_unique
# ['James', 'Kate', 'Andy', 'Isabelle']
На этот раз вы переворачиваете список, чтобы перебирать имена в обратном порядке. queue_unique на этот раз начинается не как пустой список, а как копия исходного перевернутого списка.
В цикле вы удаляете имена из queue_unique, если имя появляется позже в обратном списке. Напоминаем, что метод списка .remove() удаляет только первое вхождение элемента. Он не удаляет их все.
Оба алгоритма удаляют дубликаты. Отлично. Но сравните выводы. Разница между итоговыми списками дает подсказку о том, что будет дальше.
И P.S.: есть лучшие версии ручных алгоритмов, выполняющих ту же задачу, но суть первого раздела не в этом, так что давайте двигаться дальше!
Удаление дубликатов с помощью множества
Изучая структуры данных, вы знакомитесь с их ключевыми свойствами. Затем вы начинаете сравнивать структуры данных на основе этих свойств. Например, списки, словари, кортежи и строки являются итерируемыми объектами. Но списки и словари изменяемы, а кортежи и строки — нет. Списки, кортежи и строки являются последовательностями, а словари — нет, они являются отображениями.
Некоторые структуры данных обеспечивают уникальность, а другие — нет. Списки, например, могут содержать несколько одинаковых элементов — в приведенном выше примере в списке есть несколько строк, которые равны друг другу.
Но множества могут содержать только уникальные значения:
set([1, 2, 3, 4, 3, 2, 1])
# {1, 2, 3, 4}
Итак, самый простой способ удалить дубликаты из списка — преобразовать его в множество:
queue = ["James", "Kate", "Andy", "James", "Isabelle", "Kate"]
set(queue)
# {'Andy', 'James', 'Kate', 'Isabelle'}
Если вы предпочитаете, чтобы результатом по-прежнему был список, или хотите перезаписать исходное имя переменной, то можно написать так:
queue = list(set(queue)) queue # ['Andy', 'James', 'Kate', 'Isabelle']
Вот как просто! Намного лучше, чем несколько строк кода в предыдущем разделе.
Однако есть одна проблема. Если это очередь клиентов, то порядок, в котором они встали в очередь, довольно важен!
Обратите внимание, что новый список queue, не содержащий дубликатов, больше не сохраняет исходный порядок людей в нем. Джеймс был первым в очереди, но после удаления дубликатов Энди, похоже, переместился вперед.
Обратите внимание, что это также произошло с первым из «ручных» алгоритмов в предыдущем разделе.
Иногда порядок элементов в списке не имеет значения. В этом случае вы можете преобразовать список в множество, а затем обратно в список, чтобы удалить дубликаты.
Но иногда порядок важен. И он точно важен, когда речь идет об очереди клиентов. Давайте разберем другой вариант.
Удаление дубликатов с помощью словаря
Прежде чем продолжить, у меня к вам небольшой вопрос. Являются ли словари Python упорядоченными структурами данных?
Начиная с Python 3.7, порядок вставки элементов в словарь гарантированно сохраняется. Кроме того, ключи словаря должны быть уникальными — один и тот же ключ не может повторяться в словаре дважды.
Следовательно, если создать словарь из элементов в списке queue, дубликаты удалятся, а порядок сохранится. Для этого есть метод класса словаря:
queue = ["James", "Kate", "Andy", "James", "Isabelle", "Kate"]
dict.fromkeys(queue)
# {'James': None, 'Kate': None, 'Andy': None, 'Isabelle': None}
Вы создаете словарь из списка queue. Элементы в списке становятся ключами, и каждый ключ имеет значение по умолчанию None. Значение по умолчанию можно выбрать любое, но в данном случае это не нужно.
Итак, вы удалили дубликаты, сохранив порядок элементов, поскольку словари сохраняют порядок. Словарь создается путем итерации по списку, что объясняет, почему в этой версии сохраняется порядок элементов. Но вам не нужен словарь, и вас не интересуют значения в нем. Поэтому вы можете преобразовать его обратно в список. При преобразовании словаря в список сохраняются только ключи:
queue = list(dict.fromkeys(queue)) queue # ['James', 'Kate', 'Andy', 'Isabelle']
Теперь вы удалили дубликаты из списка и сохранили исходный порядок, преобразовав список в словарь, а затем обратно в список.
Это просто, если вы знаете эту идиому.
Ограничение
И множество, и словарь имеют важное ограничение. Элементы множества должны быть хэшируемыми объектами. Ключи в словаре также должны быть хэшируемыми. Поэтому вы не можете использовать эти методы для списка, содержащего нехэшируемые объекты. Например, для списка, который содержит другие списки.
Заключительные слова
Возможно, вам понадобится удалить дубликаты из списка в Python. Не пишите свой собственный алгоритм. Жизнь слишком коротка для этого.
Если вам не важен порядок элементов в списке, преобразуйте список в множество, а затем обратно в список: list(set(queue)).
Если порядок важен, создайте словарь из списка с помощью dict.fromkeys(), а затем преобразуйте его обратно в список: list(dict.fromkeys(queue)).
Кроме того, методы удаления дубликатов с помощью множества и словаря более эффективны, чем описанные выше ручные методы.

