¿Cómo puedo comprobar que una lista tiene un solo valor de verdad?

82

En Python, tengo una lista que debería tener uno y solo un valor de verdad (es decir, bool(value) is True). ¿Hay alguna forma inteligente de comprobarlo? En este momento, solo estoy iterando en la lista y verificando manualmente:

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

Esto parece poco elegante y no muy pitónico. ¿Existe una forma más inteligente de hacer esto?

Matthew Scouten
fuente
2
¡Creo que tu solución está bastante bien y es pitónica!
wim
1
Common Lisp: (= 1 (count-if #'identity list)).
Kaz
7
sum(lst) == 1
Pål GD
Para ser claro: ¿quiere comprobar que hay solo uno Trueo solo un valor de verdad?
Marcin

Respuestas:

44

La solución más detallada no siempre es la menos elegante. Por lo tanto, agrego solo una modificación menor (para guardar algunas evaluaciones booleanas redundantes):

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

Aquí hay algunos tiempos para comparar:

# 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

Mi salida:

$ 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

Como se puede ver, la solución OP es significativamente mejor que la mayoría de las otras soluciones publicadas aquí. Como era de esperar, los mejores son aquellos con comportamiento de cortocircuito, especialmente la solución publicada por Jon Clements. Al menos para el caso de dos Truevalores iniciales en una lista larga.

Aquí lo mismo sin ningún Truevalor:

$ 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

No verifiqué la significancia estadística, pero curiosamente, esta vez los enfoques sugeridos por FJ y especialmente el de Jon Clements nuevamente parecen ser claramente superiores.

moooeeeep
fuente
4
Umm, mirando los primeros tiempos verdaderos, ¿no es 0.446el más rápido?
Jon Clements
2
@JonClements es por eso que escribí más , lo hice más claro ahora. (la mayoría de los publicados, no la mayoría de los probados ...)
moooeeeep
1
Sospecho que el de JonClement es tan rápido porque la mayor parte anyse implementa en C
Matthew Scouten
1
+1 para su línea de apertura. Todas las respuestas con sumson en realidad peores que el código simple y directo del OP ..
wim
2
@MarkAmery Agregué una sección sobre legibilidad y elegancia (una corta, sin duda) y sobre evaluación del desempeño. Como la pregunta pedía inteligencia, creo que ambos aspectos deberían ser objeto de consideración. En mi opinión, he proporcionado una respuesta para abordar estos dos aspectos relevantes. Si cree que esta respuesta no es útil, no dude en votar en contra.
moooeeeep
256

Uno que no requiere importaciones:

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

Alternativamente, quizás una versión más legible:

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

Esta:

  • Busca asegurarse de que itenga un valor real
  • Sigue mirando desde ese punto en el iterable para asegurarse de que no haya otro valor verdadero
Jon Clements
fuente
34
@MatthewScouten no ... estamos consumiendo de un iterable aquí ... intente ejecutar el código ...
Jon Clements
12
@MatthewScouten según el consumo del iterable. anysegún los documentos, devolverá True tan pronto como se encuentre un valor que no sea falso. Después de eso, buscamos un valor verdadero nuevamente, y si lo encontramos, lo tratamos como un error ... Entonces esto funcionará para listas vacías, listas / otras secuencias y cualquier iterable ...
Jon Clements
12
@MathewScouten ¡Los efectos secundarios rompen todos los teoremas! x and not x = Falsesolo es correcto si xes referencialmente transparente.
Ben
14
Es @wim no un detalle de implementación de any()- es una característica documentada de la función y la funcionalidad garantizada de cualquier aplicación que se ajusta a la especificación de Python.
Gareth Latty
17
Cualquiera que piense que esta no es una solución legible debería considerar esto: es conciso y se basa solo en comportamientos conocidos y construcciones comunes de Python. El hecho de que un novato no lo entienda, no lo hace legible. También es un excelente medio para enseñar lo que se debe saber, ya que incita la curiosidad inmediata en quienes no ven cómo funciona.
dansalmo
49

Depende si solo está buscando el valor Trueo también está buscando otros valores que se evaluarían Truelógicamente (como 11o "hello"). Si el primero:

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

Si es el último:

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

ya que esto haría tanto el recuento como la conversión en una sola iteración sin tener que crear una nueva lista.

David Robinson
fuente
2
En Python 3:list(map(bool, l)).count(True)
toque
Esto solo encuentra el verdadero literal, no otros valores verdaderos (es decir: entradas positivas, no contenedores vacíos, etc.)
Matthew Scouten
6
Solo para señalarle al OP, esto probablemente no producirá un cortocircuito cuando se encuentre más de un valor "Verdadero", por lo que su código puede darles más eficiencia en ciertas circunstancias.
NominSim
2
La segunda función se puede escribir como return sum(bool(e) for e in l) == 1. boollas subclases inty Verdadero / Falso se comportan como 1/0 con respecto a la aritmética.
1
l1sum(bool(e) for e in l)sum(1 for e in l if e)
Evitaría
22

Una respuesta de una línea que conserva el comportamiento de cortocircuito:

from itertools import ifilter, islice

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

Esto será significativamente más rápido que las otras alternativas aquí para iterables muy grandes que tienen dos o más valores verdaderos relativamente temprano.

ifilter(None, itr)da un iterable que solo producirá elementos veraces ( xes veraz si bool(x)devuelve True). islice(itr, 2)da un iterable que solo producirá los dos primeros elementos de itr. Al convertir esto en una lista y verificar que la longitud sea igual a uno, podemos verificar que exista exactamente un elemento verdadero sin necesidad de verificar ningún elemento adicional después de haber encontrado dos.

Aquí hay algunas comparaciones de tiempos:

  • Código de configuración:

    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
    
  • Exhibiendo comportamiento de cortocircuito:

    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
    
  • Lista grande donde no se produce un cortocircuito:

    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
    
  • Lista pequeña:

    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
    

Entonces, el sum()enfoque es más rápido para listas muy pequeñas, pero a medida que la lista de entrada aumenta, mi versión es más rápida incluso cuando no es posible un cortocircuito. Cuando es posible un cortocircuito en una entrada grande, la diferencia de rendimiento es clara.

Andrew Clark
fuente
5
Ay. Me tomó tres veces más tiempo que las otras opciones para entender. Si el cortocircuito es importante, tomaría el código de OP ya que es mucho más obvio y aproximadamente igual de eficiente.
1
Voto a favor del estilo, y conservando el cortocircuito. Pero esto es más difícil de leer.
Matthew Scouten
1
+1. El único que reproduce la plena intención de cortocircuito del OP.
NominSim
1
+1 si proporciona algo de timeitexperimentación para una comparación objetiva de rendimiento con la solución OP.
moooeeeep
@moooeeeep Ingenuamente, si tuvieras un iterable infinito que tiene dos Truevalores en algún lugar "al principio", esto terminará, en comparación con las otras respuestas que hacen girar sus ruedas para siempre tratando de obtener la cuenta.
NominSim
15

Quería ganar la insignia de nigromante, así que generalicé la excelente respuesta de Jon Clements, preservando los beneficios de la lógica de cortocircuito y la verificación rápida de predicados con todos y cada uno.

Así que aquí está:

N (verdades) = n

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

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

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

m <= N (verdaderos) <= 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
fuente
11
>>> l = [0, 0, 1, 0, 0]
>>> has_one_true = len([ d for d in l if d ]) == 1
>>> has_one_true
True
gariel
fuente
4
¿Por qué se votó en contra? Creo que es el más sencillo y legible de todos.
Dansalmo,
1
@dansalmo: Es difícil estar seguro, por supuesto, pero mi teoría es que muchos programadores de python n00b, tal vez aquellos con experiencia en Java en particular, se sienten más cómodos con una sintaxis más larga. (Yo mismo, solía ser un poco así hace 5-10 años, pero hoy lo considero poco profesional e ignorante). +1
Jonas Byström
5

Tu puedes hacer:

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

O

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

Sobre la base del método de @ JoranBeasley:

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

(Suponiendo que todos sus valores son booleanos).

Esto probablemente sería más rápido simplemente resumiéndolo

sum(list) == 1   

aunque puede causar algunos problemas según los tipos de datos de su lista.

Joran Beasley
fuente
1
Algunas mayúsculas y puntuación estaría bien aquí.
Steven Rumbalski
@StevenRumbalski Ok: P
tckmn
4

Si solo hay uno True, entonces la longitud de la Trues debe ser uno:

def only_1(l): return 1 == len(filter(None, l))
Marc Laugharn
fuente
2
¿Quizás podrías explicar tu respuesta?
Linus Caldwell
4

Esto parece funcionar y debería poder manejar cualquier iterable, no solo lists. Se produce un cortocircuito siempre que sea posible para maximizar la eficiencia. Funciona tanto en Python 2 como en 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])))

Salida:

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
fuente
Me gusta este enfoque, ¿qué tal reelaborar la lógica iter(x for x in my_list if x)y luego usar next, tal vez más agradable que usar mapy?list.index
wim
@wim: Aunque no utilicé el enfoque que sugirió, su comentario me inspiró a revisar mi respuesta original y hacerla aún más incremental en la naturaleza y deshacerse de mapy list.index.
martineau
3

La solución de @ JonClements se extendió para como máximo N valores verdaderos :

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

editar: mejor versión

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

edit2: incluya al menos m True y como máximo n True

def test(iterable, n=1, m=1): 
    i = iter(iterable) 
    return  m <= sum(any(i) for x in xrange(n+1)) <= n
Nisan.H
fuente
1
No, me refiero a lo más. Devuelve True si lo sumo N-existen valores verdaderos valorado: por ejemplo, 3 Trues en una lista de 1000 obtendría iterable.count(True) = 3, NTrue(iterable, 1) = False, NTrue(iterable, 2) = False, NTrue(iterable, 3) = True, NTrue(iterable, 4) = True, ... Básicamente se extiende la and not any(i)parte deand not any(i) and not any(i) and not...
Nisan.H
1
¿No all(any(i) for i in xrange(n)) and not any(i)funciona aquí?
Eric
@Eric que solo devolvería True para exactamente n verdaderos. Sin embargo, me dio la idea de resumir las anys.
Nisan.H
¿Prefieres decir any(i) and not all(any(i) for x in xrange(n))?
moooeeeep
@moooeeeep ¿No es True and not all(<n booleans>)lógicamente lo mismo que count(True) <= n? La idea sigue siendo probar el conjunto más pequeño posible y romper en la primera condición de falla.
Nisan.H
2
def only1(l)
    sum(map(lambda x: 1 if x else 0, l)) == 1

Explicación: La mapfunción asigna una lista a otra lista, haciendo True => 1y False => 0. Ahora tenemos una lista de ceros y unos en lugar de verdadero o falso. Ahora simplemente sumamos esta lista y si es 1, solo había un valor Verdadero.

Martin Konecny
fuente
1

¿Es esto lo que estás buscando?

sum(l) == 1
c-erizo
fuente
Esto falla para una lista: [2], ya que el autor no especificó que los elementos solo deben ser Verdadero y Falso, o 1 y 0
vtlinh
1

En aras de la integridad y para demostrar el uso avanzado del flujo de control de Python para la iteración del bucle for, se puede evitar la contabilidad adicional en la respuesta aceptada, lo que hace que esto sea un poco más rápido:

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.

El flujo de control simple anterior hace uso de la característica sofisticada de bucles de Python: el else. La semántica es que si termina de iterar sobre el iterador que está consumiendo sin breaksalir de él, ingresa al elsebloque.

Aquí está la respuesta aceptada, que usa un poco más de contabilidad.

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

para cronometrar estos:

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

Entonces vemos que la respuesta aceptada toma un poco más de tiempo (un poco más del 1,5%).

Naturalmente, usar el integrado any, escrito en C, es mucho más rápido (consulte la respuesta de Jon Clement para la implementación; esta es la forma corta):

>>> 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
fuente
0
import collections

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

Tiempo lineal. Utiliza la clase Counter incorporada, que es lo que debería utilizar para comprobar los recuentos.

Al volver a leer su pregunta, parece que realmente desea verificar que solo haya un valor verdadero, en lugar de un Truevalor. Prueba esto:

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

Si bien puede obtener un mejor rendimiento en el mejor de los casos, nada tiene un mejor rendimiento en el peor de los casos. También es breve y fácil de leer.

Aquí hay una versión optimizada para el mejor rendimiento de los casos:

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

El peor de los casos tiene un rendimiento alto k(como en O(kn+c)), pero es completamente general.

Aquí hay una idea para experimentar con el rendimiento: http://ideone.com/ZRrv2m

Marcin
fuente
0

Aquí hay algo que debería funcionar para cualquier cosa veraz, aunque no tiene cortocircuito. Lo encontré mientras buscaba una forma limpia de prohibir argumentos mutuamente excluyentes:

if sum(1 for item in somelist if item) != 1:
    raise ValueError("or whatever...")
Andrés
fuente
0

Qué pasa:

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

Si solo desea contar valores booleanos verdaderos.

Radek Svoboda
fuente