Объясните поведение и асимптотику следующей функции на Python, найдите её недостатки и предложите как минимум два разных способа улучшения по времени и/или памяти, сохранив порядок элементов: def remove_dups(lst): res = [] for x in lst: if x not in res: res.append(x) return res
def remove_dupslstlstlst: res =
for x in lst: if x not in res: res.appendxxx
return res
Что она делает
Возвращает новый список, содержащий первые вхождения элементов из lst, т.е. удаляет повторяющиеся элементы, сохраняя порядок появления stable,keep−firststable, keep-firststable,keep−first.
Асимптотика и недостатки
Время: On2n^2n2 в худшем и типичном случае. Причина: проверка "x not in res" — поиск по списку занимает Okkk, где k — текущий размер res; в среднем суммарно получается ~1 + 2 + ... + u = Ou2u^2u2 ≤ On2n^2n2u—числоуникальных,n—длинаисходногоu — число уникальных, n — длина исходногоu—числоуникальных,n—длинаисходного.Память: Ouuu дополнительной памяти для результата res u≤nu ≤ nu≤n.Другие недостатки: Очень медленная на больших списках.Если элементы хешируемы, есть более быстрые варианты — текущее решение их не использует.Работает с любыми включаянепереставляемыевключая непереставляемыевключаянепереставляемые элементами, но это преимущество ценой производительности.
Как минимум два улучшения ссохранениемпорядкас сохранением порядкассохранениемпорядка:
1) Использовать множество "seen" для проверки вхождений быстрое;требуетхешируемыхэлементовбыстрое; требует хешируемых элементовбыстрое;требуетхешируемыхэлементов
Код: def remove_dups_hashablelstlstlst: seen = set
res =
for x in lst: if x not in seen: res.appendxxx
seen.addxxx
return res
Асимптотика:
Время: Onnn в среднем поамортизированнойстоимостихеш−операцийпо амортизированной стоимости хеш-операцийпоамортизированнойстоимостихеш−операций. В редких творческих атакующих сценах или при множестве коллизий можно получить деградацию до On2n^2n2, но на практике — Onnn.Память: Ouuu для seen + Ouuu для res.
Плюсы: очень быстро; простой код. Минусы: элементы должны быть хешируемы например,несписки/словарянапример, не списки/словарянапример,несписки/словаря. Использует дополнительный set.
2) Использовать dict.fromkeys короткоиэффективно,используетхешированиекоротко и эффективно, использует хешированиекороткоиэффективно,используетхеширование
Код Python3.7+гарантируетсохранениепорядкаключейPython 3.7+ гарантирует сохранение порядка ключейPython3.7+гарантируетсохранениепорядкаключей: def remove_dups_dictlstlstlst: return listdict.fromkeys(lst)dict.fromkeys(lst)dict.fromkeys(lst)
Асимптотика:
Время: Onnn в среднем.Память: Ouuu для словаря + итоговый список Ouuu.
Плюсы: минималистичный один вызов, быстро. Минусы: требует хешируемых элементов.
3) In-place вариант с двумя указателями уменьшаетдополнительнуюпамятьдлярезультирующегоспискауменьшает дополнительную память для результирующего спискауменьшаетдополнительнуюпамятьдлярезультирующегосписка
Если хотите не выделять новый список res экономияпамятинаодномспискеэкономия памяти на одном спискеэкономияпамятинаодномсписке, но при этом всё ещё пользоваться set для быстрого поиска: def remove_dups_inplacelstlstlst: seen = set
write = 0 for read in rangelen(lst)len(lst)len(lst): x = lstreadreadread
if x not in seen: seen.addxxx
lstwritewritewrite = x write += 1
удалить хвостdel lstwrite:write:write:
return lst
Асимптотика:
Время: Onnn в среднем.Память: Ouuu для seen; нет отдельного списка res памятьнарезультат—висходномспискепамять на результат — в исходном спискепамятьнарезультат—висходномсписке.
Плюсы: экономия памяти при большом исходном списке несоздаёмвторойсписоктакогожеразмеране создаём второй список такого же размеранесоздаёмвторойсписоктакогожеразмера. Минусы: модифицирует входной список; требует хешируемых элементов.
4) Для непереставляемых/нехешируемых элементов Если элементы могут быть не хешируемыми например,спискинапример, спискинапример,списки и нужно сохранять именно их неприводитькдругомутипуне приводить к другому типунеприводитькдругомутипу, варианты:
Оставить On2n^2n2 решение вашеначальноеваше начальноевашеначальное — корректно, но медленно.
Преобразовать элементы в представление, которое можно хешировать например,tuple(x)длясписковнапример, tuple(x) для списковнапример,tuple(x)длясписков и использовать seen для этих ключей, при этом добавлять в res оригинальные объекты: def remove_dups_by_reprlstlstlst: seen = set
res =
for x in lst: key = reprxxx # или tuplexxx / json.dumpsxxx — в зависимости от случая if key not in seen: seen.addkeykeykey
res.appendxxx
return res Внимание: repr/json/serializing могут быть медленными и потенциально небезопасными/рискованными коллизии,разныеобъектысодинаковымrepr,чувствительностькпорядкуключейвсловаряхит.п.коллизии, разные объекты с одинаковым repr, чувствительность к порядку ключей в словарях и т.п.коллизии,разныеобъектысодинаковымrepr,чувствительностькпорядкуключейвсловаряхит.п.
Альтернатива: использовать OrderedDict и попытаться вставлять ключи, но всё равно ключи должны быть хешируемыми.
5) Для особых доменов данных целыевузкомдиапазонецелые в узком диапазонецелыевузкомдиапазоне
Если значения — целые из известного относительно небольшого диапазона, можно применять битовые таблицы/массивы булевых флагов для уменьшения памяти и ускорения до Onnn с меньшими накладными расходами, чем set.
Резюме и рекомендации
Если элементы хешируемы чащевсеготакчаще всего такчащевсеготак: используйте seen = set вместе с res вариант1вариант 1вариант1 или удобный dict.fromkeys вариант2вариант 2вариант2. Это даёт Onnn время и простоту.Если нужно экономить память на новом списке — используйте in-place алгоритм вариант3вариант 3вариант3.Если элементы не хешируемы и нельзя конвертировать безопасно, остаётся On2n^2n2 вариант либо предварительная сериализация в хешируемую форму соговоркамис оговоркамисоговорками.Избегайте сортировки потеряпорядкапотеря порядкапотеряпорядка и вероятностных структур bloomfilterbloom filterbloomfilter если необходима точная детерминированная фильтрация без ложных положительных результатов.
Если хотите, могу привести готовые оптимальные реализации с тестами и примерами для ваших конкретных типов данных.
Функция, которую вы привели:
def remove_dupslstlstlst:
res = for x in lst:
if x not in res:
res.appendxxx return res
Что она делает
Возвращает новый список, содержащий первые вхождения элементов из lst, т.е. удаляет повторяющиеся элементы, сохраняя порядок появления stable,keep−firststable, keep-firststable,keep−first.Асимптотика и недостатки
Время: On2n^2n2 в худшем и типичном случае. Причина: проверка "x not in res" — поиск по списку занимает Okkk, где k — текущий размер res; в среднем суммарно получается ~1 + 2 + ... + u = Ou2u^2u2 ≤ On2n^2n2 u—числоуникальных,n—длинаисходногоu — число уникальных, n — длина исходногоu—числоуникальных,n—длинаисходного.Память: Ouuu дополнительной памяти для результата res u≤nu ≤ nu≤n.Другие недостатки:Очень медленная на больших списках.Если элементы хешируемы, есть более быстрые варианты — текущее решение их не использует.Работает с любыми включаянепереставляемыевключая непереставляемыевключаянепереставляемые элементами, но это преимущество ценой производительности.
Как минимум два улучшения ссохранениемпорядкас сохранением порядкассохранениемпорядка:
1) Использовать множество "seen" для проверки вхождений быстрое;требуетхешируемыхэлементовбыстрое; требует хешируемых элементовбыстрое;требуетхешируемыхэлементов Код:
def remove_dups_hashablelstlstlst:
seen = set res = for x in lst:
if x not in seen:
res.appendxxx seen.addxxx return res
Асимптотика:
Время: Onnn в среднем поамортизированнойстоимостихеш−операцийпо амортизированной стоимости хеш-операцийпоамортизированнойстоимостихеш−операций. В редких творческих атакующих сценах или при множестве коллизий можно получить деградацию до On2n^2n2, но на практике — Onnn.Память: Ouuu для seen + Ouuu для res.Плюсы: очень быстро; простой код.
Минусы: элементы должны быть хешируемы например,несписки/словарянапример, не списки/словарянапример,несписки/словаря. Использует дополнительный set.
2) Использовать dict.fromkeys короткоиэффективно,используетхешированиекоротко и эффективно, использует хешированиекороткоиэффективно,используетхеширование Код Python3.7+гарантируетсохранениепорядкаключейPython 3.7+ гарантирует сохранение порядка ключейPython3.7+гарантируетсохранениепорядкаключей:
def remove_dups_dictlstlstlst:
return listdict.fromkeys(lst)dict.fromkeys(lst)dict.fromkeys(lst)
Асимптотика:
Время: Onnn в среднем.Память: Ouuu для словаря + итоговый список Ouuu.Плюсы: минималистичный один вызов, быстро.
Минусы: требует хешируемых элементов.
3) In-place вариант с двумя указателями уменьшаетдополнительнуюпамятьдлярезультирующегоспискауменьшает дополнительную память для результирующего спискауменьшаетдополнительнуюпамятьдлярезультирующегосписка Если хотите не выделять новый список res экономияпамятинаодномспискеэкономия памяти на одном спискеэкономияпамятинаодномсписке, но при этом всё ещё пользоваться set для быстрого поиска:
удалить хвостdel lstwrite:write:write: return lstdef remove_dups_inplacelstlstlst:
seen = set write = 0
for read in rangelen(lst)len(lst)len(lst):
x = lstreadreadread if x not in seen:
seen.addxxx lstwritewritewrite = x
write += 1
Асимптотика:
Время: Onnn в среднем.Память: Ouuu для seen; нет отдельного списка res памятьнарезультат—висходномспискепамять на результат — в исходном спискепамятьнарезультат—висходномсписке.Плюсы: экономия памяти при большом исходном списке несоздаёмвторойсписоктакогожеразмеране создаём второй список такого же размеранесоздаёмвторойсписоктакогожеразмера.
Минусы: модифицирует входной список; требует хешируемых элементов.
4) Для непереставляемых/нехешируемых элементов
Если элементы могут быть не хешируемыми например,спискинапример, спискинапример,списки и нужно сохранять именно их неприводитькдругомутипуне приводить к другому типунеприводитькдругомутипу, варианты:
Оставить On2n^2n2 решение вашеначальноеваше начальноевашеначальное — корректно, но медленно.
Преобразовать элементы в представление, которое можно хешировать например,tuple(x)длясписковнапример, tuple(x) для списковнапример,tuple(x)длясписков и использовать seen для этих ключей, при этом добавлять в res оригинальные объекты:
def remove_dups_by_reprlstlstlst:
seen = set res = for x in lst:
key = reprxxx # или tuplexxx / json.dumpsxxx — в зависимости от случая
if key not in seen:
seen.addkeykeykey res.appendxxx return res
Внимание: repr/json/serializing могут быть медленными и потенциально небезопасными/рискованными коллизии,разныеобъектысодинаковымrepr,чувствительностькпорядкуключейвсловаряхит.п.коллизии, разные объекты с одинаковым repr, чувствительность к порядку ключей в словарях и т.п.коллизии,разныеобъектысодинаковымrepr,чувствительностькпорядкуключейвсловаряхит.п.
Альтернатива: использовать OrderedDict и попытаться вставлять ключи, но всё равно ключи должны быть хешируемыми.
5) Для особых доменов данных целыевузкомдиапазонецелые в узком диапазонецелыевузкомдиапазоне Если значения — целые из известного относительно небольшого диапазона, можно применять битовые таблицы/массивы булевых флагов для уменьшения памяти и ускорения до Onnn с меньшими накладными расходами, чем set.
Резюме и рекомендации
Если элементы хешируемы чащевсеготакчаще всего такчащевсеготак: используйте seen = set вместе с res вариант1вариант 1вариант1 или удобный dict.fromkeys вариант2вариант 2вариант2. Это даёт Onnn время и простоту.Если нужно экономить память на новом списке — используйте in-place алгоритм вариант3вариант 3вариант3.Если элементы не хешируемы и нельзя конвертировать безопасно, остаётся On2n^2n2 вариант либо предварительная сериализация в хешируемую форму соговоркамис оговоркамисоговорками.Избегайте сортировки потеряпорядкапотеря порядкапотеряпорядка и вероятностных структур bloomfilterbloom filterbloomfilter если необходима точная детерминированная фильтрация без ложных положительных результатов.Если хотите, могу привести готовые оптимальные реализации с тестами и примерами для ваших конкретных типов данных.