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

Например:

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

Предположим, что элементы списка хешируются.

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

Ответ 1

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])

Ответ 2

Использование:

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)'

И так далее для различных других функций (которые я назвал после их плакатов), у меня есть следующие результаты (на моем первом поколении Intel 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] Обратите внимание, что Allen изменяет список на месте - я считаю, что это исказило время, так как модуль timeit запускает код 100000 раз, а 99999 из них - с списком без дублирования.


Резюме. Прямая реализация с множествами выигрывает над запутанными однострочными: -)

Ответ 3

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

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.

Ответ 4

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

Здесь один для изменения списка:

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)

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

Ответ 5

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

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: Мне было бы интересно, можете ли вы подтвердить мои тайминги.

Ответ 6

Отклонение на основе генератора:

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

Ответ 7

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

list(OrderedDict.fromkeys(iterable))

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

Ответ 8

Взято из 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

Ответ 9

Однострочник:

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

Ответ 10

Для этого используется однострочный однострочный:

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

Ответ 11

Это самый быстрый, сравнивающий все материалы из этого продолжительного обсуждения и другие ответы, приведенные здесь, ссылаясь на это benchmark. Это на 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

Ответ 12

Вы действительно можете сделать что-то действительно классное в 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()]

Ответ 13

Должны ли дубликаты обязательно быть в списке в первую очередь? Там нет накладных расходов, если смотреть на элементы вверх, но есть немного больше накладных расходов при добавлении элементов (хотя служебные данные должны быть 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])
>>> 

Ответ 14

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

Это быстрый 2-лайнер, который использует встроенные функции распознавания списков и dicts.

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 будет работать, чтобы максимизировать эффективность процесса.

Ответ 15

O (n), если dict is hash, 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

Ответ 16

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

Ответ 17

Здесь есть отличные, эффективные решения. Однако для тех, кто не имеет отношения к абсолютному наиболее эффективному решению 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])

Ответ 18

Вот два рецепта из документации 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)))

Ответ 19

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

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

Ответ 20

>>> def unique(list):
...   y = []
...   for x in list:
...     if x not in y:
...       y.append(x)
...   return y

Ответ 21

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

Изменить:   found = set ([])
чтобы:   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

Ответ 22

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 

Ответ 23

>>> 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]']" - это "секретное имя" создаваемого списка.

Ответ 24

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

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

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

Ответ 25

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

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

Ответ 26

Один проход.

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

Ответ 27

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

def unique (l):

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

напечатать a

print unique (a)

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

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