W Pythonie mam listę, która powinna mieć jedną i tylko jedną prawdziwą wartość (to znaczy bool(value) is True
). Czy istnieje sprytny sposób, aby to sprawdzić? W tej chwili tylko iteruję listę i ręcznie sprawdzam:
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
Wydaje się to nieeleganckie i niezbyt pytoniczne. Czy jest na to sprytniejszy sposób?
(= 1 (count-if #'identity list))
.sum(lst) == 1
True
tylko jedna prawdziwa wartość, czy tylko jedna?Odpowiedzi:
Najbardziej rozwlekłe rozwiązanie nie zawsze jest najbardziej nieeleganckie. Dlatego dodaję tylko niewielką modyfikację (w celu zaoszczędzenia kilku zbędnych ocen logicznych):
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
Oto kilka czasów do porównania:
# 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
Mój wynik:
$ 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
Jak widać, rozwiązanie OP jest znacznie lepsze niż większość innych zamieszczonych tutaj rozwiązań. Zgodnie z oczekiwaniami najlepsze są te z zachowaniem zwarciowym, zwłaszcza to rozwiązanie opublikowane przez Jona Clementsa. Przynajmniej w przypadku dwóch wczesnych
True
wartości na długiej liście.Tutaj to samo dla żadnej
True
wartości:$ 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
Nie sprawdzałem istotności statystycznej, ale co ciekawe, tym razem podejścia sugerowane przez FJ, a zwłaszcza to Jona Clementsa, ponownie wydają się wyraźnie lepsze.
źródło
0.446
to najszybsze?any
jest zaimplementowana w języku Csum
są w rzeczywistości gorsze niż prosty i bezpośredni kod PO ..Taki, który nie wymaga importu:
def single_true(iterable): i = iter(iterable) return any(i) and not any(i)
Alternatywnie, być może bardziej czytelna wersja:
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
To:
i
ma jakąkolwiek prawdziwą wartośćźródło
any
będzie zgodnie z dokumentami zwróci True, gdy tylko zostanie znaleziona wartość inna niż fałszywa. Następnie ponownie szukamy prawdziwej wartości, a jeśli zostanie znaleziona, traktujemy ją jako błąd ... Więc to zadziała dla pustych list, list / innych sekwencji i wszelkich iterowalnych ...x and not x = False
jest poprawne tylko wtedy, gdyx
jest referencyjnie przezroczyste.any()
- to udokumentowana cecha funkcji i gwarantowana funkcjonalność dowolnej implementacji, która jest zgodna ze specyfikacją Pythona.Zależy to od tego, czy szukasz tylko wartości,
True
czy też szukasz innych wartości, które można by ocenićTrue
logicznie (jak11
lub"hello"
). Jeśli pierwszy:def only1(l): return l.count(True) == 1
Jeśli ten ostatni:
def only1(l): return sum(bool(e) for e in l) == 1
ponieważ spowodowałoby to zliczanie i konwersję w jednej iteracji, bez konieczności tworzenia nowej listy.
źródło
list(map(bool, l)).count(True)
return sum(bool(e) for e in l) == 1
.bool
podklasyint
i Prawda / Fałsz zachowują się jak 1/0 w zakresie arytmetyki.l
jako nazwy zmiennej (wygląda zbyt podobnie jak1
tutaj) i przepisałbymsum(bool(e) for e in l)
jakosum(1 for e in l if e)
Jednowierszowa odpowiedź, która zachowuje zachowanie polegające na zwarciu:
from itertools import ifilter, islice def only1(l): return len(list(islice(ifilter(None, l), 2))) == 1
Będzie to znacznie szybsze niż inne alternatywy tutaj dla bardzo dużych iteracji, które mają dwie lub więcej wartości rzeczywistych stosunkowo wcześnie.
ifilter(None, itr)
daje iterowalne, które dostarczą tylko prawdziwych elementów (x
jest prawdziwe, jeślibool(x)
zwracaTrue
).islice(itr, 2)
daje element iterowalny, który zwróci tylko pierwsze dwa elementyitr
. Konwertując to na listę i sprawdzając, czy długość jest równa jeden, możemy sprawdzić, czy istnieje dokładnie jeden prawdziwy element bez konieczności sprawdzania jakichkolwiek dodatkowych elementów po znalezieniu dwóch.Oto kilka porównań czasowych:
Kod konfiguracji:
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
Wykazujące zachowanie zwarciowe:
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
Duża lista, w których nie występuje zwarcie:
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
Mała lista:
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
Więc
sum()
podejście jest szybsze w przypadku bardzo małych list, ale gdy lista wejściowa staje się większa, moja wersja jest szybsza nawet wtedy, gdy zwarcie nie jest możliwe. Gdy zwarcie jest możliwe na dużym wejściu, różnica w wydajności jest wyraźna.źródło
timeit
eksperymentów w celu obiektywnego porównania wydajności z rozwiązaniem OP.True
gdzieś „wcześnie” dwie wartości, to się skończy, w przeciwieństwie do innych odpowiedzi, które zawsze kręcą kołami, próbując policzyć.Chciałem zdobyć odznakę nekromanty, więc uogólniłem doskonałą odpowiedź Jona Clementsa, zachowując zalety logiki zwarciowej i szybkiego sprawdzania predykatów ze wszystkimi.
Oto więc:
N (prawda) = n
def n_trues(iterable, n=1): i = iter(iterable) return all(any(i) for j in range(n)) and not any(i)
N (prawda) <= 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 (prawda)> = n:
def at_least_n_trues(iterable, n=1): i = iter(iterable) return all(any(i) for j in range(n))
m <= N (prawda) <= 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)
źródło
>>> l = [0, 0, 1, 0, 0] >>> has_one_true = len([ d for d in l if d ]) == 1 >>> has_one_true True
źródło
Możesz to zrobić:
x = [bool(i) for i in x] return x.count(True) == 1
Lub
x = map(bool, x) return x.count(True) == 1
Opierając się na metodzie @ JoranBeasley:
sum(map(bool, x)) == 1
źródło
if sum([bool(x) for x in list]) == 1
(Zakładając, że wszystkie twoje wartości są logiczne).
Prawdopodobnie byłoby to szybsze po prostu zsumowanie
sum(list) == 1
chociaż może to powodować pewne problemy w zależności od typów danych na liście.
źródło
Jeśli jest tylko jeden
True
, długośćTrue
s powinna wynosić jeden:def only_1(l): return 1 == len(filter(None, l))
źródło
Wydaje się, że to działa i powinno być w stanie obsłużyć każdą iterację, nie tylko
list
s. W miarę możliwości zwiera, aby zmaksymalizować wydajność. Działa zarówno w Pythonie 2, jak i 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])))
Wynik:
źródło
iter(x for x in my_list if x)
i użycienext
, może ładniejszego niż używaniemap
ilist.index
map
ilist.index
.Rozwiązanie @ JonClements zostało rozszerzone na co najwyżej N wartości True :
# 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)
edycja: lepsza wersja
def test(iterable, n=1): i = iter(iterable) return sum(any(i) for x in xrange(n+1)) <= n
edit2: uwzględnij co najmniej m True i co najwyżej n True
def test(iterable, n=1, m=1): i = iter(iterable) return m <= sum(any(i) for x in xrange(n+1)) <= n
źródło
iterable.count(True) = 3
,NTrue(iterable, 1) = False
,NTrue(iterable, 2) = False
,NTrue(iterable, 3) = True
,NTrue(iterable, 4) = True
, ... to w zasadzie rozszerzaand not any(i)
część doand not any(i) and not any(i) and not...
all(any(i) for i in xrange(n)) and not any(i)
działa tutaj?any
s.any(i) and not all(any(i) for x in xrange(n))
?True and not all(<n booleans>)
logicznie nie jest tym samym, cocount(True) <= n
? Chodzi o to, aby przetestować najmniejszy możliwy zestaw i przerwać w pierwszym stanie awarii.def only1(l) sum(map(lambda x: 1 if x else 0, l)) == 1
Objaśnienie:
map
Funkcja odwzorowuje listę na inną listę, wykonując czynnościTrue => 1
iFalse => 0
. Mamy teraz listę zer i jedynek zamiast Prawda lub Fałsz. Teraz po prostu zsumujemy tę listę i jeśli wynosi 1, była tylko jedna wartość True.źródło
Czy tego szukasz?
sum(l) == 1
źródło
Ze względu na kompletność i aby zademonstrować zaawansowane wykorzystanie przepływu sterowania Pythona dla iteracji pętli, można uniknąć dodatkowego rozliczania w akceptowanej odpowiedzi, dzięki czemu jest to nieco szybsze .:
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.
Powyższe proste przepływ sterowania wykorzystuje Pythona wyrafinowanych funkcji pętli: the
else
. Semantyka polega na tym, że jeśli zakończysz iterację po iteratorze, który konsumujesz, bezbreak
wychodzenia z niego, wtedy wchodzisz doelse
bloku.Oto akceptowana odpowiedź, która wymaga nieco większej rachunkowości.
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
do czasu te:
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
Widzimy więc, że zaakceptowana odpowiedź trwa nieco dłużej (nieco ponad półtora procenta).
Oczywiście korzystanie z wbudowanego
any
, napisanego w C, jest znacznie szybsze (patrz odpowiedź Jona Clementa na implementację - to jest krótka forma):>>> min(timeit.repeat(lambda: single_true([0]*100 + [1, 1]))) 2.7257133318785236 >>> min(timeit.repeat(lambda: single_true([1, 1] + [0]*100))) 2.012824866380015
źródło
import collections def only_n(l, testval=True, n=1): counts = collections.Counter(l) return counts[testval] == n
Czas liniowy. Używa wbudowanej klasy Counter, której należy używać do sprawdzania zliczeń.
Po ponownym przeczytaniu pytania wygląda na to, że faktycznie chcesz sprawdzić, czy jest tylko jedna prawdziwa wartość, a nie jedna
True
wartość. Spróbuj tego: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
Chociaż możesz uzyskać lepszą wydajność w najlepszym przypadku, nic nie ma lepszej wydajności w najgorszym przypadku. Jest to również krótkie i łatwe do odczytania.
Oto wersja zoptymalizowana pod kątem wydajności w najlepszym przypadku:
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
W najgorszym przypadku wydajność jest wysoka
k
(jak wO(kn+c)
), ale jest całkowicie ogólna.Oto pomysł na eksperymentowanie z wydajnością: http://ideone.com/ZRrv2m
źródło
Oto coś, co powinno działać na wszystko, co prawda, chociaż nie ma zwarcia. Znalazłem to, szukając czystego sposobu na zakazanie wzajemnie wykluczających się argumentów:
if sum(1 for item in somelist if item) != 1: raise ValueError("or whatever...")
źródło
Co powiesz na:
len([v for v in l if type(v) == bool and v])
Jeśli chcesz liczyć tylko wartości logiczne True.
źródło