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

772

Есть ли встроенная функция, которая удаляет дубликаты из списка в Python, сохраняя при этом порядок? Я знаю, что могу использовать набор для удаления дубликатов, но это разрушает первоначальный порядок. Я также знаю, что я могу катиться так:

def uniq(input):
  output = []
  for x in input:
    if x not in output:
      output.append(x)
  return output

(Спасибо, что раскрутите этот пример кода .)

Но я хотел бы воспользоваться встроенной или более Pythonic идиом, если это возможно.

Смежный вопрос: Каков самый быстрый алгоритм удаления дубликатов из списка в Python, чтобы все элементы были уникальными при сохранении порядка ?

Джош Гловер
источник

Ответы:

763

Здесь у вас есть несколько альтернатив: http://www.peterbe.com/plog/uniqifiers-benchmark

Самый быстрый:

def f7(seq):
    seen = set()
    seen_add = seen.add
    return [x for x in seq if not (x in seen or seen_add(x))]

Почему правопреемником seen.addк seen_addвместо того , чтобы просто позвонив seen.add? Python - это динамический язык, и разрешение seen.addкаждой итерации обходится дороже, чем разрешение локальной переменной.seen.addмог измениться между итерациями, и среда выполнения не достаточно умна, чтобы исключить это. Чтобы не рисковать, он должен каждый раз проверять объект.

Если вы планируете много использовать эту функцию в одном и том же наборе данных, возможно, вам лучше заказать упорядоченный набор: http://code.activestate.com/recipes/528878/

O (1) вставка, удаление и проверка члена для каждой операции.

(Небольшое дополнительное примечание: seen.add()всегда возвращается None, поэтому orвышеприведенное приведено только для попытки обновления набора, а не как неотъемлемая часть логического теста.)

Маркус Жардеро
источник
20
@JesseDhillon seen.addмог меняться между итерациями, и среда выполнения недостаточно умна, чтобы исключить это. Чтобы играть безопасно, он должен проверять объект каждый раз. - Если вы посмотрите на байт-код с dis.dis(f), вы увидите, что он выполняется LOAD_ATTRдля addчлена на каждой итерации. ideone.com/tz1Tll
Маркус Жардерот
5
Когда я пытаюсь сделать это в списке списков, я получаю: TypeError: unhashable type: 'list'
Jens Timmerman
7
Ваше решение не самое быстрое. В Python 3 (не тестировал 2) это быстрее (список записей 300 тыс. - 0,045 с (ваш) против 0,035 с (этот): seen = set (); вернуть [x для x в строках, если x не видно и нет) seen.add (x)]. Я не смог найти какой-либо эффект скорости для линии seen_add, которую вы сделали.
user136036
3
@ user136036 Пожалуйста, дайте ссылку на ваши тесты. Сколько раз ты их запускал? seen_addэто улучшение, но системные ресурсы могут влиять на время. Было бы интересно увидеть полный тайминги
jamylak
2
Любой, кто пишет код на Python, должен подумать дважды, прежде чем жертвовать удобочитаемостью и общепринятыми соглашениями Python, чтобы выжать еще несколько наносекунд на цикл. Тестирование с и без seen_add = seen.addдает только 1% увеличение скорости. Это вряд ли существенно.
sleblanc
344

Редактировать 2016

Как отметил Рэймонд , в python 3.5+, где OrderedDictреализован на C, подход к пониманию списка будет медленнее, чем OrderedDict(если вам не нужен список в конце - и даже тогда, только если ввод очень короткий). Так что лучшее решение для 3.5+ это OrderedDict.

Важно Редактировать 2015

Как отмечает @abarnert , more_itertoolsбиблиотека ( pip install more_itertools) содержит unique_everseenфункцию, которая создана для решения этой проблемы без каких-либо нечитаемых ( not seen.add) мутаций в списках. Это также самое быстрое решение:

>>> from  more_itertools import unique_everseen
>>> items = [1, 2, 0, 1, 3, 2]
>>> list(unique_everseen(items))
[1, 2, 0, 3]

Всего одна простая библиотека импорта и никаких хаков. Это происходит от реализации рецепта itertools, unique_everseenкоторый выглядит следующим образом:

def unique_everseen(iterable, key=None):
    "List unique elements, preserving order. Remember all elements ever seen."
    # unique_everseen('AAAABBBCCDAABBB') --> A B C D
    # unique_everseen('ABBCcAD', str.lower) --> A B C D
    seen = set()
    seen_add = seen.add
    if key is None:
        for element in filterfalse(seen.__contains__, iterable):
            seen_add(element)
            yield element
    else:
        for element in iterable:
            k = key(element)
            if k not in seen:
                seen_add(k)
                yield element

В Python общепринятой общая идиома (который работает , но не оптимизирован для скорости, я бы сейчас использовать ) для этого применения :2.7+unique_everseencollections.OrderedDict

Время выполнения: O (N)

>>> from collections import OrderedDict
>>> items = [1, 2, 0, 1, 3, 2]
>>> list(OrderedDict.fromkeys(items))
[1, 2, 0, 3]

Это выглядит намного лучше, чем:

seen = set()
[x for x in seq if x not in seen and not seen.add(x)]

и не использует уродливый хак :

not seen.add(x)

который опирается на тот факт, что set.addэто метод на месте, который всегда возвращает, Noneтак что not Noneоценивает True.

Однако обратите внимание, что решение для взлома быстрее в необработанной скорости, хотя оно имеет ту же сложность во время выполнения O (N).

jamylak
источник
5
Преобразование в какой-то пользовательский тип, чтобы просто взять ключи? Просто еще один костыль.
Накилон
3
@Nakilon Я действительно не понимаю, как это костыль. Он не подвергает изменчивому состоянию, поэтому в этом смысле он очень чистый. Внутренне наборы Python реализованы с помощью dict () ( stackoverflow.com/questions/3949310/… ), поэтому в основном вы просто делаете то, что интерпретатор сделал бы в любом случае.
Имран
Просто используйте побочные эффекты и делайте [seen.add(x) for x in seq if x not in seen], или, если вам не нравятся побочные эффекты понимания, просто используйте forцикл: for x in seq: seen.add(x) if x not in seen else None(все еще однострочное, хотя в этом случае я думаю, что однострочное - это глупое свойство пытаться иметь в решение
Ely
@ EMS Это не сохраняет порядок. Вы могли бы так же хорошо сделать seen = set(seq).
землетрясение
1
@CommuSoft Я согласен, хотя практически это почти всегда O (n) из-за крайне маловероятного наихудшего случая
jamylak
110

В Python 2.7 новый способ удаления дубликатов из итерируемого при сохранении его в исходном порядке:

>>> from collections import OrderedDict
>>> list(OrderedDict.fromkeys('abracadabra'))
['a', 'b', 'r', 'c', 'd']

В Python 3.5 OrderedDict имеет реализацию на языке C. Мои данные показывают, что сейчас это самый быстрый и самый короткий из различных подходов для Python 3.5.

В Python 3.6 обычный dict стал упорядоченным и компактным. (Эта функция поддерживается для CPython и PyPy, но может отсутствовать в других реализациях). Это дает нам новый самый быстрый способ дедупликации при сохранении порядка:

>>> list(dict.fromkeys('abracadabra'))
['a', 'b', 'r', 'c', 'd']

В Python 3.7 регулярный dict гарантированно упорядочен во всех реализациях. Итак, самое короткое и быстрое решение:

>>> list(dict.fromkeys('abracadabra'))
['a', 'b', 'r', 'c', 'd']

Реакция на @max: как только вы перейдете на 3.6 или 3.7 и будете использовать обычный dict вместо OrderedDict , вы не сможете по-настоящему побить производительность другим способом. Словарь плотный и легко преобразуется в список практически без накладных расходов. Целевой список предварительно масштабируется до len (d), который сохраняет все изменения, которые происходят при понимании списка. Кроме того, поскольку внутренний список ключей плотный, копирование указателей происходит почти так же быстро, как копирование списка.

Раймонд Хеттингер
источник
Это быстрее, чем любой другой подход на моей машине (python 3.5), пока я не преобразую OrderedDictв список в конце. Если мне нужно преобразовать его в список, для небольших входных данных подход к пониманию списка все еще быстрее, до 1,5 раз. Тем не менее, это решение намного чище.
максимум
7
Единственный недостаток в том, что итерируемые «элементы» должны быть хешируемыми - было бы неплохо иметь эквивалент для итерируемых элементов с произвольными элементами (в виде списка списков)
Mr_and_Mrs_D
Итерация порядка вставки по dict обеспечивает функциональность, которая обслуживает больше вариантов использования, чем удаление дубликатов. Например, научный анализ опирается на воспроизводимые вычисления, которые не поддерживает недетерминированная итеративная диктовка. Воспроизводимость является основной целью компьютерного научного моделирования, поэтому мы приветствуем эту новую функцию. Хотя я знаю, что строить с детерминистским требованием тривиально, но высокопроизводительный детерминист set()поможет более наивным пользователям разрабатывать воспроизводимые коды.
Артур
41
sequence = ['1', '2', '3', '3', '6', '4', '5', '6']
unique = []
[unique.append(item) for item in sequence if item not in unique]

уникальный → ['1', '2', '3', '6', '4', '5']

dansalmo
источник
28
Стоит отметить, что это работаетn^2
goncalopp
25
Ик. 2 удара: использование списка для тестирования членства (медленно, O (N)) и использование понимания списка для побочных эффектов (создание другого списка Noneссылок в процессе!)
Мартин Питерс
1
Я согласен с @MartijnPieters, что нет абсолютно никаких причин для понимания списка с побочными эффектами. Просто используйте forвместо этого петлю
jamylak
31

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

import pandas as pd

my_list = [0, 1, 2, 3, 4, 1, 2, 3, 5]

>>> pd.Series(my_list).drop_duplicates().tolist()
# Output:
# [0, 1, 2, 3, 4, 5]
Александр
источник
27
from itertools import groupby
[ key for key,_ in groupby(sortedList)]

Список даже не нужно сортировать , достаточным условием является то, что равные значения сгруппированы вместе.

Изменить: я предположил, что «сохранение порядка» подразумевает, что список на самом деле упорядочен. Если это не так, то решение от MizardX является правильным.

Редактирование сообщества: это, однако, самый элегантный способ «сжать повторяющиеся последовательные элементы в один элемент».

Рафал Доугирд
источник
1
Но это не сохраняет порядок!
1
Хм, это проблематично, так как я не могу гарантировать, что равные значения сгруппированы без циклического повторения по списку, и к тому времени я мог бы удалить дубликаты.
Джош Гловер
Я предположил, что «сохранение порядка» подразумевает, что список на самом деле упорядочен.
Рафал Доугирд
1
Возможно, спецификация входного списка немного неясна. Значения даже не нужно группировать вместе: [2, 1, 3, 1]. Итак, какие значения оставить, а какие удалить?
1
@igorkf Игнорирование второго элемента из пары.
Рафал
24

Я думаю, если вы хотите поддерживать порядок,

Вы можете попробовать это:

list1 = ['b','c','d','b','c','a','a']    
list2 = list(set(list1))    
list2.sort(key=list1.index)    
print list2

ИЛИ аналогично вы можете сделать это:

list1 = ['b','c','d','b','c','a','a']  
list2 = sorted(set(list1),key=list1.index)  
print list2 

Вы также можете сделать это:

list1 = ['b','c','d','b','c','a','a']    
list2 = []    
for i in list1:    
    if not i in list2:  
        list2.append(i)`    
print list2

Это также можно записать так:

list1 = ['b','c','d','b','c','a','a']    
list2 = []    
[list2.append(i) for i in list1 if not i in list2]    
print list2 
трилистник
источник
3
Первые два ответа предполагают, что порядок списка можно восстановить с помощью функции сортировки, но это может быть не так.
Ричард
5
Большинство ответов сосредоточены на производительности. Для списков, которые недостаточно велики, чтобы беспокоиться о производительности, сортировка (set (list1), key = list1.index) - лучшее, что я видел. Никакого дополнительного импорта, никакой дополнительной функции, никакой дополнительной переменной, и это довольно просто и читабельно.
Дерек Вейт
23

В Python 3.7 и выше словари гарантированно запоминают порядок вставки ключей. Ответ на этот вопрос обобщает текущее состояние дел.

Таким OrderedDictобразом, решение становится устаревшим, и без каких-либо операторов импорта мы можем просто выдать:

>>> lst = [1, 2, 1, 3, 3, 2, 4]
>>> list(dict.fromkeys(lst))
[1, 2, 3, 4]
timgeb
источник
12

Еще один очень поздний ответ на еще один очень старый вопрос:

У itertoolsрецептов есть функция, которая делает это, используя seenзаданную технику, но:

  • Обрабатывает стандартную keyфункцию.
  • Не использует непристойных хаков.
  • Оптимизирует цикл путем предварительной привязки, seen.addа не ищет его N раз. ( f7также делает это, но некоторые версии не делают.)
  • Оптимизирует цикл с помощью ifilterfalse, так что вам нужно только перебирать уникальные элементы в Python, а не все из них. ( ifilterfalseКонечно, вы все еще перебираете все из них внутри , но это в Си, и намного быстрее.)

Это на самом деле быстрее, чем f7? Это зависит от ваших данных, поэтому вам придется проверить это и посмотреть. Если вам нужен список в конце, f7используйте listcomp, и здесь нет способа сделать это. (Вы можете использовать напрямую appendвместо yielding, или вы можете listподать генератор в функцию, но ни один из них не может быть таким же быстрым, как LIST_APPEND внутри listcomp.) Во всяком случае, обычно выжимание нескольких микросекунд не будет таким важно иметь легкую для понимания, повторно используемую, уже написанную функцию, которая не требует DSU, когда вы хотите украсить.

Как и во всех рецептах, он также доступен в more-iterools .

Если вам нужен только no- keycase, вы можете упростить его как:

def unique(iterable):
    seen = set()
    seen_add = seen.add
    for element in itertools.ifilterfalse(seen.__contains__, iterable):
        seen_add(element)
        yield element
abarnert
источник
Я полностью упустил из виду more-itertoolsэто явно лучший ответ. Простой, from more_itertools import unique_everseen list(unique_everseen(items))гораздо более быстрый подход, чем мой, и намного лучше, чем принятый ответ. Думаю, загрузка библиотеки того стоит. Я собираюсь в сообщество вики мой ответ и добавить это.
jamylak
12

Просто чтобы добавить еще один (очень производительную) реализации такой функциональности от внешнего модуля 1 : iteration_utilities.unique_everseen:

>>> from iteration_utilities import unique_everseen
>>> lst = [1,1,1,2,3,2,2,2,1,3,4]

>>> list(unique_everseen(lst))
[1, 2, 3, 4]

Задержки

Я сделал несколько таймингов (Python 3.6), и они показывают, что это быстрее, чем все другие альтернативы, которые я тестировал, включая OrderedDict.fromkeys, f7и more_itertools.unique_everseen:

%matplotlib notebook

from iteration_utilities import unique_everseen
from collections import OrderedDict
from more_itertools import unique_everseen as mi_unique_everseen

def f7(seq):
    seen = set()
    seen_add = seen.add
    return [x for x in seq if not (x in seen or seen_add(x))]

def iteration_utilities_unique_everseen(seq):
    return list(unique_everseen(seq))

def more_itertools_unique_everseen(seq):
    return list(mi_unique_everseen(seq))

def odict(seq):
    return list(OrderedDict.fromkeys(seq))

from simple_benchmark import benchmark

b = benchmark([f7, iteration_utilities_unique_everseen, more_itertools_unique_everseen, odict],
              {2**i: list(range(2**i)) for i in range(1, 20)},
              'list size (no duplicates)')
b.plot()

введите описание изображения здесь

И просто чтобы убедиться, что я также провел тест с большим количеством дубликатов, просто чтобы проверить, имеет ли это значение:

import random

b = benchmark([f7, iteration_utilities_unique_everseen, more_itertools_unique_everseen, odict],
              {2**i: [random.randint(0, 2**(i-1)) for _ in range(2**i)] for i in range(1, 20)},
              'list size (lots of duplicates)')
b.plot()

введите описание изображения здесь

И тот, который содержит только одно значение:

b = benchmark([f7, iteration_utilities_unique_everseen, more_itertools_unique_everseen, odict],
              {2**i: [1]*(2**i) for i in range(1, 20)},
              'list size (only duplicates)')
b.plot()

введите описание изображения здесь

Во всех этих случаях iteration_utilities.unique_everseenфункция самая быстрая (на моем компьютере).


Эта iteration_utilities.unique_everseenфункция также может обрабатывать непредсказуемые значения во входных данных (однако с O(n*n)производительностью вместо O(n)производительности, когда значения могут быть хэшируемыми).

>>> lst = [{1}, {1}, {2}, {1}, {3}]

>>> list(unique_everseen(lst))
[{1}, {2}, {3}]

1 Отказ от ответственности: я автор этого пакета.

MSeifert
источник
Я не понимаю необходимости этой строки: seen_add = seen.add- это нужно для оценки?
Алекс
@ Алекс Это подход, данный в этом ответе . Было бы более разумно спросить это там. Я просто использовал подход из этого ответа, чтобы сравнить время.
Мейферт
Можете ли вы добавить dict.fromkeys()метод в свой график, пожалуйста?
Борис
Я не совсем уверен, могу ли я сделать то же самое в ближайшее время. Как вы думаете, это намного быстрее, чем ordereddict.fromkeys?
Майферт
«Эта функция iteration_utilities.unique_everseen также может обрабатывать непредсказуемые значения во входных данных» - да, это действительно важно. Если у вас есть список диктов диктов и т. Д., Это единственный способ выполнить работу, даже в небольшом масштабе.
Роко Мижич
6

Для отсутствующих типов (например, списков), основанных на MizardX:

def f7_noHash(seq)
    seen = set()
    return [ x for x in seq if str( x ) not in seen and not seen.add( str( x ) )]
ZMK
источник
3

Заимствование рекурсивной идеи, используемой при определении nubфункции Хаскелла для списков, это будет рекурсивный подход:

def unique(lst):
    return [] if lst==[] else [lst[0]] + unique(filter(lambda x: x!= lst[0], lst[1:]))

например:

In [118]: unique([1,5,1,1,4,3,4])
Out[118]: [1, 5, 4, 3]

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

In [122]: %timeit unique(np.random.randint(5, size=(1)))
10000 loops, best of 3: 25.3 us per loop

In [123]: %timeit unique(np.random.randint(5, size=(10)))
10000 loops, best of 3: 42.9 us per loop

In [124]: %timeit unique(np.random.randint(5, size=(100)))
10000 loops, best of 3: 132 us per loop

In [125]: %timeit unique(np.random.randint(5, size=(1000)))
1000 loops, best of 3: 1.05 ms per loop

In [126]: %timeit unique(np.random.randint(5, size=(10000)))
100 loops, best of 3: 11 ms per loop

Я также думаю, что интересно, что это может быть легко обобщено другими операциями. Нравится:

import operator
def unique(lst, cmp_op=operator.ne):
    return [] if lst==[] else [lst[0]] + unique(filter(lambda x: cmp_op(x, lst[0]), lst[1:]), cmp_op)

Например, вы можете передать функцию, которая использует понятие округления до того же целого числа, как если бы оно было «равенством» в целях уникальности, например так:

def test_round(x,y):
    return round(x) != round(y)

тогда unique (some_list, test_round) предоставил бы уникальные элементы списка, где уникальность больше не означала традиционное равенство (что подразумевается при использовании любого вида подхода на основе множеств или основанных на дикт-ключах к этой проблеме), а вместо этого означала принять только первый элемент, который округляется до K для каждого возможного целого числа K, к которому элементы могут округляться, например:

In [6]: unique([1.2, 5, 1.9, 1.1, 4.2, 3, 4.8], test_round)
Out[6]: [1.2, 5, 1.9, 4.2, 3]
Ely
источник
1
Обратите внимание, что производительность будет ухудшаться, когда количество уникальных элементов очень велико по отношению к общему количеству элементов, поскольку использование каждого последующего рекурсивного вызова filterедва ли выиграет от предыдущего вызова вообще. Но если количество уникальных элементов мало по сравнению с размером массива, это должно работать довольно хорошо.
Ely
3

В 5 раз быстрее уменьшить вариант, но более сложный

>>> l = [5, 6, 6, 1, 1, 2, 2, 3, 4]
>>> reduce(lambda r, v: v in r[1] and r or (r[0].append(v) or r[1].add(v)) or r, l, ([], set()))[0]
[5, 6, 1, 2, 3, 4]

Объяснение:

default = (list(), set())
# use list to keep order
# use set to make lookup faster

def reducer(result, item):
    if item not in result[1]:
        result[0].append(item)
        result[1].add(item)
    return result

>>> reduce(reducer, l, default)[0]
[5, 6, 1, 2, 3, 4]
Сергей М Никитин
источник
3

Вы можете ссылаться на понимание списка, поскольку оно строится символом '_ [1]'.
Например, следующая функция unique-ifies список элементов без изменения их порядка путем ссылки на его понимание списка.

def unique(my_list): 
    return [x for x in my_list if x not in locals()['_[1]']]

Демо-версия:

l1 = [1, 2, 3, 4, 1, 2, 3, 4, 5]
l2 = [x for x in l1 if x not in locals()['_[1]']]
print l2

Вывод:

[1, 2, 3, 4, 5]
Чжифэн Ху
источник
2
Также обратите внимание, что это сделало бы это операцией O (n ^ 2), где создание набора / dict (с постоянным временем поиска) и добавление только ранее невидимых элементов будет линейным.
Ely
Это Python 2.6 только я верю. И да, это O (N ^ 2)
jamylak
2

Ответ MizardX дает хорошую коллекцию нескольких подходов.

Вот что я придумал, думая вслух:

mylist = [x for i,x in enumerate(mylist) if x not in mylist[i+1:]]
Саураб Хирани
источник
Ваше решение хорошо, но оно принимает последний вид каждого элемента. Для первого появления используйте: [x для i, x в перечислении (mylist), если x не в mylist [: i]]
Rivka
7
Поскольку поиск в списке - это O(n)операция, и вы выполняете ее для каждого элемента, сложность вашего решения будет такой O(n^2). Это просто неприемлемо для такой тривиальной проблемы.
Никита Волков
2

Вот простой способ сделать это:

list1 = ["hello", " ", "w", "o", "r", "l", "d"]
sorted(set(list1 ), key=lambda x:list1.index(x))

что дает вывод:

["hello", " ", "w", "o", "r", "l", "d"]
Ahmed4end
источник
1

Вы могли бы сделать что-то вроде уродливого взлома понимания списка.

[l[i] for i in range(len(l)) if l.index(l[i]) == i]

источник
Предпочитают i,e in enumerate(l)в l[i] for i in range(len(l)).
Евпок
1

Относительно эффективный подход с _sorted_через numpyмассивы:

b = np.array([1,3,3, 8, 12, 12,12])    
numpy.hstack([b[0], [x[0] for x in zip(b[1:], b[:-1]) if x[0]!=x[1]]])

Выходы:

array([ 1,  3,  8, 12])
dominecf
источник
1
l = [1,2,2,3,3,...]
n = []
n.extend(ele for ele in l if ele not in set(n))

Выражение генератора, которое использует поиск O (1) набора, чтобы определить, следует ли включать элемент в новый список.

kylie.a
источник
1
Умное использование extendвыражения с генератором, которое зависит от расширяемой вещи (т. Е. +1), но set(n)пересчитывается на каждой стадии (которая является линейной), что подрывает общий подход к квадратичности. На самом деле, это почти наверняка хуже, чем просто использование ele in n. Создание набора для одного теста членства не стоит затрат на создание набора. Все же - это интересный подход.
Джон Колман
1

Простое рекурсивное решение:

def uniquefy_list(a):
    return uniquefy_list(a[1:]) if a[0] in a[1:] else [a[0]]+uniquefy_list(a[1:]) if len(a)>1 else [a[0]]
Илья Прокин
источник
1

Исключая дублирующиеся значения в последовательности, но сохраняйте порядок оставшихся элементов. Использование функции генератора общего назначения.

# for hashable sequence
def remove_duplicates(items):
    seen = set()
    for item in items:
        if item not in seen:
            yield item
            seen.add(item)

a = [1, 5, 2, 1, 9, 1, 5, 10]
list(remove_duplicates(a))
# [1, 5, 2, 9, 10]



# for unhashable sequence
def remove_duplicates(items, key=None):
    seen = set()
    for item in items:
        val = item if key is None else key(item)
        if val not in seen:
            yield item
            seen.add(val)

a = [ {'x': 1, 'y': 2}, {'x': 1, 'y': 3}, {'x': 1, 'y': 2}, {'x': 2, 'y': 4}]
list(remove_duplicates(a, key=lambda d: (d['x'],d['y'])))
# [{'x': 1, 'y': 2}, {'x': 1, 'y': 3}, {'x': 2, 'y': 4}]
Шривастава
источник
1

Пользователи панд должны проверить pandas.unique.

>>> import pandas as pd
>>> lst = [1, 2, 1, 3, 3, 2, 4]
>>> pd.unique(lst)
array([1, 2, 3, 4])

Функция возвращает массив NumPy. При необходимости вы можете преобразовать его в список с помощью tolistметода.

timgeb
источник
1
Хороший. Я бы никогда не подумал использовать панды для этого, но это работает
seralouk
0

Если вам нужен один лайнер, то, возможно, это поможет:

reduce(lambda x, y: x + y if y[0] not in x else x, map(lambda x: [x],lst))

... должно работать, но поправьте меня, если я ошибаюсь

code22
источник
это условное выражение, так что это хорошо
code22
0

Если вы регулярно используете pandas, а эстетика предпочтительнее производительности, рассмотрите встроенную функцию pandas.Series.drop_duplicates:

    import pandas as pd
    import numpy as np

    uniquifier = lambda alist: pd.Series(alist).drop_duplicates().tolist()

    # from the chosen answer 
    def f7(seq):
        seen = set()
        seen_add = seen.add
        return [ x for x in seq if not (x in seen or seen_add(x))]

    alist = np.random.randint(low=0, high=1000, size=10000).tolist()

    print uniquifier(alist) == f7(alist)  # True

Сроки:

    In [104]: %timeit f7(alist)
    1000 loops, best of 3: 1.3 ms per loop
    In [110]: %timeit uniquifier(alist)
    100 loops, best of 3: 4.39 ms per loop
лей
источник
0

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

def deduplicate(l):
    count = {}
    (read,write) = (0,0)
    while read < len(l):
        if l[read] in count:
            read += 1
            continue
        count[l[read]] = True
        l[write] = l[read]
        read += 1
        write += 1
    return l[0:write]
Сохам Джоши
источник
0

Решение без использования импортированных модулей или наборов:

text = "ask not what your country can do for you ask what you can do for your country"
sentence = text.split(" ")
noduplicates = [(sentence[i]) for i in range (0,len(sentence)) if sentence[i] not in sentence[:i]]
print(noduplicates)

Дает вывод:

['ask', 'not', 'what', 'your', 'country', 'can', 'do', 'for', 'you']
Роб Мюррей
источник
это O (N ** 2) сложность + разрезание списка каждый раз.
Жан-Франсуа Фабр
0

Метод на месте

Этот метод является квадратичным, потому что у нас есть линейный поиск в списке для каждого элемента списка (к этому мы должны добавить стоимость переупорядочения списка из-за del s).

Тем не менее, можно работать на месте, если мы начнем с конца списка и перейдем к источнику, удаляя каждый термин, который присутствует в подсписке слева

Эта идея в коде просто

for i in range(len(l)-1,0,-1): 
    if l[i] in l[:i]: del l[i] 

Простой тест реализации

In [91]: from random import randint, seed                                                                                            
In [92]: seed('20080808') ; l = [randint(1,6) for _ in range(12)] # Beijing Olympics                                                                 
In [93]: for i in range(len(l)-1,0,-1): 
    ...:     print(l) 
    ...:     print(i, l[i], l[:i], end='') 
    ...:     if l[i] in l[:i]: 
    ...:          print( ': remove', l[i]) 
    ...:          del l[i] 
    ...:     else: 
    ...:          print() 
    ...: print(l)
[6, 5, 1, 4, 6, 1, 6, 2, 2, 4, 5, 2]
11 2 [6, 5, 1, 4, 6, 1, 6, 2, 2, 4, 5]: remove 2
[6, 5, 1, 4, 6, 1, 6, 2, 2, 4, 5]
10 5 [6, 5, 1, 4, 6, 1, 6, 2, 2, 4]: remove 5
[6, 5, 1, 4, 6, 1, 6, 2, 2, 4]
9 4 [6, 5, 1, 4, 6, 1, 6, 2, 2]: remove 4
[6, 5, 1, 4, 6, 1, 6, 2, 2]
8 2 [6, 5, 1, 4, 6, 1, 6, 2]: remove 2
[6, 5, 1, 4, 6, 1, 6, 2]
7 2 [6, 5, 1, 4, 6, 1, 6]
[6, 5, 1, 4, 6, 1, 6, 2]
6 6 [6, 5, 1, 4, 6, 1]: remove 6
[6, 5, 1, 4, 6, 1, 2]
5 1 [6, 5, 1, 4, 6]: remove 1
[6, 5, 1, 4, 6, 2]
4 6 [6, 5, 1, 4]: remove 6
[6, 5, 1, 4, 2]
3 4 [6, 5, 1]
[6, 5, 1, 4, 2]
2 1 [6, 5]
[6, 5, 1, 4, 2]
1 5 [6]
[6, 5, 1, 4, 2]

In [94]:                                                                                                                             
gboffi
источник
Перед публикацией я искал в теле ответов «место» безрезультатно. Если другие решили проблему аналогичным образом, пожалуйста, предупредите меня, и я удалю свой ответ как можно скорее.
Гбоффи
Вы могли бы просто использовать, l[:] = <one of the the faster methods>если вы хотите операцию на месте, нет?
тимгеб
@timgeb Да и нет… Когда я делаю, a=[1]; b=a; a[:]=[2]тогда b==[2]значение равно, Trueи мы можем сказать, что мы делаем это на месте, тем не менее, вы предлагаете использовать новое пространство, чтобы получить новый список, заменить старые данные новыми данными и отметить старые данные для сборки мусора, потому что на них больше ничего не ссылаются, так что если говорить о том, что они работают на месте, это немного расширяет концепцию относительно того, что, как я показал, возможно ... это неэффективно? да, но я сказал это заранее.
ГБОБФИ
0

Подход zmk использует понимание списка, которое очень быстро, но сохраняет порядок естественным образом. Для применения к чувствительным к регистру строкам его можно легко модифицировать. Это также сохраняет оригинальный случай.

def DelDupes(aseq) :
    seen = set()
    return [x for x in aseq if (x.lower() not in seen) and (not seen.add(x.lower()))]

Тесно связанные функции:

def HasDupes(aseq) :
    s = set()
    return any(((x.lower() in s) or s.add(x.lower())) for x in aseq)

def GetDupes(aseq) :
    s = set()
    return set(x for x in aseq if ((x.lower() in s) or s.add(x.lower())))
Хьюи Дьюи
источник
0

Одно понимание списка лайнеров:

values_non_duplicated = [value for index, value in enumerate(values) if value not in values[ : index]]

Просто добавьте условие, чтобы проверить, что значение не на предыдущей позиции

Jože Ws
источник