Listelerin python'daki herhangi bir öğeyi paylaşıp paylaşmadığını test edin


132

Bir listedeki öğelerden herhangi birinin başka bir listede olup olmadığını kontrol etmek istiyorum . Bunu basitçe aşağıdaki kodla yapabilirim, ancak bunu yapacak bir kütüphane işlevi olabileceğinden şüpheleniyorum. Değilse, aynı sonucu elde etmenin daha pitonik bir yöntemi var mı?

In [78]: a = [1, 2, 3, 4, 5]

In [79]: b = [8, 7, 6]

In [80]: c = [8, 7, 6, 5]

In [81]: def lists_overlap(a, b):
   ....:     for i in a:
   ....:         if i in b:
   ....:             return True
   ....:     return False
   ....: 

In [82]: lists_overlap(a, b)
Out[82]: False

In [83]: lists_overlap(a, c)
Out[83]: True

In [84]: def lists_overlap2(a, b):
   ....:     return len(set(a).intersection(set(b))) > 0
   ....: 

Aklıma gelen tek optimizasyonlar düşüyorsa len(...) > 0çünkü bool(set([]))verimleri false. Ve elbette listelerinizi başlangıçta kümeler halinde tutarsanız, set oluşturma ek yükünü kaydedersiniz.
msw


1
Not sizi farklı olmayanlar şeklinde Truegelen 1ve Falsegelen 0. not set([1]).isdisjoint([True])alır Truediğer çözeltiler için de aynı.
Dimali

Yanıtlar:


315

Kısa cevap : kullanın not set(a).isdisjoint(b), genellikle en hızlısıdır.

İki liste aolup olmadığını test etmenin ve bherhangi bir öğeyi paylaşmanın dört yaygın yolu vardır . İlk seçenek, her ikisini de kümelere dönüştürmek ve kesişimlerini kontrol etmektir, örneğin:

bool(set(a) & set(b))

Çünkü setleri onları olduğu arama, Python bir karma tablo kullanılarak depolanırO(1) (bkz burada Python operatörleri karmaşıklığı hakkında daha fazla bilgi için). Teorik olarak, bu O(n+m)ortalama üzerinde nve mlistelerde nesneleri ave b. Ancak 1) önce listelerden ihmal edilemeyecek bir süre alabilen kümeler oluşturmalıdır ve 2) karma çarpışmaların verileriniz arasında seyrek olduğunu varsayar.

Bunu yapmanın ikinci yolu, listelerde yineleme yapan bir üretici ifadesi kullanmaktır, örneğin:

any(i in a for i in b)

Bu, yerinde aramaya izin verir, böylece ara değişkenler için yeni bellek tahsis edilmez. Aynı zamanda ilk bulgudan kurtulur. Ancak inoperatör her zaman O(n)listelerde bulunur ( buraya bakın ).

Önerilen diğer bir seçenek ise, listeden birini yinelemek, diğerini bir sette dönüştürmek ve bu sette üyeliği test etmek için hibrittir, örneğin:

a = set(a); any(i in a for i in b)

Dördüncü bir yaklaşım, isdisjoint()(dondurulmuş) kümelerin yönteminden yararlanmaktır ( buraya bakın ), örneğin:

not set(a).isdisjoint(b)

Aradığınız elemanlar bir dizinin başlangıcına yakınsa (örneğin sıralanırsa), setler kesişimi yönteminin ara değişkenler için yeni bellek ayırması gerektiğinden, oluşturucu ifadesi tercih edilir:

from timeit import timeit
>>> timeit('bool(set(a) & set(b))', setup="a=list(range(1000));b=list(range(1000))", number=100000)
26.077727576019242
>>> timeit('any(i in a for i in b)', setup="a=list(range(1000));b=list(range(1000))", number=100000)
0.16220548999262974

Liste boyutuna bağlı olarak bu örnek için yürütme süresinin grafiğini aşağıda bulabilirsiniz:

Başlangıçta paylaşıldığında öğe paylaşımı test yürütme süresi

Her iki eksenin de logaritmik olduğuna dikkat edin. Bu, üretici ifadesi için en iyi durumu temsil eder. Görüldüğü gibi, isdisjoint()yöntem çok küçük liste boyutları için daha iyidir, oysa oluşturucu ifadesi daha büyük liste boyutları için daha iyidir.

Öte yandan, arama hibrit ve üretici ifadesinin başlangıcıyla başladığından, eğer paylaşılan eleman sistematik olarak dizinin sonunda ise (veya her iki liste de herhangi bir değeri paylaşmıyorsa), ayrık ve küme kesişim yaklaşımları o zaman jeneratör ifadesi ve hibrit yaklaşımdan çok daha hızlı.

>>> timeit('any(i in a for i in b)', setup="a=list(range(1000));b=[x+998 for x in range(999,0,-1)]", number=1000))
13.739536046981812
>>> timeit('bool(set(a) & set(b))', setup="a=list(range(1000));b=[x+998 for x in range(999,0,-1)]", number=1000))
0.08102107048034668

Sonunda paylaşıldığında öğe paylaşımı test yürütme süresi

Daha büyük liste boyutları için oluşturucu ifadesinin çok daha yavaş olduğuna dikkat etmek ilginçtir. Bu, önceki rakam için 100000 yerine sadece 1000 tekrar içindir. Bu kurulum aynı zamanda hiçbir öğe paylaşılmadığında iyi bir şekilde yaklaşır ve ayrık ve kesişme yaklaşımları için en iyi durumdur.

İşte rastgele sayılar kullanan iki analiz (bir tekniği veya diğerini tercih etmek için kurulumu karıştırmak yerine):

Paylaşma şansı yüksek olan rastgele oluşturulan veriler için öğe paylaşım testi yürütme süresi Paylaşma şansı yüksek olan rastgele oluşturulan veriler için öğe paylaşım testi yürütme süresi

Yüksek paylaşma şansı: öğelerden rastgele alınır [1, 2*len(a)]. Düşük paylaşım şansı: öğelerden rastgele alınır [1, 1000*len(a)].

Şimdiye kadar, bu analiz her iki listenin de aynı büyüklükte olduğunu varsayıyordu. Farklı boyutlarda iki liste olması durumunda, örneğin açok daha küçükse, isdisjoint()her zaman daha hızlıdır:

Başlangıçta paylaşıldığında, iki farklı boyutlu listede öğe paylaşımı testi yürütme süresi Sonunda paylaşıldığında, iki farklı boyutlu listede öğe paylaşımı testi yürütme süresi

aListenin daha küçük olduğundan emin olun , aksi takdirde performans düşer. Bu deneyde, aliste boyutu olarak sabit ayarlandı 5.

Özetle:

  • Listeler çok küçükse (<10 öğe), not set(a).isdisjoint(b)her zaman en hızlısıdır.
  • Listelerdeki öğeler sıralanırsa veya yararlanabileceğiniz düzenli bir yapıya sahipse, oluşturucu ifadesi any(i in a for i in b)büyük liste boyutlarında en hızlısıdır;
  • not set(a).isdisjoint(b)Her zaman daha hızlı olan küme kesişimini test edin bool(set(a) & set(b)).
  • Karma "listeden yineleme, sette test etme" a = set(a); any(i in a for i in b)genellikle diğer yöntemlerden daha yavaştır.
  • Oluşturucu ifadesi ve melez, öğeleri paylaşmadan listeler söz konusu olduğunda diğer iki yaklaşımdan çok daha yavaştır.

Çoğu durumda, isdisjoint()hiçbir öğe paylaşılmadığında çok verimsiz olduğundan, oluşturucu ifadenin yürütülmesi çok daha uzun süreceği için yöntemi kullanmak en iyi yaklaşımdır.


8
Bu, bazı yararlı veriler, büyük-O analizinin her şeyden ibaret olmadığını ve çalışma süresiyle ilgili tüm mantığın sona erdiğini gösteriyor.
Steve Allison

en kötü durum senaryosu ne olacak? anyİlk Yanlış olmayan değerde çıkar. Sonunda eşleşen tek değerin olduğu bir liste kullanarak şunu elde ederiz: timeit('any(i in a for i in b)', setup="a=list(range(1000));b=[x+998 for x in range(999,-0,-1)]", number=1000) 13.739536046981812 timeit('bool(set(a) & set(b))', setup="a=list(range(1000));b=[x+998 for x in range(999,-0,-1)]", number=1000) 0.08102107048034668 ... ve yalnızca 1000 yineleme ile.
RobM

2
Bilgi için teşekkürler @ RobM. Cevabımı bunu yansıtacak ve bu ileti dizisinde önerilen diğer teknikleri dikkate alacak şekilde güncelledim.
Soravux

not set(a).isdisjoint(b)İki listenin bir üyeyi paylaşıp paylaşmadığını test etmek gerekir . set(a).isdisjoint(b)döndürür Trueİki liste yoksa değil bir üyeyi paylaşır. Cevap düzenlenmeli mi?
Guillochon

1
Uyarılar için teşekkürler @ Guillochon, düzeltildi.
Soravux

25
def lists_overlap3(a, b):
    return bool(set(a) & set(b))

Not: Yukarıdakiler, yanıt olarak bir boole istediğinizi varsayar. İhtiyacınız olan tek şey bir ifadede kullanılacak bir ififadeyse, sadeceif set(a) & set(b):


5
Bu en kötü durum O (n + m). Bununla birlikte, olumsuz tarafı, yeni bir set oluşturması ve ortak bir unsur erken bulunduğunda kurtarılmamasıdır.
Matthew Flaschen

1
Bunun neden olduğunu merak ediyorum O(n + m). Tahminimce setler karma tablolar kullanılarak gerçekleştirilir ve bu nedenle inoperatör O(1)zamanında çalışabilir (dejenere durumlar hariç). Bu doğru mu? Eğer öyleyse, hash tablolarının en kötü durum arama performansına sahip olduğu göz önüne alındığında, bu, daha kötü durumdan O(n)farklı olarak O(n * m)performansa sahip olacağı anlamına mı gelir?
fmark

1
@fmark: Teorik olarak haklısın. Pratik olarak kimsenin umurunda değil; CPython kaynağındaki Objects / dictobject.c'deki yorumları okuyun (setler sadece anahtarlar içerir, değerler yoktur) ve O (n) arama performansına neden olacak bir anahtar listesi oluşturup oluşturamayacağınıza bakın.
John Machin

Tamam, açıkladığınız için teşekkürler, biraz sihir olup olmadığını merak ediyordum :). Pratikte önemsememe gerek olmadığını kabul etsem de O(n), arama performansına neden olacak bir anahtar listesi oluşturmak önemsizdir ;), pastebin.com/Kn3kAW7u Just for lafs'a bakın.
fmark

2
Evet biliyorum. Artı, bana gösterdiğin kaynağı okudum, bu, rastgele olmayan hash işlevlerinde (yerleşik işlev gibi) daha da sihir belgeliyor. Bunun Java'daki gibi rastgelelik gerektirdiğini varsaydım, bu stackoverflow.com/questions/2634690/… gibi canavarlıklarla sonuçlanır . Python'un Java olmadığını kendime hatırlatmaya devam etmeliyim (Tanrıya şükür!).
fmark

10
def lists_overlap(a, b):
  sb = set(b)
  return any(el in sb for el in a)

Bu, asimptotik olarak optimaldir (en kötü durum O (n + m)) ve anykısa devre nedeniyle kavşak yaklaşımından daha iyi olabilir .

Örneğin:

lists_overlap([3,4,5], [1,2,3])

ulaşır ulaşmaz True döndürür 3 in sb

DÜZENLEME: Başka bir varyasyon (Dave Kirby sayesinde):

def lists_overlap(a, b):
  sb = set(b)
  return any(itertools.imap(sb.__contains__, a))

Bu imap, bir üreteç anlayışından çok, C'de uygulanan yineleyicisine dayanır . Ayrıca sb.__contains__eşleme işlevi olarak da kullanır . Bunun ne kadar performans farkı yarattığını bilmiyorum. Yine de kısa devre olacaktır.


1
Kesişim yaklaşımındaki döngülerin tümü C kodundadır; Yaklaşımınızda Python kodunu içeren bir döngü var. Büyük bilinmeyen, boş bir kavşağın muhtemel olup olmadığıdır.
John Machin

2
any(itertools.imap(sb.__contains__, a))Lambda işlevini kullanmaktan kaçındığı için hangisinin daha hızlı olması gerektiğini de kullanabilirsiniz .
Dave Kirby

Teşekkürler @Dave. :) Lambda'yı kaldırmanın bir kazanç olduğuna katılıyorum.
Matthew Flaschen

4

anyListe anlama ile de kullanabilirsiniz :

any([item in a for item in b])

6
Yapabilirsiniz, ancak zaman O (n * m) iken kesişme yaklaşımı için zaman O (n + m) 'dir. Bunu liste kavrama OLMADAN da yapabilirsiniz (kaybedersiniz []) ve daha hızlı çalışır ve daha az bellek kullanır, ancak zaman yine de O (n * m) olacaktır.
John Machin

1
Büyük O analiziniz doğru olsa da, küçük n ve m değerleri için temel hashtable'ları oluşturmak için gereken sürenin devreye gireceğinden şüpheleniyorum. Big O, karmaları hesaplamak için gereken süreyi dikkate almaz.
Anthony Conyers

2
Bir "hashtable" oluşturmak amortismana tabi tutulur O (n).
John Machin

1
Anlıyorum ama attığın sabit şey oldukça büyük. N'nin büyük değerleri önemli değil, küçük olanlar için önemli.
Anthony Conyers

3

Python 2.6 veya sonraki sürümlerde şunları yapabilirsiniz:

return not frozenset(a).isdisjoint(frozenset(b))

1
Görünüşe göre ilk argüman olarak bir set veya frozenset sağlamaya gerek yok. Bir dizeyle denedim ve işe yaradı (yani: herhangi bir yinelenebilir işe yarar).
Aktau

2

Herhangi bir yerleşik işlev / wa oluşturucu ifadesini kullanabilirsiniz:

def list_overlap(a,b): 
     return any(i for i in a if i in b)

John ve Lie'nin belirttiği gibi, bu iki liste tarafından paylaşılan her i için bool (i) == False olduğunda yanlış sonuçlar verir. Olmalı:

return any(i in b for i in a)

1
Lie Ryan'ın yorumunu güçlendirmek: Yanlış olan kesişimde bulunan herhangi bir x öğesi için yanlış sonuç verecektir bool(x). Lie Ryan'ın örneğinde, x 0'dır. Sadece düzeltme any(True for i in a if i in b), daha önce görüldüğü gibi daha iyi yazılmıştır any(i in b for i in a).
John Machin

1
Düzeltme: zaman yanlış sonuç verecektir tüm öğeler xkesiştiği yer şekildedir bool(x)olduğunu False.
John Machin

1

Bu soru oldukça eskidir, ancak insanlar kümelere karşı listeleri tartışırken kimsenin onları birlikte kullanmayı düşünmediğini fark ettim. Soravux örneğine göre,

Listeler için en kötü durum:

>>> timeit('bool(set(a) & set(b))',  setup="a=list(range(10000)); b=[x+9999 for x in range(10000)]", number=100000)
100.91506409645081
>>> timeit('any(i in a for i in b)', setup="a=list(range(10000)); b=[x+9999 for x in range(10000)]", number=100000)
19.746716022491455
>>> timeit('any(i in a for i in b)', setup="a= set(range(10000)); b=[x+9999 for x in range(10000)]", number=100000)
0.092626094818115234

Ve listeler için en iyi durum:

>>> timeit('bool(set(a) & set(b))',  setup="a=list(range(10000)); b=list(range(10000))", number=100000)
154.69790101051331
>>> timeit('any(i in a for i in b)', setup="a=list(range(10000)); b=list(range(10000))", number=100000)
0.082653045654296875
>>> timeit('any(i in a for i in b)', setup="a= set(range(10000)); b=list(range(10000))", number=100000)
0.08434605598449707

Bu yüzden, iki listeyi yinelemekten bile daha hızlı, bir listenin bir kümede olup olmadığını görmek için yinelemektir; bu mantıklıdır, çünkü bir sayının bir kümede olup olmadığını kontrol etmek sabit zaman alırken, bir listeyi yineleyerek kontrol etmek, sürenin uzunluğuyla orantılı zaman alır. liste.

Bu nedenle, sonucum bir listeyi yinelemek ve bir küme içinde olup olmadığını kontrol etmek .


1
Kullanma isdisjoint()@Toughy ile gösterildiği gibi, bir (donmuş) set yöntemi daha iyi: timeit('any(i in a for i in b)', setup="a= set(range(10000)); b=[x+9999 for x in range(10000)]", number=100000)=> ,00913715362548828
Aktau

1

Örtüşen öğenin ne olabileceği umurunuzda değilse len, bir küme olarak birleştirilmiş listelerle karşılaştırıldığında birleşik listeyi kontrol edebilirsiniz . Örtüşen öğeler varsa, küme daha kısa olacaktır:

len(set(a+b+c))==len(a+b+c) Örtüşme yoksa True döndürür.


İlk değer çakışırsa, ne kadar büyük olursa olsun tüm listeyi yine de bir kümeye dönüştürür.
Peter Wood

1

İşlevsel programlama stiline sahip bir tane daha ekleyeceğim:

any(map(lambda x: x in a, b))

Açıklama:

map(lambda x: x in a, b)

elemanları boole listesini döndürür bbulunur a. Bu liste daha sonra any, Trueherhangi bir öğe varsa , basitçe dönen'e aktarılır True.

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.