วิธีเปรียบเทียบรายการที่ไม่ได้เรียงลำดับสองรายการ (ไม่ได้ตั้งค่า) ใน Python อย่างมีประสิทธิภาพ


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

a & b ควรได้รับการพิจารณาอย่างเท่าเทียมกันเพราะพวกเขามีองค์ประกอบเดียวกันทุกประการเท่านั้น

สิ่งที่เป็นรายการจริงของฉันจะประกอบด้วยวัตถุ (อินสแตนซ์ชั้นเรียนของฉัน) ไม่ใช่จำนวนเต็ม


7
เปรียบเทียบวัตถุอย่างไร
Marcelo Cantos

2
ขนาดที่แท้จริงของรายการจริงคืออะไร รายการที่ถูกเปรียบเทียบจะมีขนาดใกล้เคียงกันหรือแตกต่างกันมาก คุณคาดหวังว่ารายการส่วนใหญ่จะจับคู่หรือไม่?
Dmitry B.

หนึ่งอาจตรวจสอบlen()ก่อน
greybeard

คำตอบ:


245

O (n) : วิธีการนับ ()เป็นวิธีที่ดีที่สุด (ถ้าวัตถุของคุณแฮช):

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

O (n log n) : วิธีการเรียง ()เป็นวิธีที่ดีที่สุดถัดไป (หากวัตถุของคุณสามารถสั่งซื้อได้):

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

O (n * n) : หากวัตถุนั้นไม่มีแฮชหรือไม่เป็นระเบียบคุณสามารถใช้ความเท่าเทียมกันได้:

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
ขอบคุณ. ฉันแปลงแต่ละวัตถุเป็นสตริงจากนั้นใช้วิธีการ Counter ()
johndir

เฮ้ @Raymond ฉันเพิ่งพบคำถามนี้ในการให้สัมภาษณ์และฉันใช้เป็นที่ยอมรับไม่ได้รู้เกี่ยวกับsorted() Counterผู้สัมภาษณ์ยืนยันว่ามีวิธีการที่มีประสิทธิภาพมากขึ้นและชัดเจนว่าฉันเขียนจดหมายเปล่า หลังจากการทดสอบอย่างกว้างขวางใน python 3 กับtimeitโมดูลการเรียงลำดับจะออกมาอย่างรวดเร็วในรายการจำนวนเต็ม ในรายการของรายการ 1k ช้าลงประมาณ 1.5% และในรายการสั้น ๆ 10 รายการช้ากว่า 7.5% คิด?
arctelix

4
สำหรับรายการสั้น ๆ การวิเคราะห์ Big-O มักไม่เกี่ยวข้องเนื่องจากการกำหนดเวลาถูกครอบงำโดยปัจจัยคงที่ สำหรับรายการที่ยาวขึ้นฉันสงสัยว่ามีบางอย่างผิดปกติกับการเปรียบเทียบของคุณ สำหรับ 100 ints กับ 5 ซ้ำแต่ละครั้งฉันได้รับ: 127 usec สำหรับการเรียงลำดับและ 42 สำหรับ Counter (เร็วขึ้นประมาณ 3 เท่า) ที่ 1,000 ints 5 ครั้งซ้ำ ๆ นับเร็วขึ้น 4x 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 แน่นอนว่าเราได้ผลลัพธ์ที่แตกต่าง ฉันโพสต์การตั้งค่าของฉันไปที่ห้องแชทsorted vs counter.. ฉันอยากรู้มากว่าเกิดอะไรขึ้นที่นี่
arctelix

4
ไม่เป็นไรขอบคุณ. ฉันไม่มีความสนใจในการดีบักสคริปต์กำหนดเวลาปลอม มีจำนวนมากเกิดขึ้นที่นี่ (รหัสหลามบริสุทธิ์กับ C, timsort ถูกนำไปใช้กับข้อมูลแบบสุ่มเทียบกับข้อมูลกึ่งสั่งซื้อ, รายละเอียดการใช้งานที่แตกต่างกันในแต่ละรุ่น, มีจำนวนซ้ำกันในข้อมูล ฯลฯ )
Raymond Hettinger

16

คุณสามารถจัดเรียงทั้งสอง:

sorted(a) == sorted(b)

การเรียงลำดับการนับอาจมีประสิทธิภาพมากกว่า (แต่ต้องการวัตถุที่แฮชได้)

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

ตัวนับใช้การแปลงแป้นพิมพ์ แต่วัตถุไม่สามารถล้างทำความสะอาดได้ คุณแค่ต้องใช้สติปัญญา__hash__แต่อาจเป็นไปไม่ได้สำหรับคอลเลกชัน
Jochen Ritzel

2
เรียงลำดับจะไม่ทำงานสำหรับทุกอย่างเช่นตัวเลขที่ซับซ้อนsorted([0, 1j])
John La Rooy

1
เรียงลำดับ () ยังใช้งานไม่ได้กับชุดที่ตัวดำเนินการเปรียบเทียบถูกเขียนทับสำหรับการทดสอบย่อย / ซูเปอร์เซ็ต
Raymond Hettinger

12

หากคุณรู้ว่ารายการต่าง ๆ นั้น hashable อยู่เสมอคุณสามารถใช้สิ่งCounter()ที่เป็น O (n)
หากคุณรู้ว่ารายการนั้นสามารถเรียงลำดับได้ตลอดเวลาคุณสามารถใช้sorted()สิ่งที่เป็น O (n log n)

ในกรณีทั่วไปคุณไม่สามารถพึ่งพาการเรียงลำดับหรือมีองค์ประกอบดังนั้นคุณต้องมีทางเลือกเช่นนี้ซึ่งน่าเสียดายที่ O (n ^ 2)

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

5

วิธีที่ดีที่สุดในการทำเช่นนี้คือการเรียงลำดับรายการและเปรียบเทียบ (การใช้Counterจะไม่ทำงานกับวัตถุที่ไม่สามารถแฮชได้) สิ่งนี้ตรงไปตรงมาสำหรับจำนวนเต็ม:

sorted(a) == sorted(b)

มันยากขึ้นเล็กน้อยเมื่อใช้กับวัตถุใด ๆ หากคุณสนใจเกี่ยวกับตัวตนของวัตถุเช่นว่าวัตถุเดียวกันอยู่ในทั้งสองรายการคุณสามารถใช้id()ฟังก์ชั่นเป็นคีย์การเรียงลำดับ

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

(ใน Python 2.x คุณไม่จำเป็นต้องใช้key=พารามิเตอร์จริงๆเพราะคุณสามารถเปรียบเทียบวัตถุใด ๆ กับวัตถุใด ๆ การจัดเรียงนั้นเป็นกฎเกณฑ์ แต่มีเสถียรภาพดังนั้นจึงทำงานได้ดีสำหรับจุดประสงค์นี้มันไม่สำคัญว่าวัตถุจะเรียงตามลำดับอย่างไร ในการเรียงลำดับจะเหมือนกันสำหรับทั้งสองรายการใน Python 3 แม้ว่าการเปรียบเทียบออบเจ็กต์ประเภทต่าง ๆ จะไม่ได้รับอนุญาตในหลาย ๆ สถานการณ์ - ตัวอย่างเช่นคุณไม่สามารถเปรียบเทียบสตริงกับจำนวนเต็ม - ดังนั้นหากคุณจะมีวัตถุ ประเภทต่างๆควรใช้ ID ของวัตถุอย่างชัดเจน)

หากคุณต้องการเปรียบเทียบวัตถุในรายการตามค่าในทางกลับกันอันดับแรกคุณต้องกำหนดความหมายของ "ค่า" สำหรับวัตถุ จากนั้นคุณจะต้องใช้วิธีในการระบุว่าเป็นคีย์ (และสำหรับ Python 3 เป็นประเภทที่สอดคล้องกัน) repr()วิธีการหนึ่งที่มีศักยภาพที่จะทำงานให้มากวัตถุโดยพลการคือการจัดเรียงของพวกเขา แน่นอนว่านี่อาจเสียเวลามากและการสร้างrepr()สายหน่วยความจำสำหรับรายการขนาดใหญ่และอื่น ๆ

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

หากวัตถุเป็นประเภทของคุณเองคุณสามารถกำหนด__lt__()วัตถุเหล่านั้นเพื่อให้วัตถุรู้วิธีเปรียบเทียบตัวเองกับผู้อื่น จากนั้นคุณสามารถเรียงลำดับและไม่ต้องกังวลเกี่ยวกับkey=พารามิเตอร์ แน่นอนคุณสามารถกำหนด__hash__()และใช้Counterซึ่งจะเร็วขึ้น


4

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

assertCountEqual (แรกวินาที msg = ไม่มี)

ทดสอบลำดับแรกนั้นมีองค์ประกอบเดียวกันกับที่สองโดยไม่คำนึงถึงลำดับ เมื่อไม่มีข้อความแสดงข้อผิดพลาดที่แสดงความแตกต่างระหว่างลำดับจะถูกสร้างขึ้น

องค์ประกอบที่ซ้ำกันจะไม่ถูกละเว้นเมื่อเปรียบเทียบก่อนและสอง ตรวจสอบว่าแต่ละองค์ประกอบมีจำนวนเท่ากันในทั้งสองลำดับ เทียบเท่ากับ: assertEqual (ตัวนับ (รายการ (แรก)), ตัวนับ (รายการ (ที่สอง))) แต่ทำงานกับลำดับของวัตถุที่ไม่สามารถล้างได้เช่นกัน

ใหม่ในเวอร์ชัน 3.2

หรือใน 2.7: https://docs.python.org/2.7/library/unittest.html#unittest.TestCase.assertItemsEqual


2
(สิ่งนี้จะเพิ่มคำตอบของ jarekwg ?)
greybeard

3

หากรายการมีรายการที่ไม่แฮช (เช่นรายการวัตถุ) คุณอาจสามารถใช้ฟังก์ชันCounter Classและ id () ได้เช่น:

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

2

ฉันหวังว่าโค้ดด้านล่างอาจใช้ได้ในกรณีของคุณ: -

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

สิ่งนี้จะช่วยให้แน่ใจว่าองค์ประกอบทั้งหมดในทั้งรายการa& bเหมือนกันโดยไม่คำนึงว่าอยู่ในลำดับเดียวกันหรือไม่

เพื่อความเข้าใจที่ดีขึ้นโปรดอ้างอิงคำตอบของฉันในคำถามนี้



1

ให้ 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

ไม่จำเป็นต้องทำให้พวกเขา hashable หรือจัดเรียงพวกเขา


1
ใช่ แต่นี่คือ O (n ** 2) ตามที่ผู้โพสต์อื่น ๆ ระบุไว้ดังนั้นควรใช้เฉพาะในกรณีที่วิธีการอื่นไม่ทำงาน นอกจากนี้ยังถือว่าaสนับสนุนpop(ไม่แน่นอน) และindex(เป็นลำดับ) เรย์มอนด์สันนิษฐานว่าไม่ใช่ในขณะที่ gnibbler ถือว่าเป็นลำดับ
agf

0

การใช้unittestโมดูลให้แนวทางที่สะอาดและเป็นมาตรฐานแก่คุณ

import unittest

test_object = unittest.TestCase()
test_object.assertCountEqual(a, b)
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.