Bir listenin bir ve yalnızca bir gerçek değeri olup olmadığını nasıl kontrol edebilirim?


84

Python'da , bir ve yalnızca bir doğru değere sahip olması gereken bir listem var (yani, bool(value) is True). Bunu kontrol etmenin akıllıca bir yolu var mı? Şu anda, listeyi yineliyorum ve manuel olarak kontrol ediyorum:

def only1(l)
    true_found = False
    for v in l:
        if v and not true_found:
            true_found=True
        elif v and true_found:
             return False #"Too Many Trues"
    return true_found

Bu uygunsuz görünüyor ve pek pitonik değil. Bunu yapmanın daha akıllıca bir yolu var mı?


2
Bence çözümünüz oldukça iyi ve pitonik!
wim

1
Common Lisp: (= 1 (count-if #'identity list)).
Kaz

7
sum(lst) == 1
Pål GD

Açık olmak gerekirse: yalnızca bir Trueveya yalnızca bir gerçek değer olup olmadığını kontrol etmek ister misiniz ?
Marcin

Yanıtlar:


44

En ayrıntılı çözüm her zaman en seçkin olmayan çözüm değildir. Bu nedenle, sadece küçük bir değişiklik ekliyorum (bazı gereksiz boole değerlendirmelerini kaydetmek için):

def only1(l):
    true_found = False
    for v in l:
        if v:
            # a True was found!
            if true_found:
                # found too many True's
                return False 
            else:
                # found the first True
                true_found = True
    # found zero or one True value
    return true_found

İşte karşılaştırma için bazı zamanlamalar:

# file: test.py
from itertools import ifilter, islice

def OP(l):
    true_found = False
    for v in l:
        if v and not true_found:
            true_found=True
        elif v and true_found:
             return False #"Too Many Trues"
    return true_found

def DavidRobinson(l):
    return l.count(True) == 1

def FJ(l):
    return len(list(islice(ifilter(None, l), 2))) == 1

def JonClements(iterable):
    i = iter(iterable)
    return any(i) and not any(i)

def moooeeeep(l):
    true_found = False
    for v in l:
        if v:
            if true_found:
                # found too many True's
                return False 
            else:
                # found the first True
                true_found = True
    # found zero or one True value
    return true_found

Çıktım:

$ python -mtimeit -s 'import test; l=[True]*100000' 'test.OP(l)' 
1000000 loops, best of 3: 0.523 usec per loop
$ python -mtimeit -s 'import test; l=[True]*100000' 'test.DavidRobinson(l)' 
1000 loops, best of 3: 516 usec per loop
$ python -mtimeit -s 'import test; l=[True]*100000' 'test.FJ(l)' 
100000 loops, best of 3: 2.31 usec per loop
$ python -mtimeit -s 'import test; l=[True]*100000' 'test.JonClements(l)' 
1000000 loops, best of 3: 0.446 usec per loop
$ python -mtimeit -s 'import test; l=[True]*100000' 'test.moooeeeep(l)' 
1000000 loops, best of 3: 0.449 usec per loop

Görüldüğü gibi, OP çözümü burada yayınlanan diğer çözümlerin çoğundan önemli ölçüde daha iyidir. Beklendiği gibi, en iyileri kısa devre davranışına sahip olanlar, özellikle de Jon Clements tarafından yayınlanan çözüm. En azından Trueuzun bir listedeki iki erken değerin durumu için .

Burada hiçbir Truedeğer olmadan aynı :

$ python -mtimeit -s 'import test; l=[False]*100000' 'test.OP(l)' 
100 loops, best of 3: 4.26 msec per loop
$ python -mtimeit -s 'import test; l=[False]*100000' 'test.DavidRobinson(l)' 
100 loops, best of 3: 2.09 msec per loop
$ python -mtimeit -s 'import test; l=[False]*100000' 'test.FJ(l)' 
1000 loops, best of 3: 725 usec per loop
$ python -mtimeit -s 'import test; l=[False]*100000' 'test.JonClements(l)' 
1000 loops, best of 3: 617 usec per loop
$ python -mtimeit -s 'import test; l=[False]*100000' 'test.moooeeeep(l)' 
100 loops, best of 3: 1.85 msec per loop

İstatistiksel önemi kontrol etmedim, ama ilginç bir şekilde, bu sefer FJ'nin önerdiği yaklaşımlar ve özellikle Jon Clements'in önerdiği yaklaşımlar açıkça daha üstün görünüyor.


4
Umm - erken gerçek zamanlamalara bakmak - 0.446en hızlısı değil mi?
Jon Clements

2
@JonClements bu yüzden en çok yazdım , şimdi daha net hale getirdim . (gönderilenlerin çoğu, test
edilenlerin

1
JonClement'ın çok hızlı olduğundan şüpheleniyorum çünkü çoğu anyC
Matthew Scouten

1
+1 Açılış cümleniz için. Tüm cevaplar sumaslında OP'nin basit ve doğrudan kodundan daha kötü.
wim

2
@MarkAmery Hem okunabilirlik ve zarafet (kuşkusuz kısa bir tane) hem de performans değerlendirme üzerine bir bölüm ekledim. Soru zekice sorulduğu için her iki yönün de dikkate alınması gerektiğini düşünüyorum. Gördüğüm kadarıyla, bu iki ilgili yönü ele almak için bir cevap verdim. Bu cevabın yararlı olmadığını düşünüyorsanız, olumsuz oy kullanmaktan çekinmeyin.
moooeeeep

259

İthalat gerektirmeyen:

def single_true(iterable):
    i = iter(iterable)
    return any(i) and not any(i)

Alternatif olarak, belki daha okunabilir bir versiyon:

def single_true(iterable):
    iterator = iter(iterable)

    # consume from "i" until first true or it's exhausted
    has_true = any(iterator) 

    # carry on consuming until another true value / exhausted
    has_another_true = any(iterator) 

    # True if exactly one true found
    return has_true and not has_another_true

Bu:

  • iHerhangi bir gerçek değere sahip olduğundan emin olmak için görünüyor
  • Başka bir gerçek değer olmadığından emin olmak için yinelenebilirdeki o noktadan bakmaya devam eder

34
@MatthewScouten hayır ... burada tekrarlanabilir bir şey tüketiyoruz ... kodu çalıştırmayı deneyin ...
Jon Clements

12
Yinelenebilir tüketim başına @ MatthewScouten. anyDokümanlara göre, yanlış olmayan bir değer bulunur bulunmaz True döndürür. Bundan sonra, tekrar gerçek bir değer ararız ve bulunursa, onu bir başarısızlık olarak değerlendiririz ... Yani bu, boş listeler, listeler / diğer diziler ve tekrarlanabilir herhangi bir ...
Jon Clements

12
@MathewScouten Yan etkiler tüm teoremleri bozar! x and not x = Falseyalnızca xreferans olarak şeffafsa doğrudur .
Ben

14
@wim Bu bir uygulama ayrıntısı değildirany() - işlevin belgelenmiş bir özelliğidir ve Python spesifikasyonuna uyan herhangi bir uygulamanın garantili işlevselliğidir.
Gareth Latty

17
Bunun okunabilir bir çözüm olmadığını düşünen herkes şunu dikkate almalıdır: Kısa ve özdür ve yalnızca bilinen davranışlara ve Python'un ortak yapısına dayanır. Sırf bir çaylak bunu anlamayacağı için okunabilir kılmaz. Aynı zamanda, nasıl çalıştığını görmeyenlerde anında merak uyandırdığı için, bilinmesi gerekenleri öğretmenin mükemmel bir yoludur.
dansalmo

49

Bu, yalnızca değeri mi Trueyoksa Truemantıksal olarak ( 11veya gibi "hello") değerlendirilebilecek başka değerler mi aradığınıza bağlıdır . İlki:

def only1(l):
    return l.count(True) == 1

İkincisi ise:

def only1(l):
    return sum(bool(e) for e in l) == 1

çünkü bu, yeni bir liste oluşturmak zorunda kalmadan hem saymayı hem de dönüşümü tek bir yinelemede yapar.


2
Python 3:list(map(bool, l)).count(True)
dürtmek

Bu, yalnızca gerçek True değerini bulur, diğer gerçek değerleri değil (yani: pozitif girişler, boş kaplar değil, vb.)
Matthew Scouten, 28'13

6
Sadece OP'ye işaret etmek için, birden fazla "Gerçek" değer bulunduğunda bu büyük olasılıkla kısa devre olmayacaktır, bu nedenle kodları belirli durumlarda onlara daha fazla verimlilik sağlayabilir.
NominSim

2
İkinci işlev olarak yazılabilir return sum(bool(e) for e in l) == 1. boolaltsınıflar intve Doğru / Yanlış, aritmetik konusunda 1/0 olarak davranır.

1
lDeğişken adı kullanmaktan kaçınırdım ( 1buraya çok benziyor ) ve sum(bool(e) for e in l)olarak yeniden sum(1 for e in l if e)
yazardım

22

Kısa devre davranışını koruyan tek satırlık bir yanıt:

from itertools import ifilter, islice

def only1(l):
    return len(list(islice(ifilter(None, l), 2))) == 1

Bu, nispeten erken iki veya daha fazla gerçek değere sahip çok büyük yinelemeler için buradaki diğer alternatiflerden önemli ölçüde daha hızlı olacaktır.

ifilter(None, itr)yalnızca doğru unsurları verecek bir yinelenebilir ( xeğer bool(x)dönerse doğrudur True) verir. islice(itr, 2)yalnızca ilk iki öğesini verecek bir yinelenebilir verir itr. Bunu bir listeye dönüştürerek ve uzunluğun bire eşit olduğunu kontrol ederek, iki tane bulduktan sonra herhangi bir ek öğeyi kontrol etmeye gerek kalmadan tam olarak bir doğru öğenin var olduğunu doğrulayabiliriz.

İşte bazı zamanlama karşılaştırmaları:

  • Kurulum kodu:

    In [1]: from itertools import islice, ifilter
    
    In [2]: def fj(l): return len(list(islice(ifilter(None, l), 2))) == 1
    
    In [3]: def david(l): return sum(bool(e) for e in l) == 1
    
  • Kısa devre davranışı sergilemek:

    In [4]: l = range(1000000)
    
    In [5]: %timeit fj(l)
    1000000 loops, best of 3: 1.77 us per loop
    
    In [6]: %timeit david(l)
    1 loops, best of 3: 194 ms per loop
    
  • Kısa devrenin meydana gelmediği büyük liste:

    In [7]: l = [0] * 1000000
    
    In [8]: %timeit fj(l)
    100 loops, best of 3: 10.2 ms per loop
    
    In [9]: %timeit david(l)
    1 loops, best of 3: 189 ms per loop
    
  • Küçük liste:

    In [10]: l = [0]
    
    In [11]: %timeit fj(l)
    1000000 loops, best of 3: 1.77 us per loop
    
    In [12]: %timeit david(l)
    1000000 loops, best of 3: 990 ns per loop
    

Bu nedenle, sum()yaklaşım çok küçük listeler için daha hızlıdır, ancak girdi listesi büyüdükçe, sürümüm kısa devre mümkün olmadığında bile daha hızlıdır. Büyük bir girişte kısa devre mümkün olduğunda, performans farkı açıktır.


5
Ah. Diğer seçeneklerin anlaşılması uzun süre beni üç kez aldı. Kısa devre önemliyse, OP'nin kodunu çok daha açık ve kabaca aynı derecede verimli olduğu için alırdım.

1
Tarz için oy verin ve kısa devreyi koruyun. Ancak bunu okumak daha zor.
Matthew Scouten

1
+1. OP'nin kısa devre yapma niyetini tam olarak yeniden üreten tek kişi.
NominSim

1
timeitOP çözümüyle performans açısından objektif bir karşılaştırma için bazı deneyler sağlarsanız +1 .
moooeeeep

@moooeeeep Naively eğer True"erken" bir yerde iki değere sahip sonsuz bir yinelenebilirliğe sahipseniz , bu, sayıyı elde etmeye çalışırken sonsuza dek çarklarını döndüren diğer cevaplara karşı bitecektir .
NominSim

15

Büyücü rozetini kazanmak istedim, bu yüzden Jon Clements'in mükemmel cevabını genelleştirdim, kısa devre mantığının faydalarını ve herhangi biriyle hızlı tahmin kontrolünün faydalarını korudum.

İşte burada:

N (doğrular) = n

def n_trues(iterable, n=1):
    i = iter(iterable)
    return all(any(i) for j in range(n)) and not any(i)

N (doğrular) <= n:

def up_to_n_trues(iterable, n=1):
    i = iter(iterable)
    all(any(i) for j in range(n))
    return not any(i)

N (doğrular)> = n:

def at_least_n_trues(iterable, n=1):
    i = iter(iterable)
    return all(any(i) for j in range(n))

m <= N (doğrular) <= n

def m_to_n_trues(iterable, m=1, n=1):
    i = iter(iterable)
    assert m <= n
    return at_least_n_trues(i, m) and up_to_n_trues(i, n - m)

11
>>> l = [0, 0, 1, 0, 0]
>>> has_one_true = len([ d for d in l if d ]) == 1
>>> has_one_true
True

4
Bu neden reddedildi? Bence en basit ve en okunaklı olanı.
dansalmo

1
@dansalmo: Elbette emin olmak zor, ama benim teorim, birçok n00b python programcısının - belki de özellikle Java geçmişine sahip olanların - daha uzun sözdizimiyle daha rahat hissediyor olması. (Ben de 5-10 yıl önce biraz
böyleydim

5

Yapabilirsin:

x = [bool(i) for i in x]
return x.count(True) == 1

Veya

x = map(bool, x)
return x.count(True) == 1

@ JoranBeasley'in yöntemini geliştirmek:

sum(map(bool, x)) == 1

5
if sum([bool(x) for x in list]) == 1

(Tüm değerlerinizin booleanish olduğunu varsayarsak.)

Bu muhtemelen sadece özetlemek daha hızlı olurdu

sum(list) == 1   

listenizdeki veri türlerine bağlı olarak bazı sorunlara neden olabilir.


1
Burada biraz büyük harf kullanımı ve noktalama işaretleri güzel olurdu.
Steven Rumbalski

4

Yalnızca bir tane varsa True, Trues'nin uzunluğu bir olmalıdır:

def only_1(l): return 1 == len(filter(None, l))

2
Belki cevabını açıklayabilirsin?
Linus Caldwell

4

Bu işe yarıyor gibi görünüyor ve yalnızca liste- postaları değil, yinelenenleri de işleyebilmelidir . Verimliliği en üst düzeye çıkarmak için mümkün olduğunda kısa devre yapar. Hem Python 2 hem de 3'te çalışır.

def only1(iterable):
    for i, x in enumerate(iterable):  # check each item in iterable
        if x: break                   # truthy value found
    else:
        return False                  # no truthy value found
    for x in iterable[i+1:]:          # one was found, see if there are any more
        if x: return False            #   found another...
    return True                       # only a single truthy value found

testcases = [  # [[iterable, expected result], ... ]
    [[                          ], False],
    [[False, False, False, False], False],
    [[True,  False, False, False], True],
    [[False, True,  False, False], True],
    [[False, False, False, True],  True],
    [[True,  False, True,  False], False],
    [[True,  True,  True,  True],  False],
]

for i, testcase in enumerate(testcases):
    correct = only1(testcase[0]) == testcase[1]
    print('only1(testcase[{}]): {}{}'.format(i, only1(testcase[0]),
                                             '' if correct else
                                             ', error given '+str(testcase[0])))

Çıktı:

only1(testcase[0]): False
only1(testcase[1]): False
only1(testcase[2]): True
only1(testcase[3]): True
only1(testcase[4]): True
only1(testcase[5]): False
only1(testcase[6]): False

Bu yaklaşım gibi, nasıl etrafında mantığı elden geçirilmesi konusunda iter(x for x in my_list if x)ve daha sonra kullanarak nextbelki kullanarak daha güzel mapvelist.index
wim

@wim: Sana önerilen yaklaşım kullanılmadığı halde, yorumunuz özgün cevabım revize etmek bana ilham ve daha da artan doğada ve kurtulur yapmak mapve list.index.
martineau

3

@ JonClements` çözümü en fazla N True değeri için genişletildi :

# Extend any() to n true values
def _NTrue(i, n=1):
    for x in xrange(n):
        if any(i): # False for empty
            continue
        else:
            return False
    return True

def NTrue(iterable, n=1):
    i = iter(iterable)
    return any(i) and not _NTrue(i, n)

düzenleme: daha iyi sürüm

def test(iterable, n=1): 
    i = iter(iterable) 
    return sum(any(i) for x in xrange(n+1)) <= n 

edit2: dahil en az doğrudur m ve en fazla n Gerçek yılların en

def test(iterable, n=1, m=1): 
    i = iter(iterable) 
    return  m <= sum(any(i) for x in xrange(n+1)) <= n

1
Hayır, en fazla yani. En fazla N gerçek değerli değerler varsa True döndürür: örneğin 3 doğruları 1000 listesinde alacağı iterable.count(True) = 3, NTrue(iterable, 1) = False, NTrue(iterable, 2) = False, NTrue(iterable, 3) = True, NTrue(iterable, 4) = True, ... Temelde uzanan and not any(i)kısmı içinand not any(i) and not any(i) and not...
Nisan.H

1
all(any(i) for i in xrange(n)) and not any(i)Burada çalışmıyor mu ?
Eric

@Eric, yalnızca tam olarak n doğru için True döndürür . Yine de, bana s'leri toplama fikrini verdi any.
Nisan.H

Yani demek istedin any(i) and not all(any(i) for x in xrange(n))?
moooeeeep

@moooeeeep True and not all(<n booleans>)Mantıksal olarak aynı değil count(True) <= nmi? Fikir hala mümkün olan en küçük seti test etmek ve ilk arıza durumunda kırmaktır.
Nisan H

2
def only1(l)
    sum(map(lambda x: 1 if x else 0, l)) == 1

Açıklama: mapİşlev, True => 1ve yaparak bir listeyi başka bir listeye eşler False => 0. Artık True veya False yerine 0'lar ve 1'ler listemiz var. Şimdi bu listeyi basitçe özetliyoruz ve eğer 1 ise, sadece bir Gerçek değer vardı.


1

Aradığınız bu mu?

sum(l) == 1

Bu liste için başarısız olur: [2], çünkü yazar öğelerin yalnızca Doğru ve Yanlış veya 1 ve 0 olması gerektiğini belirtmedi
vtlinh

1

Bütünlük uğruna ve döngü yinelemesi için Python'un kontrol akışının gelişmiş kullanımını göstermek için, kabul edilen cevapta fazladan hesaplama yapmaktan kaçınılabilir, bu da bunu biraz daha hızlı hale getirir:

def one_bool_true(iterable):
    it = iter(iterable)
    for i in it:
        if i:
            break
    else:            #no break, didn't find a true element
        return False
    for i in it:     # continue consuming iterator where left off
        if i: 
            return False
    return True      # didn't find a second true.

'S basit kontrol akışının yukarıdaki döngü Python'un sofistike özelliğini kullanır: else. Anlamsal olarak, tükettiğiniz yineleyici üzerinde yinelemeyi bitirirseniz break, ondan çıkmadanelse bloğa .

İşte biraz daha fazla hesaplama kullanan kabul edilen cevap.

def only1(l):
    true_found = False
    for v in l:
        if v:
            # a True was found!
            if true_found:
                # found too many True's
                return False 
            else:
                # found the first True
                true_found = True
    # found zero or one True value
    return true_found

zaman zaman bunlara:

import timeit
>>> min(timeit.repeat(lambda: one_bool_true([0]*100 + [1, 1])))
13.992251592921093
>>> min(timeit.repeat(lambda: one_bool_true([1, 1] + [0]*100)))
2.208037032979064
>>> min(timeit.repeat(lambda: only1([0]*100 + [1, 1])))
14.213872335107908
>>> min(timeit.repeat(lambda: only1([1, 1] + [0]*100)))
2.2482982632641324
>>> 2.2482/2.2080
1.0182065217391305
>>> 14.2138/13.9922
1.0158373951201385

Böylece kabul edilen cevabın biraz daha uzun sürdüğünü görüyoruz (yüzde bir buçuktan biraz fazla).

Doğal olarak, anyC ile yazılmış yerleşik olanı kullanmak çok daha hızlıdır (Jon Clement'in uygulama için cevabına bakın - bu kısa biçimdir):

>>> min(timeit.repeat(lambda: single_true([0]*100 + [1, 1])))
2.7257133318785236
>>> min(timeit.repeat(lambda: single_true([1, 1] + [0]*100)))
2.012824866380015

0
import collections

def only_n(l, testval=True, n=1):
    counts = collections.Counter(l)
    return counts[testval] == n

Doğrusal zaman. Sayımları kontrol etmek için kullanmanız gereken yerleşik Counter sınıfını kullanır.

Sorunuzu yeniden okuduktan sonra, aslında tek bir değer yerine yalnızca tek bir doğru değer olduğunu kontrol etmek istediğiniz görülüyor True. Bunu dene:

import collections

def only_n(l, testval=True, coerce=bool, n=1):
    counts = collections.Counter((coerce(x) for x in l))
    return counts[testval] == n

Siz daha iyi en iyi kasa performansını elde ederken, hiçbir şeyin daha iyi en kötü durum performansı yoktur. Bu da kısa ve okunması kolay.

İşte en iyi durum performansı için optimize edilmiş bir sürüm:

import collections
import itertools

def only_n(l, testval=True, coerce=bool, n=1):
    counts = collections.Counter()
    def iterate_and_count():
        for x in itertools.imap(coerce,l):
            yield x
            if x == testval and counts[testval] > n:
               break
    counts.update(iterate_and_count())
    return counts[testval] == n

En kötü durum performansının yüksek k(olduğu gibi O(kn+c)) vardır, ancak bu tamamen geneldir.

İşte performansı denemek için bir fikir: http://ideone.com/ZRrv2m


0

İşte kısa devre olmasa da doğru olan her şey için çalışması gereken bir şey. Birbirini dışlayan argümanları yasaklamanın temiz bir yolunu ararken buldum:

if sum(1 for item in somelist if item) != 1:
    raise ValueError("or whatever...")

0

Ne dersin:

len([v for v in l if type(v) == bool and v])

Yalnızca boole True değerlerini saymak istiyorsanız.

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.