Làm cách nào để kiểm tra nếu có bản sao trong danh sách phẳng?


184

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:


397

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

17
Trước khi đọc, tôi đã thử your_list! = List (set (your_list)) sẽ không hoạt động vì thứ tự của các phần tử sẽ thay đổi. Sử dụng len là một cách tốt để giải quyết vấn đề này
igniteflow

1
thường không hoạt động cho mảng các điểm nổi. Xem stackoverflow.com/questions/60914705
Manas Dogra

54

Đề 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 đó :-(.


21
Denis Otkidach đưa ra một giải pháp trong đó bạn chỉ cần xây dựng một bộ mới từ danh sách, sau đó kiểm tra độ dài của nó. Ưu điểm của nó là cho phép mã C bên trong Python thực hiện công việc nặng nhọc. Giải pháp của bạn lặp trong mã Python, nhưng có lợi thế là đoản mạch khi tìm thấy một kết quả khớp duy nhất. Nếu tỷ lệ cược là danh sách có thể không có bản sao, tôi thích phiên bản của Denis Otkidach, nhưng nếu tỷ lệ cược có thể có một bản sao sớm trong danh sách, giải pháp này tốt hơn.
steveha

1
Đáng giá cho chi tiết, mặc dù tôi nghĩ rằng Denis có giải pháp gọn gàng hơn.
Steve314

@steveha - Tối ưu hóa sớm?
Steve314

@ Steve314, tối ưu hóa sớm là gì? Tôi đã viết nó theo cách mà Denis Otkidach đã viết nó, vì vậy tôi đã cố gắng hiểu tại sao Alex Martelli (của danh tiếng Python Cookbook) lại viết nó khác đi. Sau khi tôi nghĩ về nó một chút, tôi nhận ra rằng phiên bản ngắn mạch của Alex và tôi đã đăng một vài suy nghĩ về sự khác biệt. Làm thế nào để bạn đi từ một cuộc thảo luận về sự khác biệt để tối ưu hóa sớm, căn nguyên của mọi tội lỗi?
steveha

3
Nếu các mục có thể băm, một giải pháp tập hợp trực tiếp hơn và theo cách tôi thể hiện, nhanh hơn (thoát ngay khi câu trả lời được biết - "ngắn mạch", steveha đặt nó). Xây dựng chính tả mà bạn đề xuất (nhanh nhất là một bộ sưu tập. Công cụ tìm kiếm) tất nhiên là chậm hơn rất nhiều (cần một alltổ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.
Alex Martelli

12

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

9

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

Nên đọc câu hỏi liên quan trước. Điều này được mô tả trong stackoverflow.com/questions/1723072/ trên
Xavier Decoret

1
Nó ném cho tôi một lỗi "cú pháp không hợp lệ" trên hàm lambda của decompose ()
raffaem

Đó là vì việc giải nén trong danh sách đối số lambda đã bị xóa trong Python 3.x.
MSeifert

5

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:

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

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:

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

Ở đâ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()

Để tham khảo: Chức năng all_distinct được viết bằng C .
người dùng

5

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

Cuộc .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 đó.
MSeifert

Ngoài ra ifilterìzipcó thể được thay thế đơn giản bằng tích hợp filterziptrong Python 3.x.
MSeifert

@MSeifert giải pháp hoạt động cho python 2.x như đã viết và vâng, đối với py3, bạn có thể sử dụng bộ lọc và ánh xạ trực tiếp ... nhưng ai đó sử dụng giải pháp py3 trong cơ sở mã py2 sẽ không nhận được lợi ích vì nó sẽ không hoạt động như một máy phát điện. Rõ ràng là tốt hơn so với ngầm trong trường hợp này;)
F1Rumors

3

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]

2
my_list = ['one', 'two', 'one']

duplicates = []

for value in my_list:
  if my_list.count(value) > 1:
    if value not in duplicates:
      duplicates.append(value)

print(duplicates) //["one"]

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

0

Tôi không thực sự biết những gì thiết lập đằng sau hậu trường, vì vậy tôi chỉ muốn giữ cho nó đơn giản.

def dupes(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

0

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

0

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

0

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\

0
def check_duplicates(my_list):
    seen = {}
    for item in my_list:
        if seen.get(item):
            return True
        seen[item] = True
    return False

Chức năng hoạt động như thế nào? Tôi tò mò về cách mà từ điển "nhìn thấy" được phổ biến.
Travis Hammond
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.