Wie kann ich überprüfen, ob eine Liste nur einen Wahrheitswert hat?

82

In Python habe ich eine Liste, die nur einen Wahrheitswert haben sollte (d bool(value) is True. H. ). Gibt es eine clevere Möglichkeit, dies zu überprüfen? Im Moment iteriere ich nur durch die Liste und überprüfe manuell:

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

Dies scheint unelegant und nicht sehr pythonisch. Gibt es eine klügere Möglichkeit, dies zu tun?

Matthew Scouten
quelle
2
Ich denke, Ihre Lösung ist ganz in Ordnung und pythonisch!
wim
1
Common Lisp : (= 1 (count-if #'identity list)).
Kaz
7
sum(lst) == 1
Pål GD
Um es klar zu sagen: TrueMöchten Sie überprüfen, ob es nur einen oder nur einen Wahrheitswert gibt?
Marcin

Antworten:

44

Die ausführlichste Lösung ist nicht immer die uneleganteste Lösung. Daher füge ich nur eine geringfügige Änderung hinzu (um einige redundante boolesche Auswertungen zu speichern):

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

Hier sind einige Zeitpunkte zum Vergleich:

# 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

Meine Ausgabe:

$ 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

Wie zu sehen ist, ist die OP-Lösung erheblich besser als die meisten anderen hier veröffentlichten Lösungen. Wie erwartet sind die besten diejenigen mit Kurzschlussverhalten, insbesondere die von Jon Clements veröffentlichte Lösung. Zumindest für den Fall von zwei frühen TrueWerten in einer langen Liste.

Hier das gleiche für überhaupt keinen TrueWert:

$ 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

Ich habe die statistische Signifikanz nicht überprüft, aber interessanterweise scheinen diesmal die von FJ vorgeschlagenen und insbesondere die von Jon Clements wieder deutlich überlegen zu sein.

moooeeeep
quelle
4
Umm - wenn man sich die frühen wahren Zeiten ansieht - ist das nicht 0.446das schnellste?
Jon Clements
2
@ JonClements, deshalb habe ich am meisten geschrieben und es jetzt klarer gemacht. (die meisten der geposteten, nicht die meisten der getesteten ...)
moooeeeep
1
Ich vermute, JonClement ist so schnell, weil das meiste anyin C implementiert ist
Matthew Scouten
1
+1 Für Ihre Eröffnungszeile. Alle Antworten mit sumsind tatsächlich schlechter als der einfache und direkte Code des OP.
wim
2
@MarkAmery Ich habe sowohl einen Abschnitt über Lesbarkeit und Eleganz (zugegebenermaßen einen kurzen) als auch über die Leistungsbewertung hinzugefügt. Da die Frage nach der Klugheit gestellt wurde, sollten meiner Meinung nach beide Aspekte berücksichtigt werden. Aus meiner Sicht habe ich eine Antwort gegeben, um diese beiden relevanten Aspekte anzusprechen. Wenn Sie der Meinung sind, dass diese Antwort nicht hilfreich ist, können Sie sie ablehnen.
Moooeeeep
256

Eine, die keine Importe erfordert:

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

Alternativ vielleicht eine besser lesbare Version:

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

Dies:

  • Sieht so aus, als ob ies einen wahren Wert hat
  • Sieht von diesem Punkt in der Iterable aus weiter, um sicherzustellen, dass es keinen anderen wahren Wert gibt
Jon Clements
quelle
34
@MatthewScouten nein ... wir verbrauchen von einem iterierbaren hier ... versuchen Sie, den Code auszuführen ...
Jon Clements
12
@MatthewScouten nach Verbrauch des iterable. anywird gemäß docs True zurückgeben, sobald ein nicht falscher Wert gefunden wird. Danach suchen wir erneut nach einem wahren Wert, und wenn er gefunden wird, behandeln wir ihn als Fehler ... Dies funktioniert also für leere Listen, Listen / andere Sequenzen und alle iterierbaren ...
Jon Clements
12
@MathewScouten Nebenwirkungen brechen alle Sätze! x and not x = Falseist nur richtig, wenn xreferenziell transparent ist.
Ben
14
@wim Es handelt sich nicht um ein Implementierungsdetail von any()- es ist ein dokumentiertes Merkmal der Funktion und die garantierte Funktionalität jeder Implementierung, die der Python-Spezifikation entspricht.
Gareth Latty
17
Jeder, der dies für keine lesbare Lösung hält, sollte dies berücksichtigen: Es ist prägnant und stützt sich nur auf bekannte Verhaltensweisen und das übliche Konstrukt von Python. Nur weil ein Noob es nicht verstehen würde, macht es es nicht lesbar. Es ist auch ein hervorragendes Mittel, um zu lehren, was bekannt sein sollte, da es diejenigen, die nicht sehen, wie es funktioniert, sofort neugierig macht.
Dansalmo
49

Es hängt davon ab, ob Sie nur nach dem Wert suchen Trueoder nach anderen Werten suchen, die Truelogisch ausgewertet werden (wie 11oder "hello"). Wenn der erstere:

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

Wenn letzteres:

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

da dies sowohl das Zählen als auch die Konvertierung in einer einzigen Iteration durchführen würde, ohne eine neue Liste erstellen zu müssen.

David Robinson
quelle
2
In Python 3:list(map(bool, l)).count(True)
stochern
Dies findet nur die wörtlichen wahren, nicht andere wahre Werte (dh: positive Ints, keine leeren Behälter usw.)
Matthew Scouten
6
Nur um das OP darauf hinzuweisen, wird dies wahrscheinlich keinen Kurzschluss verursachen, wenn mehr als ein "True" -Wert gefunden wird, sodass ihr Code unter bestimmten Umständen zu mehr Effizienz führen kann.
NominSim
2
Die zweite Funktion kann geschrieben werden als return sum(bool(e) for e in l) == 1. boolUnterklassen intund True / False verhalten sich in Bezug auf Arithmetik wie 1/0.
1
Ich würde es vermeiden, lals Variablennamen zu verwenden (es sieht zu sehr nach 1hier aus), und ich würde umschreiben sum(bool(e) for e in l)alssum(1 for e in l if e)
wim
22

Eine einzeilige Antwort, die das Kurzschlussverhalten beibehält:

from itertools import ifilter, islice

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

Dies ist für sehr große Iterables, die relativ früh zwei oder mehr wahre Werte haben, erheblich schneller als die anderen Alternativen.

ifilter(None, itr)gibt eine iterable, die nur wahrheitsgemäße Elemente liefert ( xist wahrhaftig, wenn bool(x)zurückgegeben wird True). islice(itr, 2)gibt eine Iterable an, die nur die ersten beiden Elemente von ergibt itr. Indem wir dies in eine Liste konvertieren und überprüfen, ob die Länge gleich eins ist, können wir überprüfen, ob genau ein wahrheitsgemäßes Element vorhanden ist, ohne dass zusätzliche Elemente überprüft werden müssen, nachdem wir zwei gefunden haben.

Hier sind einige Zeitvergleiche:

  • Setup-Code:

    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
    
  • Kurzschlussverhalten zeigen:

    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
    
  • Große Liste, in der kein Kurzschluss auftritt:

    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
    
  • Kleine 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
    

Der sum()Ansatz ist also für sehr kleine Listen schneller, aber wenn die Eingabeliste größer wird, ist meine Version schneller, selbst wenn ein Kurzschluss nicht möglich ist. Wenn bei einem großen Eingang ein Kurzschluss möglich ist, ist der Leistungsunterschied deutlich.

Andrew Clark
quelle
5
Autsch. Ich habe dreimal gebraucht, um die anderen Optionen zu verstehen. Wenn ein Kurzschluss wichtig ist, würde ich den OP-Code verwenden, da er viel offensichtlicher und ungefähr genauso effizient ist.
1
Stimmen Sie für Stil und behalten Sie den Kurzschluss bei. Das ist aber schwerer zu lesen.
Matthew Scouten
1
+1. Der einzige, der die volle Absicht des OP zum Kurzschließen reproduziert.
NominSim
1
+1, wenn Sie einige timeitExperimente für einen objektiven Leistungsvergleich mit der OP-Lösung durchführen.
Moooeeeep
@moooeeeep Naiv, wenn Sie eine unendliche Iterable hatten, die Trueirgendwo "früh" zwei Werte hat , wird dies beendet, im Vergleich zu den anderen Antworten, die ihre Räder für immer drehen und versuchen, die Zählung zu erhalten.
NominSim
15

Ich wollte mir das Nekromanten-Abzeichen verdienen, also verallgemeinerte ich die ausgezeichnete Antwort von Jon Clements, wobei ich die Vorteile der Kurzschlusslogik und der schnellen Überprüfung von Prädikaten bei allen bewahrte.

Also hier ist:

N (wahr) = n

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

N (wahr) <= 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 (wahr)> = n:

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

m <= N (wahr) <= 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)
Antti Haapala
quelle
11
>>> l = [0, 0, 1, 0, 0]
>>> has_one_true = len([ d for d in l if d ]) == 1
>>> has_one_true
True
Gariel
quelle
4
Warum wurde das abgelehnt? Ich denke, es ist das einfachste und am besten lesbare von allen.
Dansalmo
1
@dansalmo: Es ist natürlich schwer, sicher zu sein, aber meine Theorie ist, dass sich viele n00b-Python-Programmierer - vielleicht insbesondere solche mit Java-Hintergrund - mit längerer Syntax wohler fühlen. (Ich selbst war vor 5-10 Jahren ein bisschen so, aber heute halte ich es für unprofessionell und ignorant.) +1
Jonas Byström
5

Du kannst tun:

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

Oder

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

Aufbauend auf der Methode von @ JoranBeasley:

sum(map(bool, x)) == 1
karthikr
quelle
5
if sum([bool(x) for x in list]) == 1

(Angenommen, alle Ihre Werte sind boolesch.)

Dies wäre wahrscheinlich schneller, wenn man es nur summiert

sum(list) == 1   

Dies kann jedoch je nach Datentyp in Ihrer Liste zu Problemen führen.

Joran Beasley
quelle
1
Eine Groß- und Kleinschreibung und Zeichensetzung wäre hier schön.
Steven Rumbalski
@StevenRumbalski Ok: P
tckmn
4

Wenn es nur eine gibt True, sollte die Länge des Trues eins sein:

def only_1(l): return 1 == len(filter(None, l))
Marc Laugharn
quelle
2
Vielleicht könnten Sie Ihre Antwort erklären?
Linus Caldwell
4

Dies scheint zu funktionieren und sollte in der Lage sein, alle iterierbaren Elemente zu verarbeiten, nicht nur lists. Es schließt nach Möglichkeit kurz, um die Effizienz zu maximieren. Funktioniert sowohl in Python 2 als auch in Python 3.

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])))

Ausgabe:

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
Martineau
quelle
Ich mag diesen Ansatz, wie wäre es, die Logik zu überarbeiten iter(x for x in my_list if x)und dann zu verwenden next, vielleicht schöner als zu verwenden mapundlist.index
wim
@wim: Obwohl ich den von Ihnen vorgeschlagenen Ansatz nicht verwendet habe, hat mich Ihr Kommentar dazu inspiriert, meine ursprüngliche Antwort zu überarbeiten und sie in der Natur noch inkrementeller zu gestalten und das mapund loszuwerden list.index.
Martineau
3

@ JonClements` Lösung für höchstens N True-Werte erweitert :

# 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)

bearbeiten: bessere Version

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

edit2: umfassen zumindest stimmt m und höchstens n stimmt

def test(iterable, n=1, m=1): 
    i = iter(iterable) 
    return  m <= sum(any(i) for x in xrange(n+1)) <= n
Nisan.H
quelle
1
Nein, ich meine höchstens. Es gibt True zurück , wenn höchstens N wahr bewertet Werte vorhanden sind : zB 3 Trues in einer Liste von 1000 erhalten würde iterable.count(True) = 3, NTrue(iterable, 1) = False, NTrue(iterable, 2) = False, NTrue(iterable, 3) = True, NTrue(iterable, 4) = True, ... Es erstreckt sich im Wesentlichen den and not any(i)Teiland not any(i) and not any(i) and not...
Nisan.H
1
Funktioniert hier nicht all(any(i) for i in xrange(n)) and not any(i)?
Eric
@Eric, der True nur für genau n true zurückgeben würde. Es gab mir jedoch die Idee, über das anys zu summieren .
Nisan.H
Du meinst eher any(i) and not all(any(i) for x in xrange(n))?
Moooeeeep
@moooeeeep Ist True and not all(<n booleans>)logisch nicht dasselbe wie count(True) <= n? Die Idee ist immer noch, den kleinstmöglichen Satz zu testen und bei der ersten Fehlerbedingung zu brechen.
Nisan.H
2
def only1(l)
    sum(map(lambda x: 1 if x else 0, l)) == 1

Erläuterung: Die mapFunktion bildet eine Liste in einer anderen Liste, tun True => 1und False => 0. Wir haben jetzt eine Liste von Nullen und Einsen anstelle von Wahr oder Falsch. Jetzt addieren wir einfach diese Liste und wenn es 1 ist, gab es nur einen wahren Wert.

Martin Konecny
quelle
1

Ist es das, wonach du suchst?

sum(l) == 1
c-urchin
quelle
Dies schlägt für eine Liste fehl: [2], da der Autor nicht angegeben hat, dass die Elemente nur wahr und falsch sein dürfen, oder 1 und 0
vtlinh
1

Der Vollständigkeit halber und um die erweiterte Verwendung des Python-Kontrollflusses für die Schleifeniteration zu demonstrieren, kann die zusätzliche Abrechnung in der akzeptierten Antwort vermieden werden, was dies etwas beschleunigt.:

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.

Der oben beschriebene einfache Steuerungsfluss nutzt Pythons ausgefeilte Schleifenfunktion: die else. Die Semantik lautet: Wenn Sie die Iteration über den Iterator beenden, den Sie verbrauchen, ohne breakihn zu verlassen, geben Sie den elseBlock ein.

Hier ist die akzeptierte Antwort, die etwas mehr Buchhaltung verwendet.

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

um diese zu messen:

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

Wir sehen also, dass die akzeptierte Antwort etwas länger dauert (etwas mehr als eineinhalb Prozent).

Natürlich ist die Verwendung des anyin C geschriebenen integrierten Systems viel schneller (siehe Jon Clements Antwort für die Implementierung - dies ist die Kurzform):

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

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

Lineare Zeit. Verwendet die integrierte Counter-Klasse, mit der Sie die Anzahl überprüfen sollten.

Wenn Sie Ihre Frage erneut lesen, möchten Sie anscheinend überprüfen, ob es nur einen Wahrheitswert und keinen Wert Truegibt. Versuche dies:

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

Während Sie eine bessere Best-Case-Leistung erzielen können, hat nichts eine bessere Worst-Case-Leistung. Dies ist auch kurz und leicht zu lesen.

Hier ist eine Version, die für die beste Leistung optimiert ist:

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

Die Worst-Case-Leistung ist hoch k(wie in O(kn+c)), aber völlig allgemein.

Hier ist eine Idee, um mit der Leistung zu experimentieren: http://ideone.com/ZRrv2m

Marcin
quelle
0

Hier ist etwas, das für alles Wahrhaftige funktionieren sollte, obwohl es keinen Kurzschluss gibt. Ich habe es gefunden, als ich nach einem sauberen Weg gesucht habe, um sich gegenseitig ausschließende Argumente zu verbieten:

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

Wie wäre es mit:

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

Wenn Sie nur boolesche True-Werte zählen möchten.

Radek Svoboda
quelle