98 votes

Comment puis-je vérifier qu'une liste a une et une seule valeur de vérité?

En python, j'ai une liste qui devrait avoir une et une seule valeur de vérité (c'est-à-dire, bool(value) is True ). Y a-t-il un moyen intelligent de vérifier cela? Pour le moment, je parcours simplement la liste et vérifie manuellement:

 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
 

Cela semble inélégant et pas très pythonique. Y a-t-il une façon plus intelligente de faire cela?

308voto

Jon Clements Points 51556

Celui qui ne nécessite pas d'importations:

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

Sinon, peut-être une version plus lisible:

 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
 

Ce:

  • Veille à ce que i ait une valeur réelle
  • Continue à regarder à partir de ce point dans l'itérable pour s'assurer qu'il n'y a pas d'autre valeur vraie

55voto

David Robinson Points 33371

Cela dépend si vous recherchez simplement la valeur True ou recherchez également d'autres valeurs évaluées logiquement à True (comme 11 ou "hello" ). Si l'ancien:

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

Si ce dernier:

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

puisque cela ferait à la fois le comptage et la conversion en une seule itération sans avoir à construire une nouvelle liste.

47voto

moooeeeep Points 10167

Le niveau le plus détaillé de la solution n'est pas toujours le plus unelegant solution. Donc j'ajoute juste une modification mineure (dans l'ordre pour sauver un peu redondant boolean évaluations):

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

Voici quelques timings pour la comparaison:

# 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

Mon résultat:

$ 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

Comme on le voit, l'OP solution est nettement mieux que la plupart des autres solutions posté ici. Comme prévu, les meilleurs sont ceux de court-circuit comportement, surtout que la solution posté par Jon Clements. Au moins pour le cas de deux au début de la True des valeurs dans une liste.

Ici, la même chose pour n' True de la valeur à tous:

$ 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

Je n'ai pas vérifier la signification statistique, mais c'est intéressant, cette fois, les approches proposées par F. J., et surtout celle de Jon Clements apparaître de nouveau à être nettement supérieure.

24voto

Andrew Clark Points 77748

Une ligne de réponse qui conserve le court-circuit comportement:

from itertools import ifilter, islice

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

Ce sera nettement plus rapide que les autres solutions ici pour de très grandes iterables qui ont deux ou plus vraies valeurs relativement tôt.

ifilter(None, itr) donne un objet iterable qui ne cèdent truthy éléments (x est truthy si bool(x) retours True). islice(itr, 2) donne un objet iterable qui ne cèdent les deux premiers éléments de la itr. Par la conversion d'une liste et vérifier que la longueur est égale à un, nous pouvons vérifier que exactement un truthy élément existe sans avoir besoin de vérifier les éléments, après que nous avons trouvé deux.

Voici quelques timing des comparaisons:

  • Le programme d'installation de 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
    
  • Présentant de court-circuit comportement:

    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
    
  • Grande liste de court-circuit ne se produit pas:

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

Si l' sum() approche est plus rapide pour les très petites listes, mais comme la liste d'entrée est importante, plus ma version est plus rapide, même en cas de court-circuit n'est pas possible. En cas de court-circuit est possible sur une grande entrée, la différence de performances est clair.

15voto

Antti Haapala Points 11542

Je voulais obtenir le badge de nécromancien. J'ai donc généralisé l'excellente réponse de Jon Clements, tout en préservant les avantages de la logique de court-circuit et de la vérification rapide des prédicats.

Voici donc:

N (valeurs) = n

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

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

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

m <= N (vrais) <= 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)
 

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X