Làm thế nào để so sánh hiệu quả hai danh sách không có thứ tự (không phải bộ) trong Python?


141
a = [1, 2, 3, 1, 2, 3]
b = [3, 2, 1, 3, 2, 1]

a & b nên được coi là bằng nhau, bởi vì chúng có chính xác các yếu tố giống nhau, chỉ theo thứ tự khác nhau.

Vấn đề là, danh sách thực tế của tôi sẽ bao gồm các đối tượng (thể hiện lớp của tôi), không phải số nguyên.


7
Làm thế nào là các đối tượng so sánh?
Marcelo Cantos

2
kích thước dự kiến ​​của danh sách thực sự là gì? Các danh sách được so sánh sẽ có kích thước tương đương hoặc rất khác nhau? Bạn có mong đợi hầu hết các danh sách phù hợp hay không?
Dmitry B.

Người ta có thể kiểm tra len()s trước.
greybeard

Câu trả lời:


245

O (n) : Phương thức Counter () là tốt nhất (nếu các đối tượng của bạn có thể băm):

def compare(s, t):
    return Counter(s) == Counter(t)

O (n log n) : Phương thức sort () là tiếp theo tốt nhất (nếu các đối tượng của bạn có thứ tự):

def compare(s, t):
    return sorted(s) == sorted(t)

O (n * n) : Nếu các đối tượng không thể băm, cũng không thể sắp xếp theo thứ tự, bạn có thể sử dụng đẳng thức:

def compare(s, t):
    t = list(t)   # make a mutable copy
    try:
        for elem in s:
            t.remove(elem)
    except ValueError:
        return False
    return not t

1
Cảm ơn bạn. Tôi đã chuyển đổi từng đối tượng thành một chuỗi sau đó sử dụng phương thức Counter ().
johndir

Này @Raymond, gần đây tôi đã gặp câu hỏi này trong một cuộc phỏng vấn và tôi đã sử dụng sorted(), thừa nhận không biết về nó Counter. Người phỏng vấn nhấn mạnh rằng có một phương pháp hiệu quả hơn và rõ ràng tôi đã vẽ một khoảng trống. Sau khi thử nghiệm rộng rãi trong python 3 với timeitmô-đun, sắp xếp liên tục xuất hiện nhanh hơn trong danh sách các số nguyên. Trên danh sách, 1k mặt hàng, chậm hơn khoảng 1,5% và trên danh sách ngắn, 10 mặt hàng, chậm hơn 7,5%. Suy nghĩ?
arctelix

4
Đối với các danh sách ngắn, phân tích big-O thường không liên quan vì thời gian bị chi phối bởi các yếu tố không đổi. Đối với các danh sách dài hơn, tôi nghi ngờ có điều gì đó không đúng với điểm chuẩn của bạn. Đối với 100 ints với 5 lần lặp lại mỗi lần, tôi nhận được: 127 usec cho sắp xếp và 42 cho Counter (nhanh hơn khoảng 3 lần). Với 1.000 ints với 5 lần lặp lại, Counter nhanh hơn gấp 4 lần. python3.6 -m timeit -s 'from collections import Counter' -s 'from random import shuffle' -s 't=list(range(100)) * 5' -s 'shuffle(t)' -s 'u=t[:]' -s 'shuffle(u)' 'Counter(t)==Counter(u)'
Raymond Hettinger

@Raymond Thật vậy, chúng tôi nhận được kết quả khác nhau. Tôi đã đăng thiết lập của mình lên một phòng chat sorted vs counter.. Tôi rất tò mò về những gì đang diễn ra ở đây.
arctelix

4
Không, cám ơn. Tôi không có nhiều hứng thú trong việc gỡ lỗi các kịch bản thời gian giả. Có rất nhiều thứ đang diễn ra ở đây (mã trăn thuần túy so với mã C, timsort được áp dụng cho dữ liệu ngẫu nhiên so với dữ liệu bán theo thứ tự, các chi tiết triển khai khác nhau giữa các phiên bản, có bao nhiêu bản sao trong dữ liệu, v.v.)
Raymond Hettinger

16

Bạn có thể sắp xếp cả hai:

sorted(a) == sorted(b)

Một kiểu đếm cũng có thể hiệu quả hơn (nhưng nó đòi hỏi đối tượng phải có khả năng băm).

>>> from collections import Counter
>>> a = [1, 2, 3, 1, 2, 3]
>>> b = [3, 2, 1, 3, 2, 1]
>>> print (Counter(a) == Counter(b))
True

Bộ đếm không sử dụng băm, nhưng các đối tượng không thể xóa được. Bạn chỉ cần thực hiện một điều hợp lý __hash__, nhưng điều đó có thể là không thể đối với các bộ sưu tập.
Jochen Ritzel

2
sắp xếp sẽ không hoạt động cho tất cả mọi thứ, ví dụ như số phứcsorted([0, 1j])
John La Rooy

1
sort () cũng không hoạt động với các tập hợp trong đó các toán tử so sánh đã bị ghi đè cho các kiểm tra tập hợp con / superset.
Raymond Hettinger

12

Nếu bạn biết các mục luôn có thể băm, bạn có thể sử dụng một mục Counter()là O (n)
Nếu bạn biết các mục luôn có thể sắp xếp, bạn có thể sử dụng sorted()đó là O (n log n)

Trong trường hợp chung, bạn không thể dựa vào việc có thể sắp xếp hoặc có các yếu tố, vì vậy bạn cần một dự phòng như thế này, điều không may là O (n ^ 2)

len(a)==len(b) and all(a.count(i)==b.count(i) for i in a)

5

Cách tốt nhất để làm điều này là bằng cách sắp xếp các danh sách và so sánh chúng. (Sử dụng Countersẽ không hoạt động với các đối tượng không thể băm được.) Điều này rất đơn giản đối với số nguyên:

sorted(a) == sorted(b)

Nó được một chút phức tạp hơn với các đối tượng tùy ý. Nếu bạn quan tâm đến danh tính đối tượng, tức là, liệu các đối tượng giống nhau có trong cả hai danh sách hay không, bạn có thể sử dụng id()hàm làm khóa sắp xếp.

sorted(a, key=id) == sorted(b, key==id)

(Trong Python 2.x bạn không thực sự cần key=tham số, bởi vì bạn có thể so sánh bất kỳ đối tượng nào với bất kỳ đối tượng nào. Thứ tự là tùy ý nhưng ổn định, vì vậy nó hoạt động tốt cho mục đích này; trong đó, chỉ có thứ tự là giống nhau cho cả hai danh sách. Tuy nhiên, trong Python 3, việc so sánh các đối tượng thuộc các loại khác nhau không được phép trong nhiều trường hợp - ví dụ: bạn không thể so sánh các chuỗi với số nguyên - vì vậy nếu bạn sẽ có các đối tượng thuộc nhiều loại khác nhau, tốt nhất là sử dụng rõ ràng ID của đối tượng.)

Nếu bạn muốn so sánh các đối tượng trong danh sách theo giá trị, mặt khác, trước tiên bạn cần xác định "giá trị" nghĩa là gì đối với các đối tượng. Sau đó, bạn sẽ cần một số cách để cung cấp khóa đó (và cho Python 3, dưới dạng một loại nhất quán). Một cách tiềm năng có thể làm việc cho nhiều đối tượng tùy ý là sắp xếp theo chúng repr(). Tất nhiên, điều này có thể lãng phí rất nhiều thời gian và repr()chuỗi xây dựng bộ nhớ cho các danh sách lớn, v.v.

sorted(a, key=repr) == sorted(b, key==repr)

Nếu các đối tượng là tất cả các loại của riêng bạn, bạn có thể xác định __lt__()trên chúng để đối tượng biết cách so sánh chính nó với các đối tượng khác. Sau đó, bạn có thể chỉ cần sắp xếp chúng và không lo lắng về key=tham số. Tất nhiên bạn cũng có thể xác định __hash__()và sử dụng Counter, sẽ nhanh hơn.


4

https://docs.python.org/3.5/l Library / unittest.html # unittest.TestCase.assertCountEqual

assertCountEqual (thứ nhất, thứ hai, thông điệp = Không)

Kiểm tra chuỗi đó trước tiên chứa các phần tử giống như thứ hai, bất kể thứ tự của chúng. Khi họ không làm như vậy, một thông báo lỗi liệt kê sự khác biệt giữa các chuỗi sẽ được tạo ra.

Các yếu tố trùng lặp không được bỏ qua khi so sánh thứ nhất và thứ hai. Nó xác minh xem mỗi phần tử có cùng số đếm trong cả hai chuỗi. Tương đương với: assertEqual (Counter (danh sách (đầu tiên)), Counter (danh sách (thứ hai))) nhưng cũng hoạt động với chuỗi các đối tượng không thể xóa được.

Mới trong phiên bản 3.2.

hoặc trong 2.7: https://docs.python.org/2.7/l Library / unittest.html # unittest.TestCase.assertItemsEqual



3

Nếu danh sách chứa các mục không thể băm (chẳng hạn như danh sách các đối tượng), bạn có thể sử dụng lớp Counter và hàm id (), chẳng hạn như:

from collections import Counter
...
if Counter(map(id,a)) == Counter(map(id,b)):
    print("Lists a and b contain the same objects")

2

Tôi hy vọng đoạn mã dưới đây có thể hoạt động trong trường hợp của bạn: -

if ((len(a) == len(b)) and
   (all(i in a for i in b))):
    print 'True'
else:
    print 'False'

Điều này sẽ đảm bảo rằng tất cả các yếu tố trong cả hai danh sách a& bđều giống nhau, bất kể chúng có cùng thứ tự hay không.

Để hiểu rõ hơn, hãy tham khảo câu trả lời của tôi trong câu hỏi này



1

Hãy để danh sách a, b

def ass_equal(a,b):
try:
    map(lambda x: a.pop(a.index(x)), b) # try to remove all the elements of b from a, on fail, throw exception
    if len(a) == 0: # if a is empty, means that b has removed them all
        return True 
except:
    return False # b failed to remove some items from a

Không cần phải làm cho chúng có thể băm hoặc sắp xếp chúng.


1
Có nhưng đây là O (n ** 2) như một số áp phích khác đã nêu, vì vậy chỉ nên được sử dụng nếu các phương pháp khác không hoạt động. Nó cũng giả sử các ahỗ trợ pop(có thể thay đổi) và index(là một chuỗi). Raymond giả định không trong khi gnibbler chỉ giả định một chuỗi.
agf

0

Sử dụng unittestmô-đun cung cấp cho bạn một cách tiếp cận sạch sẽ và tiêu chuẩn.

import unittest

test_object = unittest.TestCase()
test_object.assertCountEqual(a, b)
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.