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

Например:

>>> x = [1, 1, 2, 'a', 'a', 3]
>>> unique(x)
[1, 2, 'a', 3]

Предположим, элементы списка могут быть хэшируемыми.

Пояснение: результат должен сохранить первый дубликат в списке. Например, [1, 2, 3, 2, 3, 1] становится [1, 2, 3].

27 ответов

def unique(items):
    found = set([])
    keep = []

    for item in items:
        if item not in found:
            found.add(item)
            keep.append(item)

    return keep

print unique([1, 1, 2, 'a', 'a', 3])

С помощью:

lst = [8, 8, 9, 9, 7, 15, 15, 2, 20, 13, 2, 24, 6, 11, 7, 12, 4, 10, 18, 13, 23, 11, 3, 11, 12, 10, 4, 5, 4, 22, 6, 3, 19, 14, 21, 11, 1, 5, 14, 8, 0, 1, 16, 5, 10, 13, 17, 1, 16, 17, 12, 6, 10, 0, 3, 9, 9, 3, 7, 7, 6, 6, 7, 5, 14, 18, 12, 19, 2, 8, 9, 0, 8, 4, 5]

И используя модуль timeit:

$ python -m timeit -s 'import uniquetest' 'uniquetest.etchasketch(uniquetest.lst)'

И так далее для различных других функций (которые я назвал в честь их плакатов), у меня есть следующие результаты (на моем первом MacBook Pro первого поколения):

Allen:                  14.6 µs per loop [1]
Terhorst:               26.6 µs per loop
Tarle:                  44.7 µs per loop
ctcherry:               44.8 µs per loop
Etchasketch 1 (short):  64.6 µs per loop
Schinckel:              65.0 µs per loop
Etchasketch 2:          71.6 µs per loop
Little:                 89.4 µs per loop
Tyler:                 179.0 µs per loop

[1] Обратите внимание, что Аллен изменяет список на месте - я считаю, что это исказило время, так как timeit Модуль запускает код 100000 раз, и 99999 из них имеют список без дублирования.


Резюме: прямолинейная реализация с наборами выигрывает у запутанных однострочников:-)

Вот самое быстрое решение на данный момент (для следующего ввода):

def del_dups(seq):
    seen = {}
    pos = 0
    for item in seq:
        if item not in seen:
            seen[item] = True
            seq[pos] = item
            pos += 1
    del seq[pos:]

lst = [8, 8, 9, 9, 7, 15, 15, 2, 20, 13, 2, 24, 6, 11, 7, 12, 4, 10, 18, 
       13, 23, 11, 3, 11, 12, 10, 4, 5, 4, 22, 6, 3, 19, 14, 21, 11, 1, 
       5, 14, 8, 0, 1, 16, 5, 10, 13, 17, 1, 16, 17, 12, 6, 10, 0, 3, 9, 
       9, 3, 7, 7, 6, 6, 7, 5, 14, 18, 12, 19, 2, 8, 9, 0, 8, 4, 5]
del_dups(lst)
print(lst)
# -> [8, 9, 7, 15, 2, 20, 13, 24, 6, 11, 12, 4, 10, 18, 23, 3, 5, 22, 19, 14, 
#     21, 1, 0, 16, 17]

Поиск в словаре немного быстрее, чем поиск в Python 3.

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

Вот один для изменения списка на месте:

def unique(items):
  seen = set()
  for i in xrange(len(items)-1, -1, -1):
    it = items[i]
    if it in seen:
      del items[i]
    else:
      seen.add(it)

Итерация в обратном направлении по индексам гарантирует, что удаление элементов не повлияет на итерацию.

Это самый быстрый метод на месте, который я нашел (при условии большой доли дубликатов):

def unique(l):
    s = set(); n = 0
    for x in l:
        if x not in s: s.add(x); l[n] = x; n += 1
    del l[n:]

Это на 10% быстрее, чем реализация Аллена, на которой он основан (приурочен к timeit.repeat, JIT, скомпилированному psyco). Он сохраняет первый экземпляр любого дубликата.

repton-infinity: Мне было бы интересно, если бы вы могли подтвердить мои сроки.

Это может быть самый простой способ:

list(OrderedDict.fromkeys(iterable))

Начиная с Python 3.5, OrderedDict теперь реализован на C, поэтому он стал самым коротким, чистым и быстрым.

Обязательный генераторный вариант:

def unique(seq):
  seen = set()
  for x in seq:
    if x not in seen:
      seen.add(x)
      yield x

Один лайнер:

new_list = reduce(lambda x,y: x+[y][:1-int(y in x)], my_list, [])

Взято с http://www.peterbe.com/plog/uniqifiers-benchmark

def f5(seq, idfun=None):  
    # order preserving 
    if idfun is None: 
        def idfun(x): return x 
    seen = {} 
    result = [] 
    for item in seq: 
        marker = idfun(item) 
        # in old Python versions: 
        # if seen.has_key(marker) 
        # but in new ones: 
        if marker in seen: continue 
        seen[marker] = 1 
        result.append(item) 
    return result

Это самый быстрый способ, сравнивающий все материалы из этого длительного обсуждения и других ответов, приведенных здесь, со ссылкой на этот тест. Это еще на 25% быстрее, чем самая быстрая функция из обсуждения, f8, Спасибо Дэвиду Кирби за идею.

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

Некоторое время сравнения:

$ python uniqifiers_benchmark.py 
* f8_original 3.76
* uniquify 3.0
* terhorst 5.44
* terhorst_localref 4.08
* del_dups 4.76

Однострочник для этого:

>>> x = [1, 1, 2, 'a', 'a', 3]
>>> [ item for pos,item in enumerate(x) if x.index(item)==pos ]
[1, 2, 'a', 3]

Вы можете сделать что-то действительно классное в Python, чтобы решить эту проблему. Вы можете создать понимание списка, которое будет ссылаться на себя в процессе построения. Следующее:

   # remove duplicates...
   def unique(my_list):
       return [x for x in my_list if x not in locals()['_[1]'].__self__]

Редактировать: я удалил "себя", и он работает на Mac OS X, Python 2.5.1.

_[1] - это "секретная" ссылка Python на новый список. Выше, конечно, немного грязно, но вы можете адаптировать его под свои нужды по мере необходимости. Например, вы можете написать функцию, которая возвращает ссылку на понимание; это было бы больше похоже на:

return [x for x in my_list if x not in this_list()]

Удалить дубликаты и сохранить порядок:

Это быстрый 2-х строчный режим, использующий встроенную функциональность списков и диктов.

x = [1, 1, 2, 'a', 'a', 3]

tmpUniq = {} # temp variable used below 
results = [tmpUniq.setdefault(i,i) for i in x if i not in tmpUniq]

print results
[1, 2, 'a', 3]

Функция dict.setdefaults() возвращает значение, а также добавляет его к temp dict непосредственно в понимании списка. Использование встроенных функций и хэшей dict будет работать для максимизации эффективности процесса.

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

>>> x  = []
>>> y = set()
>>> def add_to_x(val):
...     if val not in y:
...             x.append(val)
...             y.add(val)
...     print x
...     print y
... 
>>> add_to_x(1)
[1]
set([1])
>>> add_to_x(1)
[1]
set([1])
>>> add_to_x(1)
[1]
set([1])
>>> 

has_key в python - это O(1). Вставка и извлечение из хеша также O(1). Зацикливает n элементов дважды, поэтому O(n).

def unique(list):
  s = {}
  output = []
  for x in list:
    count = 1
    if(s.has_key(x)):
      count = s[x] + 1

    s[x] = count
  for x in list:
    count = s[x]
    if(count > 0):
      s[x] = 0
      output.append(x)
  return output

O(n), если dict - хеш, O(nlogn), если dict - дерево, и простой, фиксированный. Спасибо Мэтью за предложение. Извините, я не знаю основные типы.

def unique(x):    
  output = []
  y = {}
  for item in x:
    y[item] = ""

  for item in x:
    if item in y:
      output.append(item)

  return output

Вот два рецепта из документации itertools:

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 ifilterfalse(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

def unique_justseen(iterable, key=None):
    "List unique elements, preserving order. Remember only the element just seen."
    # unique_justseen('AAAABBBCCDAABBB') --> A B C D A B
    # unique_justseen('ABBCcAD', str.lower) --> A B C A D
    return imap(next, imap(itemgetter(1), groupby(iterable, key)))

Здесь есть несколько отличных, эффективных решений. Тем не менее, для тех, кто не обеспокоен абсолютным наиболее эффективным O(n) Решение, я бы пошел с простой однострочник O(n^2*log(n)) решение:

def unique(xs):
    return sorted(set(xs), key=lambda x: xs.index(x))

или более эффективный двухслойный O(n*log(n)) решение:

def unique(xs):
    positions = dict((e,pos) for pos,e in reversed(list(enumerate(xs))))
    return sorted(set(xs), key=lambda x: positions[x])

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

Более длинный ответ: http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/52560

Если вы уберете пустой список из вызова set () в ответе Terhost, вы получите небольшое повышение скорости.

Изменение: найдено = установлено ([])
to: found = set ()

Тем не менее, вам не нужен набор вообще.

def unique(items):
    keep = []

    for item in items:
        if item not in keep:
            keep.append(item)

    return keep

Используя timeit, я получил следующие результаты:

с набором ([]) - 4.97210427363
с множеством () - 4.65712377445
без набора - 3.44865284975

x = [] # Your list  of items that includes Duplicates

# Assuming that your list contains items of only immutable data types

dict_x = {} 

dict_x = {item : item for i, item in enumerate(x) if item not in dict_x.keys()}
# Average t.c. = O(n)* O(1) ; furthermore the dict comphrehension and generator like behaviour of enumerate adds a certain efficiency and pythonic feel to it.

x = dict_x.keys() # if you want your output in list format 
>>> def unique(list):
...   y = []
...   for x in list:
...     if x not in y:
...       y.append(x)
...   return y
>>> x=[1,1,2,'a','a',3]
>>> y = [ _x for _x in x if not _x in locals()['_[1]'] ]
>>> y
[1, 2, 'a', 3]


"locals()['_[1]']" - это "секретное имя" создаваемого списка.

Я не знаю, быстро это или нет, но, по крайней мере, все просто.

Просто преобразуйте его сначала в набор, а затем снова в список

def unique(container):
  return list(set(container))

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

x = [1, 1, 2, 'a', 'a', 3]
y = []
for each in x:
    if each not in y:
        y.append(each)

А =[1,2,3,4,5,7,7,8,8,9,9,3,45]

уникальность определения (л):

ids={}
for item in l:
    if not ids.has_key(item):
        ids[item]=item
return  ids.keys()

распечатать

печать уникальная (а)

----------------------------

Для вставки элементов потребуется получение тета (n), если элемент выходит или нет, потребуется постоянное время для тестирования всех элементов, а также тэта (n), поэтому мы можем видеть, что это решение будет принимать тэта (п) Медведь в уме этот словарь в python реализуется хеш-таблицей

Один проход

a = [1,1,'a','b','c','c']

new_list = []
prev = None

while 1:
    try:
        i = a.pop(0)
        if i != prev:
            new_list.append(i)
        prev = i
    except IndexError:
        break
Другие вопросы по тегам