Перейти к содержанию

412 уроков, 25 библиотек и челлендж «Что выведет код?» — бесплатно, код прямо в браузере

Начать обучение
Урок 16 из 20 Начальный 15 мин 100 XP

Как удалить дубликаты из списка в Python одной строкой

Убрать повторы из списка — две строки: set(x) выкидывает дубликаты, dict.fromkeys(x) сохраняет порядок первого вхождения. Разбираем оба приёма и случай, когда множеству нужен ваш ключ.

Редакция Питоники

Список с повторами приходит почти из каждого источника: журнал визитов содержит имя гостя по записи на каждый день, лог кликов — URL на каждый щелчок, корзина — товар на каждую добавку. Вопрос «сколько всего разных» и задача «покажи без повторов» решаются одним и тем же ходом: превратить список во множество из урока 1 — оно по определению хранит только уникальные элементы. У приёма есть один подвох, о который спотыкается каждый второй: порядок. Разберём оба способа — быстрый и аккуратный — и случаи, когда множеству нужен ваш собственный ключ.

set(x): дубликаты исчезают одной строкой

set() от списка из урока 2 отбрасывает повторы молча и мгновенно. Проверим на списке, где каждый элемент встречается дважды:

множество от списка с повторами
nums = [3, 1, 3, 2, 1, 2]

unique = set(nums)
print('уникальных:', len(unique))
print(sorted(unique))
print(sorted(list(set(nums))))
Вывод
уникальных: 3
[1, 2, 3]
[1, 2, 3]

Шесть элементов превратились в три, и вторая строка — уже встречавшаяся идиома list(set(x)): множество отфильтровало дубликаты, list вернул список. Обратите внимание на печать: и в одном, и в другом случае вывод идёт через sorted. Множество не хранит порядок — печатать его напрямую нельзя, результаты будут разными от запуска к запуску, а sorted даёт стабильный отсортированный список. Для чисел это удобный порядок; для строк — алфавитный.

dict.fromkeys(x): дубликаты с сохранением порядка

Когда порядок первого вхождения важен — гости в журнале, страницы в маршруте, — на помощь приходит словарь. dict.fromkeys(x) строит словарь, у которого ключами служат элементы списка, а значения пустые. Ключи в словаре помнят порядок вставки, а дубликат просто не создаёт вторую пару:

два способа рядом
logs = ['Анна', 'Борис', 'Анна', 'Вера', 'Борис']

print('уникальных имён:', len(set(logs)))
print('dict.fromkeys сохраняет порядок:', list(dict.fromkeys(logs)))
Вывод
уникальных имён: 3
dict.fromkeys сохраняет порядок: ['Анна', 'Борис', 'Вера']

dict.fromkeys сохраняет порядок первого вхождения: словарь помнит порядок вставки ключей, а дубликат не создаёт вторую пару. Анна попала в результат от первой записи, Борис — от второй, Вера — от четвёртой. Обёртка list возвращает список ключей — теперь его можно печатать напрямую: порядок у списка гарантирован.

fromkeys со значением по умолчанию
seen_days = dict.fromkeys(['пн', 'вт'], 0)

print(seen_days)
seen_days['пн'] += 1
print(list(seen_days), seen_days['пн'])
Вывод
{'пн': 0, 'вт': 0}
['пн', 'вт'] 1

Второй аргумент fromkeys раздаёт всем ключам начальное значение: словарь виденных дней с нулями — готовый каркас счётчика, и порядок дней при этом сохранён. Изменяемое значение по умолчанию не кладут: у всех ключей окажется один и тот же объект, и правка одного коснётся остальных.

Откуда у словаря такое право — хранить порядок, — а у множества нет, и почему namedtuple и кортежи годятся в ключи, а списки — нет, разобрано в уроке про общие механизмы множеств и словарей. Пока достаточно практики: нужен порядок — fromkeys, порядок не нужен — set и sorted.

ПриёмПорядок результатаЧто печатать
sorted(set(x))СортированныйНапрямую
list(set(x))Не гарантированТолько через sorted
list(dict.fromkeys(x))Первое вхождениеНапрямую
len(set(x))—Число уникальных

Своё правило равенства: ручной цикл с памятью

Оба приёма сравнивают элементы через ==, а это не всегда то равенство, которое нужно. Теги Python и python — для множества разные строки, хотя человек видит один тег. Когда равенство своё, порядок важен и хочется одного прохода, работает ручной цикл: множество служит памятью о том, что уже видели:

дедупликация без учёта регистра
tags = ['Python', 'python', 'PYTHON', 'книги', 'Книги']

seen = set()
unique_tags = []
for tag in tags:
    key = tag.lower()
    if key not in seen:
        seen.add(key)
        unique_tags.append(tag)

print(unique_tags)
print(len(tags), '->', len(unique_tags))
Вывод
['Python', 'книги']
5 -> 2

Множество seen хранит не сами теги, а их нижние версии — ключ сравнения. Первое вхождение осталось в списке в исходном регистре: Python и книги, без перемен. Проверка in на множестве стоит O(1) из урока 5, поэтому весь проход — O(n): на списке в миллион элементов цикл не притормозит.

Только посчитать: len(set(x))

Часто список дубликатов не нужен вовсе — нужен ответ на вопрос «сколько разных». Тогда ни sorted, ни fromkeys не нужны:

число уникальных без создания списка
answers = ['да', 'нет', 'да', 'не знаю', 'нет', 'да']

print('ответов:', len(answers))
print('разных ответов:', len(set(answers)))
print('есть повтор:', len(answers) != len(set(answers)))
Вывод
ответов: 6
разных ответов: 3
есть повтор: True

Последняя строка — готовый детектор повторов: длины не совпали, значит хотя бы один элемент встречался дважды. Приём стоит держать в голове при валидации: список номеров заказов с повторами — почти всегда баг источника.

сумма без повторов
purchases = [5, 7, 5, 7, 7]

print('чек с повторами:', sum(purchases))
print('уникальные товары:', sorted(set(purchases)))
print('сумма без повторов:', sum(set(purchases)))
Вывод
чек с повторами: 31
уникальные товары: [5, 7]
сумма без повторов: 12

Множество работает с любыми функциями, которые умеют перебирать коллекцию: sum, min, max, len. Пять покупок свелись к двум уникальным товарам, и сумма по множеству отвечает на вопрос «сколько стоит один комплект из купленного». Считать поверх set не только короче, но и безопаснее перебора с флажками.

Дубликаты кортежей и предел приёма

Элементы могут быть и составными: пары координат, записи. Кортежи из урока 11 хешируемы, поэтому оба приёма работают с ними без изменений:

дедупликация кортежей
points = [(1, 2), (3, 4), (1, 2), (5, 6)]

print(list(dict.fromkeys(points)))
print(sorted(set(points)))
Вывод
[(1, 2), (3, 4), (5, 6)]
[(1, 2), (3, 4), (5, 6)]
списки превращаются в кортежи
rows = [[1, 2], [3, 4], [1, 2]]

as_tuples = []
for r in rows:
    as_tuples.append(tuple(r))

print(sorted(set(as_tuples)))
Вывод
[(1, 2), (3, 4)]

Что дальше

Два приёма урока закрывают девять из десяти житейских задач на повторы. В следующем уроке заглянем под капот: почему и множество, и словарь проверяют принадлежность мгновенно и почему элементы обоих проходят один экзамен на хешируемость. А урок с задачами на множества потренирует уникальных гостей, общих друзей и первый неповторяющийся элемент — на этих же приёмах.

Итог урока умещается в таблицу из двух строк: set(x) — когда порядок безразличен, dict.fromkeys(x) — когда важен порядок первого вхождения. Всё остальное — вопрос того, что считать равным, и это правило вы задаёте сами.

Что выведет код?

Сначала предскажи ответ в голове — это главный навык программиста.

words = ['б', 'а', 'б', 'в', 'а']
print(list(dict.fromkeys(words)))
print(len(set(words)))
logs = ['x', 'y', 'x', 'z', 'y', 'x']
print('визитов:', len(logs))
print('гостей:', len(set(logs)))
d = dict.fromkeys([1, 2, 1, 3])
print(len(d))
print(list(d))
Проверь себя
0 / 7

1. Почему list(set(x)) нельзя печатать напрямую, если важен порядок элементов?

2. Как удалить дубликаты из списка, сохранив порядок первого вхождения?

3. Что вернёт list(dict.fromkeys(['Анна', 'Борис', 'Анна', 'Вера']))?

4. Как быстро узнать, есть ли в списке повторяющиеся элементы?

5. set([[1, 2], [1, 2]]) — что произойдёт?

6. Зачем в ручном цикле дедупликации нужен set seen, если есть список unique_tags?

7. set([1, True, 1.0]) — сколько элементов в множестве?

Карточки терминов
Запомнено: 0 / 7
Практика

Дан список дней визитов: ['пн', 'вт', 'пн', 'ср', 'вт', 'пн']. Напечатайте список дней без повторов в порядке первого визита (через dict.fromkeys) и строку «уникальных дней: N».

practice.py
Вопросы и ответы по уроку

Как удалить дубликаты из списка в Python?

Одной строкой: list(set(список)). Множество отфильтрует повторы, list вернёт список. Порядок элементов при этом не сохраняется: печатайте sorted(set(x)), если нужен стабильный вывод, или используйте dict.fromkeys для порядка первого вхождения.

Урок 2: создание множеств

Как убрать дубликаты, сохранив исходный порядок элементов?

list(dict.fromkeys(список)): словарь строит ключи в порядке первого вхождения и не создаёт вторую пару для дубликата, а list возвращает список ключей. Для своего правила равенства — например, без учёта регистра — подойдёт ручной цикл с множеством-памятью.

Почему после list(set(x)) порядок элементов отличается от исходного?

Потому что множество не хранит порядок вставки: элементы раскладываются по хешам. Для маленьких чисел порядок может случайно совпасть с исходным, полагаться на это нельзя. Стабильные варианты — sorted(set(x)) или list(dict.fromkeys(x)).

Можно ли дедуплицировать список списков?

Напрямую нет: set([[1, 2], [1, 2]]) падает с TypeError: unhashable type: 'list'. Превратите элементы в кортежи — set(tuple(p) for p in points) — или дедуплицируйте по одному полю через словарь, если составной элемент целиком не нужен.

Урок 17: hash — общий механизм

Как посчитать только число уникальных элементов?

len(set(список)) — без создания промежуточного списка. Сравнение len(x) != len(set(x)) заодно отвечает на вопрос, есть ли в списке повторы вообще.

Понравился урок? Сошлитесь на него

«dict.fromkeys сохраняет порядок первого вхождения: словарь помнит порядок вставки ключей, а дубликат не создаёт вторую пару.»

Скопируйте готовую ссылку в формате HTML, Markdown или чистый адрес и вставьте в статью на Habr, VC, Telegram-канал или свой блог — так о проекте узнают новые читатели.

TelegramVK

Что дальше

Осталось в разделе: 20 из 20. Прогресс хранится в вашем браузере.

Похожие уроки по темам

Подобраны автоматически по пересечению тем и ключевых слов.