it-swarm.com.de

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

In Python habe ich eine Liste, die eins und nur eins Wahrheitswert haben sollte (dh bool(value) is True). Gibt es eine clevere Möglichkeit, dies zu überprüfen? Im Moment durchlaufe ich nur 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 einen klügeren Weg, dies zu tun?

73
Matthew Scouten

Die ausführlichste Lösung ist nicht immer die uneleganteste Lösung. Deshalb füge ich nur eine kleine Modifikation 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 Zeitangaben 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 deutlich besser als die meisten anderen hier veröffentlichten Lösungen. Die besten sind erwartungsgemäß diejenigen mit Kurzschlussverhalten, insbesondere die von Jon Clements veröffentlichte Lösung. Zumindest für den Fall von zwei frühen True Werten in einer langen Liste.

Hier das gleiche für überhaupt keinen True Wert:

$ 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 F. J. und insbesondere die von Jon Clements vorgeschlagenen Ansätze eindeutig überlegen zu sein.

39
moooeeeep

Eine, die keinen Import 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)
    has_true = any(iterator) # consume from "i" until first true or it's exhuasted
    has_another_true = any(iterator) # carry on consuming until another true value / exhausted
    return has_true and not has_another_true # True if exactly one true found

Dies:

  • Überprüft, ob i einen wahren Wert hat
  • Stellt sicher, dass es keinen anderen wahren Wert gibt
229
Jon Clements

Es hängt davon ab, ob Sie nur nach dem Wert True suchen oder auch nach anderen Werten, die logisch zu True ausgewertet werden (wie 11 oder "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 das Umwandeln in einer einzigen Iteration erledigen würde, ohne eine neue Liste erstellen zu müssen.

45
David Robinson

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 erheblich schneller als die anderen Alternativen hier für sehr große Iterables, die relativ früh zwei oder mehr wahre Werte haben.

ifilter(None, itr) gibt eine iterable zurück, die nur wahrheitsgemäße Elemente liefert (x ist wahr, wenn bool(x)True zurückgibt). islice(itr, 2) gibt eine iterable zurück, die nur die ersten beiden Elemente von itr liefert. 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 wahres Element vorhanden ist, ohne dass wir zusätzliche Elemente überprüfen 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
    

Daher ist der sum() -Ansatz für sehr kleine Listen schneller, aber wenn die Eingabeliste größer wird, ist meine Version auch dann schneller, wenn kein Kurzschluss möglich ist. Wenn an einem großen Eingang ein Kurzschluss möglich ist, ist der Leistungsunterschied deutlich.

21
Andrew Clark

Ich wollte mir das Nekromantenabzeichen verdienen, also verallgemeinerte ich die ausgezeichnete Antwort von Jon Clements, wobei ich die Vorteile der Kurzschlusslogik und der schnellen Prädikatenüberprüfung 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(trues) <= 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)
14
Antti Haapala
>>> l = [0, 0, 1, 0, 0]
>>> has_one_true = len([ d for d in l if d ]) == 1
>>> has_one_true
True
11
gariel

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

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

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 @ JoranBeasley-Methode:

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

Dies scheint zu funktionieren und sollte in der Lage sein, jedes iterable zu verarbeiten, nicht nur lists. Es schließt, wann immer möglich, kurz, um die Effizienz zu maximieren. Funktioniert sowohl in Python 2 als auch in 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
4
martineau
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 abhängig von den Datentypen in Ihrer Liste zu Problemen führen.

3
Joran Beasley

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

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

edit: bessere Version

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

edit2: include mindestens m Trues und höchstens n Trues

def test(iterable, n=1, m=1): 
    i = iter(iterable) 
    return  m <= sum(any(i) for x in xrange(n+1)) <= n
3
Nisan.H
def only1(l)
    sum(map(lambda x: 1 if x else 0, l)) == 1

Erläuterung: Die Funktion map ordnet eine Liste einer anderen Liste zu, indem sie True => 1 und False => 0. Wir haben jetzt eine Liste von 0s und 1s anstelle von True oder False. Jetzt summieren wir diese Liste einfach und wenn es 1 ist, gab es nur einen wahren Wert.

2
Martin Konecny

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

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 Steuerungsablauf nutzt Pythons ausgefeiltes Schleifenfeature: das else. Die Semantik besteht darin, dass Sie, wenn Sie die Iteration des verwendeten Iterators beenden, ohne break- zu verlassen, den Block else eingeben.

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

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 timen:

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 der in C geschriebenen integrierten Funktion any viel schneller (siehe Jon Clements Antwort zur 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
1
Aaron Hall

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.

0
Radek Svoboda
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 noch einmal lesen, möchten Sie anscheinend überprüfen, ob es nur einen Wahrheitswert gibt und nicht nur einen True Wert. 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, bietet nichts eine bessere Worst-Case-Leistung. Dies ist auch kurz und leicht zu lesen.

Hier ist eine für die bestmögliche Leistung optimierte Version:

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-Performance hat ein hohes k (wie in O(kn+c)), ist aber völlig allgemein.

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

0
Marcin

Ist es das, wonach du suchst?

sum(l) == 1
0
c-urchin

Hier ist etwas, das für alles, was wahr ist, funktionieren sollte, obwohl es keinen Kurzschluss hat. Ich fand es auf der Suche nach einer sauberen Möglichkeit, sich gegenseitig ausschließende Argumente zu verbieten:

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