Разберите следующий фрагмент на Python и опишите, что он делает, в каких случаях он работает неправильно или неэффективно, и как вы бы его улучшили для больших массивов данных: def remove_duplicates(arr): res = []\nfor i in arr:\n if i not in res:\n res.append(i)\nreturn res
Что делает фрагмент def remove_duplicatesarrarrarr: res =
for i in arr: if i not in res: res.appendiii
return res
Возвращает список элементов из arr без повторов, сохраняя порядок первого появления first−seenorderfirst-seen orderfirst−seenorder.Проверка "i not in res" использует сравнение по равенству ======.
Проблемы и случаи, когда работает плохо или неправильно
Медленно для больших массивов O(n2)O(n^2)O(n2):
Операция "i not in res" для списка — линейная, поэтому при n элементах получается приблизительно сумма 1..n → On2n^2n2 по времени.Память — Onnn в res, но это ожидаемо.
Поведение с не-хэшируемыми элементами:
Текущий код работает и с не-хэшируемыми элементами например,спискаминапример, спискаминапример,списками, потому что использует список для проверки. Это плюс, но при больших объёмах всё равно медленно.
Специальные случаи равенства:
Некоторые значения например,NaNнапример, NaNнапример,NaN ведут себя нетривиально: float′nan′'nan'′nan′ != float′nan′'nan'′nan′, это может привести к тому, что «повторы» NaN останутся как несколько элементов.Объекты с переопределённым eq / hash могут давать неожиданные результаты при использовании различных подходов set/dictvsлинейныйпоискset/dict vs линейный поискset/dictvsлинейныйпоиск.
Как улучшить вариантыипримерварианты и примервариантыипример
1) Самый простой и быстрый для хэшируемых элементов сохранениепорядкасохранение порядкасохранениепорядка:
В Python 3.7+ можно сделать: listdict.fromkeys(arr)dict.fromkeys(arr)dict.fromkeys(arr)
это Onnn по времени всреднемв среднемвсреднем, Onnn по памяти. Очень компактно и быстро. Требует, чтобы элементы были хэшируемы.
2) Общий оптимальный подход с учётом смешанных типов хэшируемые+не−хэшируемыехэшируемые + не-хэшируемыехэшируемые+не−хэшируемые:
Используем множество для хэшируемых элементов и отдельный список для не-хэшируемых fallbackfallbackfallback. Это даёт Onnn в среднем, а квадратичность остаётся только для не-хэшируемых элементов. Пример: def remove_duplicatesarrarrarr: seen_hashable = set
seen_unhashable =
res =
for x in arr: try: if x not in seen_hashable: seen_hashable.addxxx
res.appendxxx
except TypeError: x не хэшируем — fallback if x not in seen_unhashable: seen_unhashable.appendxxx
res.appendxxx
return res
3) Если порядок не важен:
Просто setarrarrarr — Onnn среднее, но порядок теряется.Или: sortedset(arr)set(arr)set(arr) — Onlognn log nnlogn и возвращает упорядоченные элементы.
4) Если данные — Numpy / Pandas:
Для numpy: numpy.uniquearrarrarrестьопцияreturnindexдлясохраненияпорядкапервыхпоявленийесть опция return_index для сохранения порядка первых появленийестьопцияreturnindexдлясохраненияпорядкапервыхпоявлений.Для pandas: pd.Seriesarrarrarr.drop_duplicates — оптимизировано для больших наборов.
5) Для очень больших данных непомещаютсявпамятьне помещаются в памятьнепомещаютсявпамять:
Исходный код: On2n^2n2 время, Onnn память.dict.fromkeys / set-based: Onnn среднее время, Onnn память требуетхэшируемыхэлементовтребует хэшируемых элементовтребуетхэшируемыхэлементов.Сортировка + groupby: Onlognn log nnlogn время, возможно меньше памяти при внешней сортировке.
Резюме и рекомендация
Для обычных списков хэшируемых объектов используйте listdict.fromkeys(arr)dict.fromkeys(arr)dict.fromkeys(arr) или set-подход с seen — это простой и быстрый вариант.Если встречаются не-хэшируемые объекты — используйте комбинированный подход set+fallbackнасписокset + fallback на списокset+fallbackнасписок.Для очень больших данных применяйте инструменты уровня numpy/pandas, внешнюю сортировку или базу данных; при необходимости приближённого результата рассмотрите bloom filter.
Что делает фрагмент
Возвращает список элементов из arr без повторов, сохраняя порядок первого появления first−seenorderfirst-seen orderfirst−seenorder.Проверка "i not in res" использует сравнение по равенству ======.def remove_duplicatesarrarrarr:
res = for i in arr:
if i not in res:
res.appendiii return res
Проблемы и случаи, когда работает плохо или неправильно
Медленно для больших массивов O(n2)O(n^2)O(n2):
Операция "i not in res" для списка — линейная, поэтому при n элементах получается приблизительно сумма 1..n → On2n^2n2 по времени.Память — Onnn в res, но это ожидаемо.Поведение с не-хэшируемыми элементами:
Текущий код работает и с не-хэшируемыми элементами например,спискаминапример, спискаминапример,списками, потому что использует список для проверки. Это плюс, но при больших объёмах всё равно медленно.Специальные случаи равенства:
Некоторые значения например,NaNнапример, NaNнапример,NaN ведут себя нетривиально: float′nan′'nan'′nan′ != float′nan′'nan'′nan′, это может привести к тому, что «повторы» NaN останутся как несколько элементов.Объекты с переопределённым eq / hash могут давать неожиданные результаты при использовании различных подходов set/dictvsлинейныйпоискset/dict vs линейный поискset/dictvsлинейныйпоиск.Как улучшить вариантыипримерварианты и примервариантыипример
1) Самый простой и быстрый для хэшируемых элементов сохранениепорядкасохранение порядкасохранениепорядка:
В Python 3.7+ можно сделать:listdict.fromkeys(arr)dict.fromkeys(arr)dict.fromkeys(arr) это Onnn по времени всреднемв среднемвсреднем, Onnn по памяти. Очень компактно и быстро. Требует, чтобы элементы были хэшируемы.
2) Общий оптимальный подход с учётом смешанных типов хэшируемые+не−хэшируемыехэшируемые + не-хэшируемыехэшируемые+не−хэшируемые:
Используем множество для хэшируемых элементов и отдельный список для не-хэшируемых fallbackfallbackfallback. Это даёт Onnn в среднем, а квадратичность остаётся только для не-хэшируемых элементов.Пример:
def remove_duplicatesarrarrarr:
seen_hashable = set seen_unhashable = res = for x in arr:
try:
if x not in seen_hashable:
seen_hashable.addxxx res.appendxxx except TypeError:
x не хэшируем — fallback if x not in seen_unhashable:
seen_unhashable.appendxxx res.appendxxx
return res
3) Если порядок не важен:
Просто setarrarrarr — Onnn среднее, но порядок теряется.Или: sortedset(arr)set(arr)set(arr) — Onlognn log nnlogn и возвращает упорядоченные элементы.4) Если данные — Numpy / Pandas:
Для numpy: numpy.uniquearrarrarr естьопцияreturnindexдлясохраненияпорядкапервыхпоявленийесть опция return_index для сохранения порядка первых появленийестьопцияreturni ndexдлясохраненияпорядкапервыхпоявлений.Для pandas: pd.Seriesarrarrarr.drop_duplicates — оптимизировано для больших наборов.5) Для очень больших данных непомещаютсявпамятьне помещаются в памятьнепомещаютсявпамять:
Варианты:Внешняя сортировка externalsortexternalsortexternalsort + удаление последовательных дублей.Использование СУБД INSERTIGNORE/UNIQUE−индексINSERT IGNORE / UNIQUE-индексINSERTIGNORE/UNIQUE−индекс.Bloom filter для приближённой дедупликации экономияпамяти,новозможныложныесрабатыванияэкономия памяти, но возможны ложные срабатыванияэкономияпамяти,новозможныложныесрабатывания.
Кратко по сложностям
Исходный код: On2n^2n2 время, Onnn память.dict.fromkeys / set-based: Onnn среднее время, Onnn память требуетхэшируемыхэлементовтребует хэшируемых элементовтребуетхэшируемыхэлементов.Сортировка + groupby: Onlognn log nnlogn время, возможно меньше памяти при внешней сортировке.Резюме и рекомендация
Для обычных списков хэшируемых объектов используйте listdict.fromkeys(arr)dict.fromkeys(arr)dict.fromkeys(arr) или set-подход с seen — это простой и быстрый вариант.Если встречаются не-хэшируемые объекты — используйте комбинированный подход set+fallbackнасписокset + fallback на списокset+fallbackнасписок.Для очень больших данных применяйте инструменты уровня numpy/pandas, внешнюю сортировку или базу данных; при необходимости приближённого результата рассмотрите bloom filter.