Làm thế nào để bạn loại bỏ trùng lặp từ một danh sách trong khi giữ trật tự?


770

Có tích hợp nào loại bỏ các bản sao khỏi danh sách trong Python, trong khi giữ trật tự không? Tôi biết rằng tôi có thể sử dụng một bộ để loại bỏ trùng lặp, nhưng điều đó phá hủy trật tự ban đầu. Tôi cũng biết rằng tôi có thể tự lăn như thế này:

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

(Nhờ thư giãn cho mẫu mã đó .)

Nhưng tôi muốn tận dụng một thành ngữ Pythonic tích hợp hoặc nhiều hơn nếu có thể.

Câu hỏi liên quan: Trong Python, thuật toán nhanh nhất để loại bỏ các bản sao khỏi danh sách là gì để tất cả các phần tử là duy nhất trong khi duy trì trật tự ?

Câu trả lời:


762

Ở đây bạn có một số lựa chọn thay thế: http://www.peterbe.com/plog/uniqifier-benchmark

Nhanh nhất

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

Tại sao assign seen.addđể seen_addthay vì chỉ gọi seen.add? Python là một ngôn ngữ động và việc giải quyết seen.addmỗi lần lặp lại tốn kém hơn so với việc giải quyết một biến cục bộ.seen.addcó thể đã thay đổi giữa các lần lặp và thời gian chạy không đủ thông minh để loại trừ điều đó. Để chơi nó an toàn, nó phải kiểm tra đối tượng mỗi lần.

Nếu bạn dự định sử dụng chức năng này rất nhiều trên cùng một tập dữ liệu, có lẽ bạn sẽ tốt hơn với một bộ được đặt hàng: http://code.activestate.com/recipes/528878/

Ôi (1) chèn, xóa và kiểm tra thành viên trên mỗi thao tác.

(Lưu ý bổ sung nhỏ: seen.add()luôn trả về None, vì vậy, orở trên chỉ có một cách để thử cập nhật đã đặt và không phải là một phần không thể thiếu của kiểm tra logic.)


20
@JesseDhillon seen.addcó thể đã thay đổi giữa các lần lặp và thời gian chạy không đủ thông minh để loại trừ điều đó. Để chơi an toàn, nó phải kiểm tra đối tượng mỗi lần. - Nếu bạn nhìn vào mã byte với dis.dis(f), bạn có thể thấy rằng nó thực thi LOAD_ATTRcho addthành viên trên mỗi lần lặp. ideone.com/tz1Tll
Markus Jarderot

5
Khi tôi thử danh sách này trong danh sách các danh sách tôi nhận được: TypeError: loại không thể xóa: 'list'
Jens Timmerman

7
Giải pháp của bạn không phải là giải pháp nhanh nhất. Trong Python 3 (không kiểm tra 2), điều này nhanh hơn (danh sách 300k mục - 0,045s (của bạn) so với 0,035s (cái này): saw = set (); return [x cho x trong các dòng nếu x không nhìn thấy và không saw.add (x)]. Tôi không thể tìm thấy bất kỳ hiệu ứng tốc độ nào của dòng saw_add bạn đã làm.
user136036

3
@ user136036 Vui lòng liên kết với các bài kiểm tra của bạn. Đã bao nhiêu lần bạn chạy chúng? seen_addlà một sự cải tiến nhưng thời gian có thể bị ảnh hưởng bởi tài nguyên hệ thống tại thời điểm đó. Sẽ được quan tâm để xem thời gian đầy đủ
jamylak

2
Đối với bất kỳ ai đang viết mã Python, bạn thực sự nên suy nghĩ kỹ trước khi hy sinh khả năng đọc và các quy ước Python thường được thống nhất chỉ để vắt thêm vài nano giây mỗi vòng. Thử nghiệm có và không seen_add = seen.addmang lại tốc độ chỉ tăng 1%. Nó hầu như không đáng kể.
sleblanc

343

Chỉnh sửa năm 2016

Như Raymond đã chỉ ra , trong python 3.5+ OrderedDictđược triển khai trong C, cách tiếp cận hiểu danh sách sẽ chậm hơn OrderedDict(trừ khi bạn thực sự cần danh sách ở cuối - và thậm chí sau đó, chỉ khi đầu vào rất ngắn). Vì vậy, giải pháp tốt nhất cho 3,5+ là OrderedDict.

Chỉnh sửa quan trọng 2015

Như @abarnert lưu ý, more_itertoolsthư viện ( pip install more_itertools) chứa một unique_everseenhàm được xây dựng để giải quyết vấn đề này mà không có bất kỳ đột biến ( ) nào không thể đọc được trong việc hiểu danh sách. Đây cũng là giải pháp nhanh nhất:not seen.add

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

Chỉ cần một thư viện nhập đơn giản và không có hack. Điều này xuất phát từ việc thực hiện công thức itertools unique_everseentrông giống như:

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

Trong Python 2.7+, thành ngữ chung được chấp nhận (hoạt động nhưng không được tối ưu hóa cho tốc độ, bây giờ tôi sẽ sử dụng unique_everseen) cho việc sử dụng nàycollections.OrderedDict :

Thời gian chạy: O (N)

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

Điều này trông đẹp hơn nhiều so với:

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

và không sử dụng hack xấu xí :

not seen.add(x)

dựa trên thực tế set.addlà một phương thức tại chỗ luôn trả về Noneđể not Noneđánh giá True.

Tuy nhiên, lưu ý rằng giải pháp hack nhanh hơn ở tốc độ thô mặc dù nó có cùng độ phức tạp thời gian chạy O (N).


5
Chuyển đổi sang một số loại tùy chỉnh chỉ để lấy chìa khóa? Chỉ cần một cái nạng khác.
Nakilon

3
@Nakilon Tôi không thực sự thấy nó là một cái nạng. Nó không phơi bày bất kỳ trạng thái đột biến nào, vì vậy nó rất sạch theo nghĩa đó. Trong nội bộ, các bộ Python được triển khai với dict () ( stackoverflow.com/questions/3949 310/ Khăn ), vì vậy về cơ bản, bạn chỉ đang làm những gì trình thông dịch sẽ làm.
Imran

Chỉ cần sử dụng các tác dụng phụ và làm [seen.add(x) for x in seq if x not in seen], hoặc nếu bạn không thích các tác dụng phụ hiểu, chỉ cần sử dụng một forvòng lặp: for x in seq: seen.add(x) if x not in seen else None(vẫn là một lớp lót, mặc dù trong trường hợp này tôi nghĩ rằng một lớp lót là một thuộc tính ngớ ngẩn để cố gắng có trong giải pháp.
ely

@EMS Điều đó không giữ trật tự. Bạn cũng có thể làm như vậy seen = set(seq).
flornquake

1
@CommuSoft Tôi đồng ý, mặc dù trên thực tế, nó hầu như luôn luôn là O (n) do trường hợp xấu nhất rất khó xảy ra
jamylak

110

Trong Python 2.7 , cách mới để loại bỏ các bản sao khỏi một lần lặp trong khi giữ nó theo thứ tự ban đầu là:

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

Trong Python 3.5 , OrderedDict có triển khai C. Thời gian của tôi cho thấy rằng đây là cách nhanh nhất và ngắn nhất trong các cách tiếp cận khác nhau cho Python 3.5.

Trong Python 3.6 , dict thông thường trở nên vừa có trật tự. (Tính năng này được giữ cho CPython và PyPy nhưng có thể không có trong các triển khai khác). Điều đó cho chúng ta một cách khấu trừ nhanh nhất mới trong khi vẫn giữ trật tự:

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

Trong Python 3.7 , lệnh chính quy được đảm bảo cho cả hai thứ tự trên tất cả các cài đặt. Vì vậy, giải pháp ngắn nhất và nhanh nhất là:

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

Trả lời @max: Khi bạn chuyển sang 3.6 hoặc 3.7 và sử dụng lệnh chính quy thay vì OrderedDict , bạn không thể thực sự đánh bại hiệu suất theo bất kỳ cách nào khác. Từ điển dày đặc và dễ dàng chuyển đổi thành một danh sách mà hầu như không có chi phí. Danh sách mục tiêu được định cỡ trước thành len (d) để lưu tất cả các thay đổi kích thước xảy ra trong phần hiểu danh sách. Ngoài ra, vì danh sách khóa nội bộ dày đặc, sao chép các con trỏ gần như nhanh chóng như một bản sao danh sách.


Nó nhanh hơn bất kỳ cách tiếp cận nào khác trên máy của tôi (python 3.5) miễn là cuối cùng tôi không chuyển đổi OrderedDictthành danh sách. Nếu tôi cần phải chuyển đổi nó thành một danh sách, đối với các đầu vào nhỏ , phương pháp hiểu danh sách vẫn nhanh hơn tới 1,5 lần. Điều đó nói rằng, giải pháp này là sạch sẽ hơn nhiều.
tối đa

7
Điều đáng chú ý duy nhất là các "phần tử" có thể lặp lại phải có thể băm được - sẽ rất tuyệt nếu có các phần tử tương đương với các phần tử tùy ý (như một danh sách các danh sách)
Mr_and_Mrs_D

Lặp lại thứ tự chèn trên một dict cung cấp chức năng phục vụ nhiều trường hợp sử dụng hơn là loại bỏ trùng lặp. Ví dụ, phân tích khoa học dựa vào tái sản xuất tính toán mà dict không xác định lặp đi lặp lại không hỗ trợ. Khả năng sinh sản là một mục tiêu chính hiện nay trong mô hình khoa học tính toán, vì vậy chúng tôi hoan nghênh tính năng mới này. Mặc dù tôi biết rằng việc xây dựng với một chế độ xác định là không quan trọng, một hiệu suất cao, mang tính quyết định set()sẽ giúp người dùng ngây thơ hơn phát triển các mã có thể lặp lại.
Arthur

41
sequence = ['1', '2', '3', '3', '6', '4', '5', '6']
unique = []
[unique.append(item) for item in sequence if item not in unique]

độc đáo → ['1', '2', '3', '6', '4', '5']


28
Điều đáng chú ý là điều này diễn ra trongn^2
goncalopp

25
Ick. 2 lần đình công: Sử dụng danh sách để kiểm tra tư cách thành viên (chậm, O (N)) và sử dụng hiểu danh sách cho các tác dụng phụ (xây dựng danh sách Nonetài liệu tham khảo khác trong quy trình!)
Martijn Pieters

1
Tôi đồng ý với @MartijnPieters hoàn toàn không có lý do nào cho việc hiểu danh sách với các tác dụng phụ. Chỉ cần sử dụng một forvòng lặp thay thế
jamylak

31

Không đá con ngựa chết (câu hỏi này đã rất cũ và đã có rất nhiều câu trả lời hay), nhưng đây là một giải pháp sử dụng gấu trúc khá nhanh trong nhiều trường hợp và rất dễ sử dụng.

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

Danh sách thậm chí không phải sắp xếp , điều kiện đủ là các giá trị bằng nhau được nhóm lại với nhau.

Chỉnh sửa: Tôi giả định rằng "giữ trật tự" ngụ ý rằng danh sách thực sự được sắp xếp. Nếu đây không phải là trường hợp, thì giải pháp từ MizardX là đúng.

Chỉnh sửa cộng đồng: Tuy nhiên, đây là cách thanh lịch nhất để "nén các phần tử liên tiếp trùng lặp thành một phần tử".


1
Nhưng điều này không giữ gìn trật tự!

1
Hrm, đây là vấn đề, vì tôi không thể đảm bảo rằng các giá trị bằng nhau được nhóm lại với nhau mà không lặp lại một lần trong danh sách, khi đó tôi có thể cắt tỉa các bản sao.
Josh Glover

Tôi giả định rằng "trật tự bảo quản" ngụ ý rằng danh sách thực sự được sắp xếp.
Rafał Dowgird

1
Có thể đặc điểm kỹ thuật của danh sách đầu vào là một chút không rõ ràng. Các giá trị thậm chí không cần phải được nhóm lại với nhau: [2, 1, 3, 1]. Vì vậy, những giá trị để giữ và những gì để xóa?

1
@igorkf Bỏ qua phần tử thứ hai của (các) cặp.
Rafał Dowgird

24

Tôi nghĩ rằng nếu bạn muốn duy trì trật tự,

bạn có thể thử điều này:

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

HOẶC tương tự bạn có thể làm điều này:

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

Bạn cũng có thể làm điều này:

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

Nó cũng có thể được viết như sau:

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

3
Hai câu trả lời đầu tiên của bạn cho rằng thứ tự của danh sách có thể được xây dựng lại bằng cách sử dụng chức năng sắp xếp, nhưng điều này có thể không phải như vậy.
Richard

5
Hầu hết các câu trả lời đều tập trung vào hiệu suất. Đối với các danh sách không đủ lớn để lo lắng về hiệu suất, thì sắp xếp (set (list1), key = list1.index) là thứ tốt nhất tôi từng thấy. Không nhập thêm, không có chức năng bổ sung, không có biến phụ, và nó khá đơn giản và dễ đọc.
Derek Veit

23

Trong Python 3.7 trở lên, từ điển được đảm bảo ghi nhớ thứ tự chèn khóa của chúng. Câu trả lời cho điều này câu hỏi tóm tắt tình trạng hiện tại.

Các OrderedDictgiải pháp như vậy, trở nên lỗi thời và không có bất kỳ báo cáo nhập khẩu, chúng tôi chỉ đơn giản là có thể phát hành:

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

12

Đối với một câu trả lời rất muộn cho một câu hỏi rất cũ khác:

Các itertoolscông thức nấu ăn có một chức năng thực hiện điều này, bằng cách sử dụng seenkỹ thuật thiết lập, nhưng:

  • Xử lý một tiêu chuẩn key chức năng .
  • Sử dụng không có hack đột xuất.
  • Tối ưu hóa vòng lặp bằng cách liên kết trước seen.addthay vì tìm kiếm N lần. (f7 cũng làm điều này, nhưng một số phiên bản thì không.)
  • Tối ưu hóa vòng lặp bằng cách sử dụng ifilterfalse, do đó bạn chỉ phải lặp qua các phần tử duy nhất trong Python, thay vì tất cả chúng. (Tất nhiên, bạn vẫn lặp đi lặp lại tất cả chúng bên trong ifilterfalse, nhưng đó là bằng C, và nhanh hơn nhiều.)

Có thực sự nhanh hơn f7? Nó phụ thuộc vào dữ liệu của bạn, vì vậy bạn sẽ phải kiểm tra và xem. Nếu bạn muốn có một danh sách cuối cùng, hãy f7sử dụng một listcomp và không có cách nào để làm điều đó ở đây. (Bạn có thể trực tiếp appendthay vì yielding hoặc bạn có thể đưa máy phát điện vàolist hàm, nhưng không ai có thể nhanh như LIST_APPEND trong danh sách.) Ở mọi mức độ, thông thường, việc vắt ra vài micrô giây sẽ không như quan trọng là có một chức năng đã được viết dễ hiểu, có thể sử dụng lại, không cần DSU khi bạn muốn trang trí.

Như với tất cả các công thức nấu ăn, nó cũng có sẵn trong more-iterools .

Nếu bạn chỉ muốn keytrường hợp không , bạn có thể đơn giản hóa nó như sau:

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

Tôi hoàn toàn bỏ qua more-itertoolsđây rõ ràng là câu trả lời tốt nhất. Một from more_itertools import unique_everseen list(unique_everseen(items))cách tiếp cận đơn giản Một cách tiếp cận nhanh hơn nhiều so với của tôi và tốt hơn nhiều so với câu trả lời được chấp nhận, tôi nghĩ rằng việc tải xuống thư viện là xứng đáng. Tôi sẽ vào cộng đồng wiki câu trả lời của tôi và thêm nó vào.
jamylak

12

Chỉ cần thêm một (rất performant) thực hiện chức năng một ví dụ từ một mô-đun bên ngoài 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]

Thời gian

Tôi đã làm một số timings (Python 3.6) và những chương trình mà nó là nhanh hơn so với tất cả các lựa chọn thay thế khác mà tôi thử nghiệm, bao gồm OrderedDict.fromkeys, f7more_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()

nhập mô tả hình ảnh ở đây

Và để chắc chắn rằng tôi cũng đã thực hiện một thử nghiệm với nhiều bản sao hơn chỉ để kiểm tra xem nó có tạo ra sự khác biệt không:

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

nhập mô tả hình ảnh ở đây

Và một chỉ chứa một giá trị:

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

nhập mô tả hình ảnh ở đây

Trong tất cả các trường hợp này, iteration_utilities.unique_everseenchức năng là nhanh nhất (trên máy tính của tôi).


iteration_utilities.unique_everseenHàm này cũng có thể xử lý các giá trị không thể xóa trong đầu vào (tuy nhiên với O(n*n)hiệu suất thay vì O(n)hiệu suất khi các giá trị có thể băm).

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

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

1 Tuyên bố miễn trừ trách nhiệm: Tôi là tác giả của gói đó.


Tôi không hiểu sự cần thiết của dòng này: seen_add = seen.add- điều này có cần thiết cho điểm chuẩn không?
Alex

@Alex Đây là cách tiếp cận được đưa ra trong câu trả lời này . Nó sẽ có ý nghĩa hơn để hỏi nó ở đó. Tôi chỉ sử dụng cách tiếp cận từ câu trả lời đó để so sánh thời gian.
MSeifert

bạn có thể thêm dict.fromkeys()phương pháp vào biểu đồ của bạn được không?
Boris

Tôi không thực sự chắc chắn nếu tôi có cùng một thời gian sớm. Bạn có nghĩ rằng nó nhanh hơn nhiều so với ordereddict.fromkeys?
MSeifert

"Hàm iteration_utilities.unique_everseen này cũng có thể xử lý các giá trị không thể xóa được trong đầu vào" - vâng, điều này thực sự quan trọng. Nếu bạn có một danh sách các dicts dicts dicts vv thì đây là cách duy nhất để thực hiện công việc, ngay cả ở quy mô nhỏ.
Roko Mijic

6

Đối với không có loại có thể băm (ví dụ: danh sách danh sách), dựa trên 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

Mượn ý tưởng đệ quy được sử dụng để xác định nubchức năng của Haskell cho các danh sách, đây sẽ là một cách tiếp cận đệ quy:

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

ví dụ:

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

Tôi đã thử nó để tăng kích thước dữ liệu và thấy độ phức tạp thời gian tuyến tính (không dứt khoát, nhưng gợi ý rằng điều này sẽ tốt cho dữ liệu bình thường).

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

Tôi cũng nghĩ thật thú vị khi điều này có thể dễ dàng được khái quát hóa thành tính duy nhất bởi các hoạt động khác. Như thế này:

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)

Ví dụ: bạn có thể chuyển vào một hàm sử dụng khái niệm làm tròn cho cùng một số nguyên như thể đó là "đẳng thức" cho các mục đích duy nhất, như sau:

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

sau đó là duy nhất (some_list, test_round) sẽ cung cấp các yếu tố duy nhất của danh sách trong đó tính duy nhất không còn có nghĩa là bình đẳng truyền thống (được ngụ ý bằng cách sử dụng bất kỳ cách tiếp cận dựa trên tập hợp hoặc khóa chính tả nào cho vấn đề này) mà thay vào đó chỉ phần tử đầu tiên làm tròn thành K cho mỗi số nguyên K có thể mà các phần tử có thể làm tròn thành, ví dụ:

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
Lưu ý rằng hiệu suất sẽ trở nên tồi tệ khi số lượng phần tử duy nhất rất lớn so với tổng số phần tử, vì mỗi lần sử dụng cuộc gọi đệ quy liên tiếp filtersẽ hầu như không được hưởng lợi từ cuộc gọi trước đó. Nhưng nếu số lượng phần tử duy nhất nhỏ so với kích thước mảng, thì điều này sẽ hoạt động khá tốt.
ely

3

5 x biến thể giảm nhanh hơn nhưng tinh vi hơn

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

Giải trình:

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

Bạn có thể tham khảo một sự hiểu biết danh sách vì nó đang được xây dựng bởi biểu tượng '_ [1]'.
Ví dụ, hàm sau duy nhất-ifies một danh sách các phần tử mà không thay đổi thứ tự của chúng bằng cách tham chiếu mức độ hiểu danh sách của nó.

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

Bản giới thiệu:

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

Đầu ra:

[1, 2, 3, 4, 5]

2
Cũng lưu ý rằng nó sẽ làm cho nó hoạt động O (n ^ 2), trong đó khi tạo một tập hợp / dict (có thời gian tra cứu liên tục) và chỉ thêm các phần tử chưa thấy trước đó sẽ là tuyến tính.
ely

Đây là Python 2.6 chỉ tôi tin. Và vâng, đó là O (N ^ 2)
jamylak

2

Câu trả lời của MizardX cung cấp một bộ sưu tập tốt các phương pháp tiếp cận.

Đây là những gì tôi nghĩ ra trong khi suy nghĩ lớn:

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

Giải pháp của bạn là tốt, nhưng nó xuất hiện cuối cùng của mỗi yếu tố. Để có lần xuất hiện đầu tiên, hãy sử dụng: [x cho i, x trong liệt kê (danh sách của tôi) nếu x không có trong danh sách của tôi [: i]]
Rivka

7
Vì tìm kiếm trong danh sách là một O(n)thao tác và bạn thực hiện nó trên từng mục, nên độ phức tạp của giải pháp của bạn sẽ là O(n^2). Điều này chỉ không thể chấp nhận được cho một vấn đề tầm thường như vậy.
Nikita Volkov

2

Đây là một cách đơn giản để làm điều đó:

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

cung cấp đầu ra:

["hello", " ", "w", "o", "r", "l", "d"]

1

Bạn có thể làm một loại hack hiểu danh sách xấu xí.

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

Thích i,e in enumerate(l)đến l[i] for i in range(len(l)).
Evpok

1

Cách tiếp cận tương đối hiệu quả với _sorted_một numpymảng:

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

Đầu ra:

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

Biểu thức trình tạo sử dụng tra cứu O (1) của tập hợp để xác định xem có bao gồm một phần tử trong danh sách mới hay không.


1
Sử dụng thông minh extendvới biểu thức máy phát phụ thuộc vào vật được mở rộng (nên +1), nhưng set(n)được tính toán lại ở mỗi giai đoạn (là tuyến tính) và điều này làm cho phương pháp tiếp cận tổng thể trở thành bậc hai. Trong thực tế, điều này gần như chắc chắn tồi tệ hơn so với việc sử dụng đơn giản ele in n. Tạo một bộ cho một thử nghiệm thành viên duy nhất không đáng giá cho việc tạo bộ. Tuy nhiên - đó là một cách tiếp cận thú vị.
John Coleman

1

Một giải pháp đệ quy đơn giản:

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

Loại bỏ các giá trị trùng lặp trong một chuỗi, nhưng giữ nguyên thứ tự của các mục còn lại. Sử dụng chức năng tạo mục đích chung.

# 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

người dùng gấu trúc nên kiểm tra pandas.unique.

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

Hàm trả về một mảng NumPy. Nếu cần, bạn có thể chuyển đổi nó thành một danh sách với tolistphương thức.


1
Đẹp một. Tôi sẽ không bao giờ tưởng tượng được việc sử dụng gấu trúc cho việc đó nhưng nó hoạt động
seralouk

0

Nếu bạn cần một lớp lót thì có lẽ điều này sẽ giúp:

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

... nên làm việc nhưng sửa tôi nếu tôi sai


đó là một biểu thức có điều kiện nên rất tốt
mã22

0

Nếu bạn thường xuyên sử dụng pandasvà tính thẩm mỹ được ưu tiên hơn hiệu suất, thì hãy xem xét chức năng tích hợp 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

Thời gian:

    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

điều này sẽ giữ trật tự và chạy trong thời gian O (n). về cơ bản, ý tưởng là tạo ra một cái lỗ ở bất cứ nơi nào có một bản sao được tìm thấy và nhấn chìm nó xuống đáy. sử dụng một con trỏ đọc và ghi. Bất cứ khi nào một bản sao được tìm thấy chỉ con trỏ đọc tiến lên và con trỏ ghi vẫn ở trên mục trùng lặp để ghi đè lên 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

Một giải pháp mà không sử dụng các mô-đun hoặc bộ nhập khẩu:

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)

Cung cấp đầu ra:

['ask', 'not', 'what', 'your', 'country', 'can', 'do', 'for', 'you']

đây là độ phức tạp O (N ** 2) + danh sách cắt mỗi lần.
Jean-François Fabre

0

Một phương pháp tại chỗ

Phương pháp này là bậc hai, bởi vì chúng tôi có một tra cứu tuyến tính vào danh sách cho mọi phần tử của danh sách (để chúng tôi phải thêm chi phí sắp xếp lại danh sách vì dels).

Điều đó nói rằng, có thể hoạt động tại chỗ nếu chúng ta bắt đầu từ cuối danh sách và tiến tới nguồn gốc loại bỏ từng thuật ngữ có trong danh sách phụ ở bên trái

Ý tưởng này trong mã chỉ đơn giản là

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

Một thử nghiệm đơn giản về việc thực hiện

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

Trước khi đăng tôi đã tìm kiếm nội dung của các câu trả lời cho 'địa điểm' không có kết quả. Nếu những người khác đã giải quyết vấn đề theo cách tương tự, vui lòng thông báo cho tôi và tôi sẽ xóa câu trả lời của tôi càng sớm càng tốt.
gboffi

Bạn chỉ có thể sử dụng l[:] = <one of the the faster methods>nếu bạn muốn một hoạt động tại chỗ, không?
hồ bấm giờ

@timgeb Có và không ... Khi tôi làm a=[1]; b=a; a[:]=[2]thì b==[2]giá trị Truevà chúng tôi có thể nói rằng chúng tôi đang làm nó tại chỗ, tuy nhiên những gì bạn đề xuất là sử dụng không gian mới để có một danh sách mới, thay thế các dữ liệu cũ bằng dữ liệu mới và đánh dấu dữ liệu cũ cho bộ sưu tập rác vì không được tham chiếu thêm bởi bất cứ điều gì, vì vậy nói rằng nó hoạt động tại chỗ là một chút kéo dài khái niệm wrt những gì tôi đã chỉ ra rằng nó có thể ... có hiệu quả không? vâng, nhưng tôi đã nói trước điều đó
gboffi

0

Cách tiếp cận của zmk sử dụng khả năng hiểu danh sách rất nhanh, nhưng vẫn giữ trật tự một cách tự nhiên. Để áp dụng cho các chuỗi nhạy cảm trường hợp, nó có thể dễ dàng sửa đổi. Điều này cũng bảo tồn trường hợp ban đầu.

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

Các chức năng liên quan chặt chẽ là:

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

Hiểu một danh sách lót:

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

Chỉ cần thêm một điều kiện để kiểm tra giá trị đó không nằm ở vị trí trước đó

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.