Ví dụ, được đưa ra danh sách ['one', 'two', 'one']
, thuật toán sẽ trả về True
, trong khi đưa ra ['one', 'two', 'three']
nó sẽ trả về False
.
Ví dụ, được đưa ra danh sách ['one', 'two', 'one']
, thuật toán sẽ trả về True
, trong khi đưa ra ['one', 'two', 'three']
nó sẽ trả về False
.
Câu trả lời:
Sử dụng set()
để loại bỏ trùng lặp nếu tất cả các giá trị có thể băm :
>>> your_list = ['one', 'two', 'one']
>>> len(your_list) != len(set(your_list))
True
Đề xuất cho danh sách ngắn :
any(thelist.count(x) > 1 for x in thelist)
Đừng không sử dụng trên một danh sách dài - nó có thể mất nhiều thời gian tỉ lệ với hình vuông của số lượng các mục trong danh sách!
Đối với danh sách dài hơn với các mục có thể băm (chuỗi, số, & c):
def anydup(thelist):
seen = set()
for x in thelist:
if x in seen: return True
seen.add(x)
return False
Nếu các mục của bạn không thể băm được (danh sách phụ, dicts, v.v.), nó sẽ có hairier, mặc dù vẫn có thể lấy O (N logN) nếu chúng ít nhất có thể so sánh được. Nhưng bạn cần biết hoặc kiểm tra các đặc điểm của các mục (có thể băm hay không, có thể so sánh hay không) để có hiệu suất tốt nhất có thể - O (N) cho các hàm băm, O (N log N) cho các so sánh không thể băm nó xuống đến O (N bình phương) và không ai có thể làm gì về điều đó :-(.
all
tổng số là 1). Một dict với tất cả các giá trị True, mà bạn cũng đề cập, là một mô phỏng lố bịch, vô dụng của a set
, không có giá trị gia tăng nào. Big-O không phải là tất cả mọi thứ trong lập trình.
Điều này đã cũ, nhưng câu trả lời ở đây đã đưa tôi đến một giải pháp hơi khác. Nếu bạn đang lạm dụng sự hiểu biết, bạn có thể bị đoản mạch theo cách này.
xs = [1, 2, 1]
s = set()
any(x in s or s.add(x) for x in xs)
# You can use a similar approach to actually retrieve the duplicates.
s = set()
duplicates = set(x for x in xs if x in s or s.add(x))
Nếu bạn thích phong cách lập trình chức năng, đây là một hàm hữu ích, tự viết mã và kiểm tra mã bằng doctest .
def decompose(a_list):
"""Turns a list into a set of all elements and a set of duplicated elements.
Returns a pair of sets. The first one contains elements
that are found at least once in the list. The second one
contains elements that appear more than once.
>>> decompose([1,2,3,5,3,2,6])
(set([1, 2, 3, 5, 6]), set([2, 3]))
"""
return reduce(
lambda (u, d), o : (u.union([o]), d.union(u.intersection([o]))),
a_list,
(set(), set()))
if __name__ == "__main__":
import doctest
doctest.testmod()
Từ đó, bạn có thể kiểm tra tính đơn nhất bằng cách kiểm tra xem phần tử thứ hai của cặp được trả về có trống không:
def is_set(l):
"""Test if there is no duplicate element in l.
>>> is_set([1,2,3])
True
>>> is_set([1,2,1])
False
>>> is_set([])
True
"""
return not decompose(l)[1]
Lưu ý rằng điều này không hiệu quả vì bạn đang xây dựng phân tách rõ ràng. Nhưng dọc theo dòng sử dụng giảm, bạn có thể đưa ra một cái gì đó tương đương (nhưng hơi kém hiệu quả) để trả lời 5:
def is_set(l):
try:
def func(s, o):
if o in s:
raise Exception
return s.union([o])
reduce(func, l, set())
return True
except:
return False
Tôi nghĩ rằng nó sẽ hữu ích để so sánh thời gian của các giải pháp khác nhau được trình bày ở đây. Đối với điều này, tôi đã sử dụng thư viện của riêng mình simple_benchmark
:
Vì vậy, thực sự cho trường hợp này, giải pháp từ Denis Otkidach là nhanh nhất.
Một số cách tiếp cận cũng thể hiện đường cong dốc hơn nhiều, đây là những cách tiếp cận chia tỷ lệ bậc hai với số lượng phần tử (giải pháp đầu tiên của Alex Martellis, wjandrea và cả hai giải pháp Xavier Decorets). Một điều quan trọng nữa phải kể đến là giải pháp gấu trúc từ Keiku có yếu tố không đổi rất lớn. Nhưng đối với các danh sách lớn hơn, nó gần như bắt kịp với các giải pháp khác.
Và trong trường hợp trùng lặp là ở vị trí đầu tiên. Điều này rất hữu ích để xem giải pháp nào là ngắn mạch:
Ở đây một số cách tiếp cận không bị chập điện: Kaiku, Frank, Xavier_Decoret (giải pháp đầu tiên), Turn, Alex Martelli (giải pháp đầu tiên) và cách tiếp cận được trình bày bởi Denis Otkidach (nhanh nhất trong trường hợp không trùng lặp).
Tôi đã bao gồm một chức năng từ thư viện của riêng tôi ở đây: iteration_utilities.all_distinct
có thể cạnh tranh với giải pháp nhanh nhất trong trường hợp không trùng lặp và thực hiện trong thời gian liên tục cho trường hợp bắt đầu trùng lặp (mặc dù không nhanh nhất).
Mã cho điểm chuẩn:
from collections import Counter
from functools import reduce
import pandas as pd
from simple_benchmark import BenchmarkBuilder
from iteration_utilities import all_distinct
b = BenchmarkBuilder()
@b.add_function()
def Keiku(l):
return pd.Series(l).duplicated().sum() > 0
@b.add_function()
def Frank(num_list):
unique = []
dupes = []
for i in num_list:
if i not in unique:
unique.append(i)
else:
dupes.append(i)
if len(dupes) != 0:
return False
else:
return True
@b.add_function()
def wjandrea(iterable):
seen = []
for x in iterable:
if x in seen:
return True
seen.append(x)
return False
@b.add_function()
def user(iterable):
clean_elements_set = set()
clean_elements_set_add = clean_elements_set.add
for possible_duplicate_element in iterable:
if possible_duplicate_element in clean_elements_set:
return True
else:
clean_elements_set_add( possible_duplicate_element )
return False
@b.add_function()
def Turn(l):
return Counter(l).most_common()[0][1] > 1
def getDupes(l):
seen = set()
seen_add = seen.add
for x in l:
if x in seen or seen_add(x):
yield x
@b.add_function()
def F1Rumors(l):
try:
if next(getDupes(l)): return True # Found a dupe
except StopIteration:
pass
return False
def decompose(a_list):
return reduce(
lambda u, o : (u[0].union([o]), u[1].union(u[0].intersection([o]))),
a_list,
(set(), set()))
@b.add_function()
def Xavier_Decoret_1(l):
return not decompose(l)[1]
@b.add_function()
def Xavier_Decoret_2(l):
try:
def func(s, o):
if o in s:
raise Exception
return s.union([o])
reduce(func, l, set())
return True
except:
return False
@b.add_function()
def pyrospade(xs):
s = set()
return any(x in s or s.add(x) for x in xs)
@b.add_function()
def Alex_Martelli_1(thelist):
return any(thelist.count(x) > 1 for x in thelist)
@b.add_function()
def Alex_Martelli_2(thelist):
seen = set()
for x in thelist:
if x in seen: return True
seen.add(x)
return False
@b.add_function()
def Denis_Otkidach(your_list):
return len(your_list) != len(set(your_list))
@b.add_function()
def MSeifert04(l):
return not all_distinct(l)
Và cho các đối số:
# No duplicate run
@b.add_arguments('list size')
def arguments():
for exp in range(2, 14):
size = 2**exp
yield size, list(range(size))
# Duplicate at beginning run
@b.add_arguments('list size')
def arguments():
for exp in range(2, 14):
size = 2**exp
yield size, [0, *list(range(size)]
# Running and plotting
r = b.run()
r.plot()
Gần đây tôi đã trả lời một câu hỏi liên quan để thiết lập tất cả các bản sao trong danh sách, sử dụng trình tạo. Nó có lợi thế là nếu được sử dụng chỉ để thiết lập 'nếu có bản sao' thì bạn chỉ cần lấy mục đầu tiên và phần còn lại có thể bỏ qua, đó là lối tắt cuối cùng.
Đây là một cách tiếp cận dựa trên bộ thú vị mà tôi đã điều chỉnh trực tiếp từ moooeeeep :
def getDupes(l):
seen = set()
seen_add = seen.add
for x in l:
if x in seen or seen_add(x):
yield x
Theo đó, một danh sách đầy đủ các bản sao sẽ được list(getDupes(etc))
. Để đơn giản kiểm tra "nếu" có bản sao, nó cần được gói như sau:
def hasDupes(l):
try:
if getDupes(l).next(): return True # Found a dupe
except StopIteration:
pass
return False
Điều này có tỷ lệ tốt và cung cấp thời gian hoạt động nhất quán ở bất cứ nơi nào có bản sao trong danh sách - Tôi đã thử nghiệm với danh sách lên tới 1m mục. Nếu bạn biết điều gì đó về dữ liệu, cụ thể, các bản sao đó có khả năng hiển thị trong nửa đầu hoặc những thứ khác cho phép bạn hiểu sai các yêu cầu của mình, như cần phải có bản sao thực sự, thì có một vài công cụ định vị thực sự thay thế Điều đó có thể tốt hơn. Hai cái tôi khuyên là ...
Cách tiếp cận dựa trên dict đơn giản, rất dễ đọc:
def getDupes(c):
d = {}
for i in c:
if i in d:
if d[i]:
yield i
d[i] = False
else:
d[i] = True
Tận dụng itertools (về cơ bản là ifilter / izip / tee) trong danh sách được sắp xếp, rất hiệu quả nếu bạn nhận được tất cả các bản sao mặc dù không nhanh chóng chỉ nhận được phần đầu tiên:
def getDupes(c):
a, b = itertools.tee(sorted(c))
next(b, None)
r = None
for k, g in itertools.ifilter(lambda x: x[0]==x[1], itertools.izip(a, b)):
if k != r:
yield k
r = k
Đây là những người biểu diễn hàng đầu từ các phương pháp tôi đã thử cho danh sách dupe đầy đủ , với bản dupe đầu tiên xuất hiện ở bất cứ đâu trong danh sách phần tử 1m từ đầu đến giữa. Thật đáng ngạc nhiên khi có ít chi phí cho bước sắp xếp được thêm vào. Số dặm của bạn có thể thay đổi, nhưng đây là kết quả tính giờ cụ thể của tôi:
Finding FIRST duplicate, single dupe places "n" elements in to 1m element array
Test set len change : 50 - . . . . . -- 0.002
Test in dict : 50 - . . . . . -- 0.002
Test in set : 50 - . . . . . -- 0.002
Test sort/adjacent : 50 - . . . . . -- 0.023
Test sort/groupby : 50 - . . . . . -- 0.026
Test sort/zip : 50 - . . . . . -- 1.102
Test sort/izip : 50 - . . . . . -- 0.035
Test sort/tee/izip : 50 - . . . . . -- 0.024
Test moooeeeep : 50 - . . . . . -- 0.001 *
Test iter*/sorted : 50 - . . . . . -- 0.027
Test set len change : 5000 - . . . . . -- 0.017
Test in dict : 5000 - . . . . . -- 0.003 *
Test in set : 5000 - . . . . . -- 0.004
Test sort/adjacent : 5000 - . . . . . -- 0.031
Test sort/groupby : 5000 - . . . . . -- 0.035
Test sort/zip : 5000 - . . . . . -- 1.080
Test sort/izip : 5000 - . . . . . -- 0.043
Test sort/tee/izip : 5000 - . . . . . -- 0.031
Test moooeeeep : 5000 - . . . . . -- 0.003 *
Test iter*/sorted : 5000 - . . . . . -- 0.031
Test set len change : 50000 - . . . . . -- 0.035
Test in dict : 50000 - . . . . . -- 0.023
Test in set : 50000 - . . . . . -- 0.023
Test sort/adjacent : 50000 - . . . . . -- 0.036
Test sort/groupby : 50000 - . . . . . -- 0.134
Test sort/zip : 50000 - . . . . . -- 1.121
Test sort/izip : 50000 - . . . . . -- 0.054
Test sort/tee/izip : 50000 - . . . . . -- 0.045
Test moooeeeep : 50000 - . . . . . -- 0.019 *
Test iter*/sorted : 50000 - . . . . . -- 0.055
Test set len change : 500000 - . . . . . -- 0.249
Test in dict : 500000 - . . . . . -- 0.145
Test in set : 500000 - . . . . . -- 0.165
Test sort/adjacent : 500000 - . . . . . -- 0.139
Test sort/groupby : 500000 - . . . . . -- 1.138
Test sort/zip : 500000 - . . . . . -- 1.159
Test sort/izip : 500000 - . . . . . -- 0.126
Test sort/tee/izip : 500000 - . . . . . -- 0.120 *
Test moooeeeep : 500000 - . . . . . -- 0.131
Test iter*/sorted : 500000 - . . . . . -- 0.157
.next()
gọi trong khối mã thứ hai của bạn không hoạt động trên Python 3.x. Tôi nghĩ next(getDupes(l))
nên làm việc trên các phiên bản Python, vì vậy có thể có ý nghĩa để thay đổi điều đó.
ifilter
và ìzip
có thể được thay thế đơn giản bằng tích hợp filter
và zip
trong Python 3.x.
Một cách khác để làm điều này ngắn gọn là với Counter .
Để chỉ xác định nếu có bất kỳ bản sao trong danh sách ban đầu:
from collections import Counter
def has_dupes(l):
# second element of the tuple has number of repetitions
return Counter(l).most_common()[0][1] > 1
Hoặc để có danh sách các mục có trùng lặp:
def get_dupes(l):
return [k for k, v in Counter(l).items() if v > 1]
Tôi thấy điều này có hiệu suất tốt nhất vì nó làm ngắn mạch hoạt động khi lần đầu tiên được tìm thấy, sau đó thuật toán này có độ phức tạp về thời gian và không gian O (n) trong đó n là độ dài của danh sách:
def has_duplicated_elements(iterable):
""" Given an `iterable`, return True if there are duplicated entries. """
clean_elements_set = set()
clean_elements_set_add = clean_elements_set.add
for possible_duplicate_element in iterable:
if possible_duplicate_element in clean_elements_set:
return True
else:
clean_elements_set_add( possible_duplicate_element )
return False
Một giải pháp đơn giản hơn như sau. Chỉ cần kiểm tra Đúng / Sai bằng .duplicated()
phương pháp gấu trúc và sau đó lấy tổng. Vui lòng xem thêm pandas.Series.d trùng lặp - tài liệu gấu trúc 0.24.1
import pandas as pd
def has_duplicated(l):
return pd.Series(l).duplicated().sum() > 0
print(has_duplicated(['one', 'two', 'one']))
# True
print(has_duplicated(['one', 'two', 'three']))
# False
Nếu danh sách chứa các mục không thể xóa được, bạn có thể sử dụng giải pháp của Alex Martelli nhưng với một danh sách thay vì một bộ, mặc dù nó chậm hơn đối với các đầu vào lớn hơn: O (N ^ 2).
def has_duplicates(iterable):
seen = []
for x in iterable:
if x in seen:
return True
seen.append(x)
return False
Tôi đã sử dụng cách tiếp cận của pyrospade, vì tính đơn giản của nó và đã sửa đổi một chút trong danh sách ngắn được tạo từ sổ đăng ký Windows không phân biệt chữ hoa chữ thường.
Nếu chuỗi giá trị PATH thô được chia thành các đường dẫn riêng lẻ, tất cả các đường dẫn 'null' (chuỗi trống hoặc chỉ có khoảng trắng) có thể được xóa bằng cách sử dụng:
PATH_nonulls = [s for s in PATH if s.strip()]
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())))
def DelDupes(aseq) :
seen = set()
return [x for x in aseq if (x.lower() not in seen) and (not seen.add(x.lower()))]
PATH ban đầu có cả mục nhập 'null' và trùng lặp cho mục đích thử nghiệm:
[list] Root paths in HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Environment:PATH[list] Root paths in HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Environment
1 C:\Python37\
2
3
4 C:\Python37\Scripts\
5 c:\python37\
6 C:\Program Files\ImageMagick-7.0.8-Q8
7 C:\Program Files (x86)\poppler\bin
8 D:\DATA\Sounds
9 C:\Program Files (x86)\GnuWin32\bin
10 C:\Program Files (x86)\Intel\iCLS Client\
11 C:\Program Files\Intel\iCLS Client\
12 D:\DATA\CCMD\FF
13 D:\DATA\CCMD
14 D:\DATA\UTIL
15 C:\
16 D:\DATA\UHELP
17 %SystemRoot%\system32
18
19
20 D:\DATA\CCMD\FF%SystemRoot%
21 D:\DATA\Sounds
22 %SystemRoot%\System32\Wbem
23 D:\DATA\CCMD\FF
24
25
26 c:\
27 %SYSTEMROOT%\System32\WindowsPowerShell\v1.0\
28
Các đường dẫn rỗng đã bị xóa, nhưng vẫn có các bản sao, ví dụ: (1, 3) và (13, 20):
[list] Null paths removed from HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Environment:PATH
1 C:\Python37\
2 C:\Python37\Scripts\
3 c:\python37\
4 C:\Program Files\ImageMagick-7.0.8-Q8
5 C:\Program Files (x86)\poppler\bin
6 D:\DATA\Sounds
7 C:\Program Files (x86)\GnuWin32\bin
8 C:\Program Files (x86)\Intel\iCLS Client\
9 C:\Program Files\Intel\iCLS Client\
10 D:\DATA\CCMD\FF
11 D:\DATA\CCMD
12 D:\DATA\UTIL
13 C:\
14 D:\DATA\UHELP
15 %SystemRoot%\system32
16 D:\DATA\CCMD\FF%SystemRoot%
17 D:\DATA\Sounds
18 %SystemRoot%\System32\Wbem
19 D:\DATA\CCMD\FF
20 c:\
21 %SYSTEMROOT%\System32\WindowsPowerShell\v1.0\
Và cuối cùng, bản sao đã bị xóa:
[list] Massaged path list from in HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Environment:PATH
1 C:\Python37\
2 C:\Python37\Scripts\
3 C:\Program Files\ImageMagick-7.0.8-Q8
4 C:\Program Files (x86)\poppler\bin
5 D:\DATA\Sounds
6 C:\Program Files (x86)\GnuWin32\bin
7 C:\Program Files (x86)\Intel\iCLS Client\
8 C:\Program Files\Intel\iCLS Client\
9 D:\DATA\CCMD\FF
10 D:\DATA\CCMD
11 D:\DATA\UTIL
12 C:\
13 D:\DATA\UHELP
14 %SystemRoot%\system32
15 D:\DATA\CCMD\FF%SystemRoot%
16 %SystemRoot%\System32\Wbem
17 %SYSTEMROOT%\System32\WindowsPowerShell\v1.0\
def check_duplicates(my_list):
seen = {}
for item in my_list:
if seen.get(item):
return True
seen[item] = True
return False