Как избавиться от повторов в списке Python без потери последовательности элементов

Раздел: Python -> Обучение Python

Практическая задача: удаление дубликатов из списка с сохранением порядка

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

В Python 3.7 и новее словари сохраняют порядок вставки. Поэтому самый лаконичный и быстрый способ - использовать dict.fromkeys(). Этот метод создаёт словарь, где ключами становятся элементы списка, а значения игнорируются. Порядок ключей сохраняется, а дубликаты автоматически отбрасываются. Результат преобразуется обратно в список.


original = [3, 1, 2, 1, 5, 3, 4, 2]
unique = list(dict.fromkeys(original))
print(unique)
  

Python set (тип set в python)

[3, 1, 2, 5, 4]
  

практические задания по python (практические задания по python)

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

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

Классический итеративный метод с множеством (set) для отслеживания уже встреченных элементов. Он гарантирует порядок и работает в любой версии Python. Сложность O(n).


original = [3, 1, 2, 1, 5, 3, 4, 2]
seen = set()
unique = []
for item in original:
    if item not in seen:
        seen.add(item)
        unique.append(item)
print(unique)
  

уроки python с заданиями (уроки python с заданиями)

[3, 1, 2, 5, 4]
  

Какое решение подходит для версий Python до 3.7?

Если нужна совместимость со старыми версиями (до 3.7), где порядок словарей не гарантируется, используется collections.OrderedDict.


from collections import OrderedDict
original = [3, 1, 2, 1, 5, 3, 4, 2]
unique = list(OrderedDict.fromkeys(original))
print(unique)
  
[3, 1, 2, 5, 4]
  

Как поступить, если элементы списка не являются хешируемыми (например, вложенные списки)?

Для нехешируемых объектов (списки, словари) нельзя использовать set или dict. Тогда применяется полный перебор с проверкой in для нового списка. Это медленнее (O(n^2)), но универсально.


original = [[1,2], [3,4], [1,2], [5]]
unique = []
for item in original:
    if item not in unique:
        unique.append(item)
print(unique)
  
[[1, 2], [3, 4], [5]]
  

Цели: выбор метода зависит от хешируемости данных, требуемой производительности и версии Python. Для больших списков хешируемых элементов предпочтительнее set или dict, для небольших или нехешируемых - цикл с проверкой in.

Типичные ошибки и их решения:

  • Потеря порядка при использовании set напрямую: list(set(original)). Решение: использовать методы, сохраняющие порядок.
  • Изменение списка во время итерации - не следует удалять элементы из исходного списка внутри цикла. Лучше создавать новый список.
  • Ошибка TypeError при попытке добавить нехешируемый объект в set. Решение: использовать проверку in unique или преобразовать объект к хешируемому типу (например, кортежу).
  • Медленная работа на больших списках при проверке in unique (O(n^2)). Решение: использовать вспомогательное множество для хешируемых элементов.

Как удалить дубликаты, используя pandas (для анализа данных)?

Если в проекте уже используется библиотека pandas, можно применить метод drop_duplicates(). Подходит для числовых массивов и Series.


import pandas as pd
original = [3, 1, 2, 1, 5, 3, 4, 2]
unique = pd.Series(original).drop_duplicates().tolist()
print(unique)
  
[3, 1, 2, 5, 4]
  

Цель: интеграция с пайплайнами обработки данных, когда списки являются частью DataFrame. Метод сохраняет порядок по умолчанию.

Продвинутые и нераспространённые методы удаления дубликатов

1. Использование itertools.groupby после сортировки по индексу

Сначала создаётся список кортежей (индекс, элемент), затем сортировка по элементу, группировка, выбор первого вхождения каждой группы и восстановление порядка по индексу. Метод не требует вспомогательной памяти, но сложность O(n log n) из-за сортировки.

Пример

from itertools import groupby

original = [3, 1, 2, 1, 5, 3, 4, 2]
indexed = list(enumerate(original))
indexed.sort(key=lambda x: x[1])
grouped = groupby(indexed, key=lambda x: x[1])
first_occurrences = [next(g) for _, g in grouped]
first_occurrences.sort(key=lambda x: x[0])
unique = [item for _, item in first_occurrences]
print(unique)
  
[3, 1, 2, 5, 4]
  

2. Использование numpy.unique с возвратом индексов

Для числовых массивов библиотека numpy предоставляет функцию unique с параметром return_index, которая возвращает индексы первых вхождений уникальных элементов. После сортировки этих индексов восстанавливается порядок.

Пример

import numpy as np

original = [3, 1, 2, 1, 5, 3, 4, 2]
_, indices = np.unique(original, return_index=True)
unique = [original[i] for i in sorted(indices)]
print(unique)
  
[3, 1, 2, 5, 4]
  

3. Рекурсивное удаление дубликатов из вложенных списков

Если список содержит вложенные структуры, можно написать рекурсивную функцию, которая обходит все уровни, сохраняя порядок, и удаляет дубликаты на каждом уровне. Для сравнения вложенных списков они временно преобразуются в кортежи (хешируемые).

Пример

def dedup_nested(lst):
    seen = set()
    result = []
    for item in lst:
        if isinstance(item, list):
            # рекурсивно обрабатываем вложенный список
            processed = dedup_nested(item)
            # хешируем как кортеж для сравнения
            key = tuple(processed)
            if key not in seen:
                seen.add(key)
                result.append(processed)
        else:
            if item not in seen:
                seen.add(item)
                result.append(item)
    return result

nested = [[1,2], [3,4], [1,2], [5, [1,2]], [5, [1,2]]]
print(dedup_nested(nested))
  
[[1, 2], [3, 4], [5, [1, 2]]]
  

4. Использование декоратора с мемоизацией для ускорения повторных вызовов

Можно создать декоратор, который кэширует результаты предыдущих вызовов. Применительно к удалению дубликатов: если один и тот же список обрабатывается несколько раз, декоратор вернёт сохранённый результат. Показано на примере функции dedup.

Пример

from functools import lru_cache

def dedup(lst):
    seen = set()
    result = []
    for item in lst:
        if item not in seen:
            seen.add(item)
            result.append(item)
    return result

# Оборачиваем в lru_cache (список должен быть преобразован в кортеж для хеширования)
@lru_cache(maxsize=None)
def cached_dedup(tpl):
    return tuple(dedup(list(tpl)))

original = (3, 1, 2, 1, 5, 3, 4, 2)
unique = list(cached_dedup(original))
print(unique)
  
[3, 1, 2, 5, 4]
  

5. Использование more_itertools.unique_everseen

Библиотека more-itertools содержит функцию unique_everseen, которая возвращает итератор, выдающий уникальные элементы в порядке их первого появления. Устанавливается через pip install more-itertools.

Пример

from more_itertools import unique_everseen

original = [3, 1, 2, 1, 5, 3, 4, 2]
unique = list(unique_everseen(original))
print(unique)
  
[3, 1, 2, 5, 4]
  

Практические задания по Python - comments

En
практические задания по python (python)