Python, bir listedeki birden çok değerin üyeliğini test edebilir mi?


122

İki veya daha fazla değerin bir listede üye olup olmadığını test etmek istiyorum, ancak beklenmeyen bir sonuç alıyorum:

>>> 'a','b' in ['b', 'a', 'foo', 'bar']
('a', True)

Öyleyse, Python bir listede aynı anda birden fazla değerin üyeliğini test edebilir mi? Bu sonuç ne anlama geliyor?

Yanıtlar:


198

Bu, istediğinizi yapar ve neredeyse her durumda işe yarar:

>>> all(x in ['b', 'a', 'foo', 'bar'] for x in ['a', 'b'])
True

'a','b' in ['b', 'a', 'foo', 'bar']Python bunu bir demet olarak yorumladığı için ifade beklendiği gibi çalışmıyor:

>>> 'a', 'b'
('a', 'b')
>>> 'a', 5 + 2
('a', 7)
>>> 'a', 'x' in 'xerxes'
('a', True)

Diğer seçenekler

Bu testi yürütmenin başka yolları da vardır, ancak bunlar pek çok farklı türde girdi için işe yaramayacaktır. Kabie'nin işaret ettiği gibi , bu sorunu setleri kullanarak çözebilirsiniz ...

>>> set(['a', 'b']).issubset(set(['a', 'b', 'foo', 'bar']))
True
>>> {'a', 'b'} <= {'a', 'b', 'foo', 'bar'}
True

...ara sıra:

>>> {'a', ['b']} <= {'a', ['b'], 'foo', 'bar'}
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'list'

Kümeler yalnızca hashable öğelerle oluşturulabilir. Ancak oluşturucu ifadesi all(x in container for x in items)hemen hemen her tür kap türünü işleyebilir. Tek gereksinim, containeryeniden yinelenebilir olmasıdır (yani bir jeneratör değil). itemsherhangi bir yinelenebilir olabilir.

>>> container = [['b'], 'a', 'foo', 'bar']
>>> items = (i for i in ('a', ['b']))
>>> all(x in [['b'], 'a', 'foo', 'bar'] for x in items)
True

Hız Testleri

Çoğu durumda, alt küme testi bundan daha hızlı olacaktır all, ancak fark şok edici değildir - sorunun alakasız olduğu durumlar hariç, çünkü kümeler bir seçenek değildir. Listeleri sırf böyle bir test amacıyla kümelere dönüştürmek her zaman zahmete değmeyecektir. Ve jeneratörleri setlere dönüştürmek bazen inanılmaz derecede savurgan olabilir ve programları birçok büyüklükte yavaşlatır.

İşte gösterim için birkaç kriter. En büyük fark, her ikisi de containerve itemsnispeten küçük olduğunda ortaya çıkar . Bu durumda, alt küme yaklaşımı bir kat daha hızlıdır:

>>> smallset = set(range(10))
>>> smallsubset = set(range(5))
>>> %timeit smallset >= smallsubset
110 ns ± 0.702 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
>>> %timeit all(x in smallset for x in smallsubset)
951 ns ± 11.5 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

Bu büyük bir fark gibi görünüyor. Ancak containerbir set olduğu sürece all, çok daha büyük ölçeklerde hala mükemmel bir şekilde kullanılabilir:

>>> bigset = set(range(100000))
>>> bigsubset = set(range(50000))
>>> %timeit bigset >= bigsubset
1.14 ms ± 13.9 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
>>> %timeit all(x in bigset for x in bigsubset)
5.96 ms ± 37 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

Alt küme testini kullanmak hala daha hızlı, ancak bu ölçekte yalnızca yaklaşık 5 kat. Hız artışı, Python'un hızlı cdestekli uygulamasından kaynaklanmaktadır set, ancak temel algoritma her iki durumda da aynıdır.

Eğer senin itemszaten başka nedenlerden dolayı bir listede saklanır, o zaman alt kümesi testi yaklaşımı kullanmadan önce bir dizi dönüştürmek gerekir. Ardından hızlanma yaklaşık 2,5x'e düşer:

>>> %timeit bigset >= set(bigsubseq)
2.1 ms ± 49.2 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

Ve eğer sizin containerdiziniz bir diziyse ve önce dönüştürülmesi gerekiyorsa, hızlanma daha da küçüktür:

>>> %timeit set(bigseq) >= set(bigsubseq)
4.36 ms ± 31.4 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

Son derece yavaş sonuçlar aldığımız tek zaman container, bir dizi olarak ayrıldığımız zamandır :

>>> %timeit all(x in bigseq for x in bigsubseq)
184 ms ± 994 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

Ve tabii ki, bunu sadece mecbur kalırsak yapacağız. bigseqİçindeki tüm öğeler karma hale getirilebilirse, bunun yerine şunu yapacağız:

>>> %timeit bigset = set(bigseq); all(x in bigset for x in bigsubseq)
7.24 ms ± 78 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

Bu, alternatiften sadece 1.66 kat daha hızlıdır ( set(bigseq) >= set(bigsubseq)yukarıda 4.36'da zamanlanmıştır).

Bu nedenle, alt küme testi genellikle daha hızlıdır, ancak inanılmaz bir farkla değil. Öte yandan, ne zaman alldaha hızlı olduğuna bakalım . Ya itemson milyon değer uzunluğundaysa ve içinde olmayan değerlere sahip olma ihtimali varsa container?

>>> %timeit hugeiter = (x * 10 for bss in [bigsubseq] * 2000 for x in bss); set(bigset) >= set(hugeiter)
13.1 s ± 167 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
>>> %timeit hugeiter = (x * 10 for bss in [bigsubseq] * 2000 for x in bss); all(x in bigset for x in hugeiter)
2.33 ms ± 65.2 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

Jeneratörü bir sete dönüştürmek bu durumda inanılmaz derecede israfa dönüşüyor. setYapıcı tüm jeneratör tüketmek zorundadır. Ancak kısa devre davranışı all, jeneratörün yalnızca küçük bir kısmının tüketilmesi gerektiğini garanti eder, bu nedenle, bir alt küme testinden dört kat daha hızlıdır .

Kuşkusuz bu aşırı bir örnek. Ancak görüldüğü gibi, her durumda bir yaklaşımın veya diğerinin daha hızlı olacağını varsayamazsınız.

Upshot

Çoğu zaman, containerbir kümeye dönüştürmek , en azından tüm öğeleri karma hale getirilebilirse, buna değer. Bunun nedeni inkümeler için O (1), indiziler için ise O (n) olmasıdır.

Öte yandan, alt küme testini kullanmak muhtemelen sadece bazen buna değer. Test öğeleriniz zaten bir sette saklanmışsa kesinlikle yapın. Aksi takdirde, allyalnızca biraz daha yavaştır ve herhangi bir ek depolama alanı gerektirmez. Aynı zamanda büyük eşya üreticileri ile de kullanılabilir ve bu durumda bazen büyük bir hızlanma sağlar.


62

Bunu yapmanın başka bir yolu:

>>> set(['a','b']).issubset( ['b','a','foo','bar'] )
True

21
Eğlenceli gerçek: set(['a', 'b']) <= set(['b','a','foo','bar'])aynı şeyi hecelemenin başka bir yolu ve daha "matematiksel" görünüyor.
Kirk Strauser

8
Python 2.7'den itibaren kullanabilirsiniz{'a', 'b'} <= {'b','a','foo','bar'}
Viktor Stískala

11

Ben çok emin değilim indaha yüksek önceliğe sahip ,olarak ifadesi yorumlanır ediliyor böylece 'a', ('b' in ['b' ...])daha sonra değerlendirdiği, 'a', Trueçünkü 'b'dizide olduğunu.

İstediğinizi nasıl yapacağınıza dair önceki cevaba bakın.


7

Tüm giriş eşleşmelerinizi kontrol etmek istiyorsanız ,

>>> all(x in ['b', 'a', 'foo', 'bar'] for x in ['a', 'b'])

en az bir eşleşmeyi kontrol etmek istiyorsanız ,

>>> any(x in ['b', 'a', 'foo', 'bar'] for x in ['a', 'b'])

3

Python ayrıştırıcısı, bu ifadeyi bir demet olarak değerlendirdi, burada ilk değer vardı 'a've ikinci değer ifadedir 'b' in ['b', 'a', 'foo', 'bar'](değerlendirilir True).

İstediğinizi yapabileceğiniz basit bir işlev yazabilirsiniz, ancak:

def all_in(candidates, sequence):
    for element in candidates:
        if element not in sequence:
            return False
    return True

Ve şöyle adlandırın:

>>> all_in(('a', 'b'), ['b', 'a', 'foo', 'bar'])
True

2
[x for x in ['a','b'] if x in ['b', 'a', 'foo', 'bar']]

Bunun seçilen cevaptan daha iyi olduğunu düşünmemin nedeni, gerçekten 'all ()' işlevini çağırmanıza gerek olmamasıdır. Boş liste, IF ifadelerinde Yanlış olarak değerlendirilir, boş olmayan liste Doğru olarak değerlendirilir.

if [x for x in ['a','b'] if x in ['b', 'a', 'foo', 'bar']]:
    ...Do something...

Misal:

>>> [x for x in ['a','b'] if x in ['b', 'a', 'foo', 'bar']]
['a', 'b']
>>> [x for x in ['G','F'] if x in ['b', 'a', 'foo', 'bar']]
[]

1

Bu köşeli parantezleri bile dışarıda bırakabileceğimizi söyleyebilirim.

array = ['b', 'a', 'foo', 'bar']
all([i in array for i in 'a', 'b'])

0

Burada sunulan cevapların her ikisi de tekrarlanan unsurları ele almayacaktır. Örneğin, [1,2,2] 'nin [1,2,3,4]' ün bir alt listesi olup olmadığını test ediyorsanız, her ikisi de True döndürür. Yapmak istediğin bu olabilir, ama ben sadece açıklığa kavuşturmak istedim. [1,2,3,4] 'de [1,2,2] için yanlış döndürmek istiyorsanız, her iki listeyi de sıralamanız ve her bir öğeyi her listede hareketli bir indeksle kontrol etmeniz gerekir. Sadece biraz daha karmaşık bir döngü.


1
'her ikisi de'? İkiden fazla cevap var. Tüm yanıtların bu sorundan mı yoksa yalnızca ikisinin (ve eğer öyleyse, hangilerinin) bu sorundan muzdarip olduğunu mu söylediniz?
Wipqozn

-1

lambdalar olmadan nasıl pitonik olabilirsin! .. ciddiye alınmamalı .. ama bu yol da işe yarar:

orig_array = [ ..... ]
test_array = [ ... ]

filter(lambda x:x in test_array, orig_array) == test_array

Değerlerden herhangi birinin dizide olup olmadığını test etmek istiyorsanız son kısmı atlayın:

filter(lambda x:x in test_array, orig_array)

1
Bunun bir filterjeneratör olduğu Python 3'te tasarlandığı gibi çalışmayacağına dair bir uyarı. Boole bağlamında veya boole bağlamında listtest edebileceğiniz bir sonucu gerçekten elde etmek istiyorsanız ==(boş olup olmadığını görmek için) onu sarmalamanız gerekir . Bir liste anlama veya bir üreteç ifadesi kullanmak anyveya alltercih edilir.
Blckknght

-1

İşte bunu nasıl yaptım:

A = ['a','b','c']
B = ['c']
logic = [(x in B) for x in A]
if True in logic:
    do something
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.