Python'daki iki sıralanmamış listeyi (küme değil) verimli bir şekilde nasıl karşılaştırırım?


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

a & b eşit kabul edilmelidir, çünkü tam olarak aynı unsurlara sahiptirler, sadece farklı sırayla.

Mesele şu ki, gerçek listelerim tamsayılardan değil nesnelerden (sınıf örneklerim) oluşacaktır.


7
Nesneler nasıl karşılaştırılır?
Marcelo Cantos

2
gerçek listelerin beklenen büyüklüğü nedir? Karşılaştırılan listeler karşılaştırılabilir boyutlarda mı yoksa çok farklı mı olacak? Çoğu listenin eşleşmesini bekler misiniz?
Dmitry B.19

len()Önce s kontrol edilebilir .
greybeard

Yanıtlar:


245

O (n) : Counter () yöntemi en iyisidir (nesneleriniz yıkanabilirse):

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

O (n günlüğü n) : Sort () yöntemi en iyisidir (nesneleriniz düzenlenebilirse):

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

O (n * n) : Nesneler ne yıkanabilir ne de düzenlenemezse eşitliği kullanabilirsiniz:

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
Teşekkür ederim. Her nesneyi bir dizeye dönüştürdükten sonra Counter () yöntemini kullandım.
johndir

Hey @Raymond, bu soruya son zamanlarda bir röportajda rastladım ve sorted()kuşkusuz bilmeden kullandım Counter. Görüşmeci daha etkili bir yöntem olduğu konusunda ısrar etti ve açıkça bir boşluk çizdim. timeitModül ile python 3'te kapsamlı testlerden sonra , tamsayı listelerinde sürekli olarak daha hızlı çıkar. 1k öğe listelerinde, yaklaşık% 1.5 daha yavaş ve kısa listelerde 10 öğe,% 7.5 daha yavaş. Düşünceler?
arctelix

4
Kısa listeler için, big-O analizi genellikle önemsizdir, çünkü zamanlamalara sabit faktörler hakimdir. Daha uzun listeler için, karşılaştırmanızda bir sorun var. Her biri 5 tekrarlı 100 ints için şunu elde ederim: sıralanmış için 127 usec ve Counter için 42 (yaklaşık 3 kat daha hızlı). 5 tekrarlı 1.000 ints'de Counter 4 kat daha hızlıdır. 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 Gerçekten farklı sonuçlar alıyoruz. Kurulumumu bir sohbet odasına gönderdim sorted vs counter.. Burada neler olduğunu merak ediyorum.
arctelix

4
Hayır teşekkürler. Sahte zamanlama komut dosyalarında hata ayıklamakla fazla ilgilenmiyorum. Burada çok şey var (saf python vs C kodu, yarı sıralı verilere karşı rasgele verilere uygulanan timsort, sürümlerde farklı uygulama ayrıntıları, verilerde kaç kopya var, vb.)
Raymond Hettinger

16

Her ikisini de sıralayabilirsiniz:

sorted(a) == sorted(b)

Bir sayma sıralaması da daha verimli olabilir (ancak nesnenin yıkanabilir olmasını gerektirir).

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

Sayaç karma kullanır, ancak nesneler kendiliğinden paylaşılamaz. Sadece mantıklı bir uygulama yapmanız gerekir __hash__, ancak bu koleksiyonlar için imkansız olabilir.
Jochen Ritzel

2
sıralanmış her şey için de çalışmaz, örneğin karmaşık sayılarsorted([0, 1j])
John La Rooy

1
sort (), karşılaştırma işleçlerinin alt küme / üst küme sınamaları için geçersiz kılındığı kümelerle de çalışmaz.
Raymond Hettinger

12

Maddelerin her zaman yıkanabileceğini biliyorsanız Counter(), O (n) 'yi
kullanabilirsiniz. Maddelerin her zaman sıralanabilir olduğunu biliyorsanız sorted(), O (n log n)' yi kullanabilirsiniz.

Genel durumda, sıralayabileceğinize veya öğelere sahip olamayacağınıza güvenemezsiniz, bu yüzden böyle bir geri dönüşe ihtiyacınız var, maalesef O (n ^ 2)

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

5

Bunu yapmanın en iyi yolu listeleri sıralamak ve karşılaştırmaktır. (Kullanılması Counter, yıkanamayan nesnelerle çalışmaz.) Bu, tamsayılar için basittir:

sorted(a) == sorted(b)

Keyfi nesnelerle biraz daha karmaşıklaşıyor. Nesne kimliğini, yani her iki listede de aynı nesnelerin olup olmadığını önemsiyorsanız , id()işlevi sıralama anahtarı olarak kullanabilirsiniz .

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

(Python 2.x'te aslında key= parametreye , çünkü herhangi bir nesneyi herhangi bir nesneyle karşılaştırabilirsiniz. Sıralama keyfi ancak sabittir, bu nedenle bu amaç için iyi çalışır; nesnelerin hangi sırada olduğu önemli değildir. Ancak, Python 3'te, farklı türlerdeki nesneleri karşılaştırmak pek çok durumda izin verilmez - örneğin, dizeleri tamsayılarla karşılaştıramazsınız - yani nesneleriniz varsa çeşitli türlerde, nesnenin kimliğini açıkça kullanmak için en iyisidir.)

Öte yandan listedeki nesneleri değere göre karşılaştırmak istiyorsanız , önce nesneler için "değer" in ne anlama geldiğini tanımlamanız gerekir. O zaman bunu bir anahtar olarak (ve Python 3 için tutarlı bir tür olarak) sağlamanın bir yoluna ihtiyacınız olacak. Birçok keyfi nesne için işe yarayabilecek potansiyel bir yol, bunlara göre sıralamaktır repr(). Tabii ki, bu repr()büyük listeler ve benzeri için çok fazla zaman ve hafıza oluşturma dizeleri harcayabilir .

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

Nesnelerin tümü kendi türünüzdeyse, __lt__()nesnenin kendisini başkalarıyla nasıl karşılaştıracağını bilmesi için bunları tanımlayabilirsiniz . Sonra onları sıralayabilir ve key=parametre hakkında endişelenmeyebilirsiniz . Tabii ki daha hızlı olacak olan tanımlayabilir __hash__()ve kullanabilirsiniz Counter.


4

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

assertCountEqual (birinci, ikinci, msg = Yok)

Bu sıranın, sıralarına bakılmaksızın ikinci olarak aynı öğeleri içerdiğini test edin. Yapmadıkları zaman, sekanslar arasındaki farkları listeleyen bir hata mesajı üretilecektir.

Birinci ve ikinci karşılaştırılırken yinelenen öğeler yok sayılmaz. Her bir öğenin her iki dizide de aynı sayıya sahip olup olmadığını doğrular. Eşdeğer: assertEqual (Sayaç (liste (ilk)), Sayaç (liste (ikinci))) ancak paylaşılamayan nesnelerin dizileriyle de çalışır.

Sürüm 3.2'deki yenilikler.

veya 2.7'de: https://docs.python.org/2.7/library/unittest.html#unittest.TestCase.assertItemsEqual



3

Liste, yıkanamayan öğeler (nesnelerin listesi gibi) içeriyorsa, Sayaç Sınıfı ve id () işlevini aşağıdaki gibi kullanabilirsiniz:

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

2

Umarım aşağıdaki kod parçası sizin durumunuzda işe yarayabilir: -

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

Bu listelerin hem tüm unsurları sağlayacaktır a& bbakılmaksızın aynı sırada olup olmadıklarını arasında aynıdır.

Daha iyi anlamak için bu sorudaki cevabıma bakın



1

A, b listeleri olsun

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

Onları yıkanabilir hale getirmeye veya sıralamaya gerek yok.


1
Evet, ancak bu diğer posterlerin de belirttiği gibi O (n ** 2) 'dir, bu nedenle yalnızca diğer yöntemler işe yaramazsa kullanılmalıdır. Aynı zamanda adesteklerin pop(değiştirilebilir) ve index(bir sekans olduğunu) varsayar . Raymond'un hiçbiri varsaymazken, gnibbler sadece bir dizi üstlenmez.
agf

0

unittestModülü kullanmak size temiz ve standart bir yaklaşım sağlar.

import unittest

test_object = unittest.TestCase()
test_object.assertCountEqual(a, b)
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.