Як видалити дублікати зі списку, зберігаючи порядок?


769

Чи є вбудований модуль, який видаляє дублікати зі списку на Python, зберігаючи порядок? Я знаю, що я можу використовувати набір для видалення дублікатів, але це руйнує початковий порядок. Я також знаю, що можу власноруч кататись так:

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

(Завдяки розмотуванню для цього зразка коду .)

Але я хотів би скористатись вбудованою чи більш пітонічною ідіомою, якщо це можливо.

Пов'язане питання: У Python який найшвидший алгоритм видалення дублікатів зі списку, щоб усі елементи були унікальними при збереженні порядку ?

Відповіді:


762

Тут є кілька альтернатив: 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) це швидше (список списків 300k - 0,045s (ваш) проти 0,035s (цей): saw = set (); повернути [x для x у рядках, якщо x не в баченому і не saw.add (x)]. Я не міг знайти жодного ефекту швидкості у видному рядку
saw_add

3
@ user136036 Будь ласка, посилання на ваші тести. Скільки разів ви запускали їх? seen_addє вдосконаленням, але на терміни можуть впливати системні ресурси на той час. Було б цікаво побачити повний
таймінг

2
Кожному, хто пише Python-код, вам дійсно варто подумати двічі, перш ніж принести в жертву читабельність і загально узгоджені конвенції Python, щоб вичавити ще кілька наносекунд на цикл. Тестування з і без seen_add = seen.addдає лише швидкість збільшення на 1%. Це навряд чи важливо.
sleblanc

343

Редагувати 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).


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).
flornquake

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 OrdersDict має C-реалізацію. Мої таймінги показують, що зараз це найшвидший і найкоротший з різних підходів для Python 3.5.

У Python 3.6 регулярний диктат став одночасно упорядкованим та компактним. (Ця функція утримується для CPython та PyPy, але може не бути в інших реалізаціях). Це дає нам новий найшвидший спосіб виведення, зберігаючи порядок:

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

У Python 3.7 регулярний диктант гарантується обом упорядкованим у всіх реалізаціях. Отже, найкоротше і швидке рішення:

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

Відповідь на @max: Після переходу на 3.6 або 3.7 та використання звичайного дикта замість OrdersDict , ви не можете перемогти продуктивність будь-яким іншим способом. Словник щільний і легко перетворюється на список майже без накладних витрат. Список цілей має попередній розмір до 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']


28
Варто зауважити, що це триваєn^2
goncalopp

25
Ick. 2 удари: Використання списку для тестування членства (повільно, O (N)) та використання списку розуміння побічних ефектів (створення іншого списку Noneпосилань у процесі!)
Martijn Pieters

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
Хрм, це проблематично, оскільки я не можу гарантувати, що рівні значення згруповані разом, не перекопуючи один раз список, до цього часу я міг би обрізати дублікати.
Джош Гловер

Я припускав, що "збереження порядку" означає, що список фактично упорядкований.
Rafał Dowgird

1
Можливо, специфікація списку входів трохи незрозуміла. Значення навіть не потрібно групувати: [2, 1, 3, 1]. Отже, які значення зберігати, а які видалити?

1
@igorkf Ігнорування другого елемента пари.
Rafał Dowgird

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
Більшість відповідей орієнтовані на ефективність. Для списків, які недостатньо великі, щоб турбуватися про продуктивність, найкраще, що я бачив, відсортовано (набір (список1), ключ = list1.index). Без додаткового імпорту, без зайвих функцій, без зайвої змінної, і він досить простий і читабельний.
Дерек Вейт

23

У Python 3.7 і вище, словники гарантовано запам'ятовують їх порядок вставки ключів. Відповідь на це питання резюмує сучасний стан справ.

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

>>> lst = [1, 2, 1, 3, 3, 2, 4]
>>> list(dict.fromkeys(lst))
[1, 2, 3, 4]

11

Ще одна дуже пізня відповідь на ще одне дуже давнє запитання:

У itertoolsрецептах є функція , яка робить це, використовуючи seenнабір техніку, але:

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

Це насправді швидше, ніж f7? Це залежить від ваших даних, тому вам доведеться перевірити їх і побачити. Якщо ви хочете в кінці списку, f7використовуйте listcomp, і тут немає ніякого способу зробити це. (Ви можете безпосередньо appendзамість yielding, або можете подати генератор вlist функцію, але жоден не може бути таким швидким, як LIST_APPEND всередині спільного списку.) У будь-якому випадку, як правило, витиснення декількох мікросекунд не буде таким важливо мати легко зрозумілу функцію для багаторазового використання, вже написану, яка не потребує DSU, коли ви хочете прикрасити.

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

Якщо ви просто хочете нуля key, ви можете спростити його як:

def unique(iterable):
    seen = set()
    seen_add = seen.add
    for element in itertools.ifilterfalse(seen.__contains__, iterable):
        seen_add(element)
        yield element

Я повністю не помітив, що more-itertoolsце, очевидно, найкраща відповідь. Простий from more_itertools import unique_everseen list(unique_everseen(items))Набагато швидший підхід, ніж мій, і набагато краще, ніж прийнята відповідь, я вважаю, що завантаження бібліотеки того варте. Я збираюся відповісти на вікі спільноти і додати це.
jamylak

11

Просто щоб додати ще один (дуже продуктивну) реалізації такої функціональності від зовнішнього модуля 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 Відмова: Я автор цього пакету.


Я не розумію необхідності для цієї лінії: seen_add = seen.add- чи потрібно це для орієнтирів?
Алекс

@Alex Це такий підхід, який дано у цій відповіді . Було б більше сенсу запитати його там. Я просто використав підхід з цієї відповіді, щоб порівняти терміни.
MSeifert

чи можете ви, dict.fromkeys()будь ласка, додати метод до своєї діаграми?
Борис

Я не дуже впевнений, чи маю я те ж саме, щоб зробити таймінги скоро. Ви думаєте, що це набагато швидше, ніж ordereddict.fromkeys?
MSeifert

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

3

Запозичення рекурсивної ідеї, що використовується при визначенні nubфункції Haskell для списків, це був би рекурсивний підхід:

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)

тоді унікальний (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]

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]'.
Наприклад, наступна функція унікально ідентифікує список елементів, не змінюючи їх порядок, посилаючись на його розуміння списку.

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 тільки я вірю. І так, це О (N ^ 2)
jamylak

2

Відповідь MizardX дає хорошу колекцію з декількох підходів.

Ось що я придумав, думаючи вголос:

mylist = [x for i,x in enumerate(mylist) if x not in mylist[i+1:]]

Ваше рішення приємне, але воно займає останній вигляд кожного елемента. Щоб взяти перший вигляд, використовуйте: [x for i, x у перерахуванні (мій список), якщо x не в моєму списку [: 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"]

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

1
l = [1,2,2,3,3,...]
n = []
n.extend(ele for ele in l if ele not in set(n))

Вираз генератора, який використовує пошук (1) набору для визначення того, чи слід включати елемент у новий список.


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методом.


1
Хороший. Я б ніколи не уявляв, щоб використовувати панди для цього, але це працює
seralouk

0

Якщо вам потрібен один вкладиш, можливо, це допоможе:

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

... повинен працювати, але виправте мене, якщо я помиляюся


це умовний вираз, так що це добре
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

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

Цей метод є квадратичним, оскільки у нас є лінійний пошук у списку для кожного елемента списку (до цього ми повинні додати вартість перестановки списку через dels).

З огляду на це, можна діяти на місці, якщо ми починаємо з кінця списку і переходимо до початку, видаляючи кожен термін, присутній у підсписку зліва

Ця ідея в коді проста

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

@timgeb Та й немає ... Коли я роблю a=[1]; b=a; a[:]=[2]те b==[2]значення , Trueі ми можемо сказати , що ми робимо це на місці, проте , що ви пропонуєте використовуєте новий простір , щоб новий список, замінити старі дані з новими даними і відзначте старі дані для збору сміття, тому що ні на що більше не посилаються, тому мовляв, що це працює на місці, це трохи розтягує концепцію wrt, що я показав, що це можливо ... це неефективно? так, але я це заздалегідь сказав.
gboffi

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

Просто додайте умовне, щоб перевірити, чи немає значення на попередній позиції

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.