リストに1つだけの真の値があることを確認するにはどうすればよいですか?


84

Pythonでは、1つだけの真の値(つまり、bool(value) is True)を持つべきリストがあります。これをチェックする賢い方法はありますか?今、私はリスト全体を繰り返し、手動でチェックしています:

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

これはエレガントではなく、あまりパイソン的ではないようです。これを行うための賢い方法はありますか?


2
私はあなたの解決策は非常にうまく、それはpythonicだと思います!
2013年

1
Common Lisp :(= 1 (count-if #'identity list))
カズ2013年

7
sum(lst) == 1
–PålGD 2013

明確にするために:True真実の値が1つしかないことを確認しますか?
Marcin 2013年

回答:


44

最も冗長な解決策は、必ずしも最も不誠実な解決策であるとは限りません。したがって、(冗長なブール評価を保存するために)わずかな変更を追加します。

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

比較するタイミングは次のとおりです。

# 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

私の出力:

$ 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

ご覧のとおり、OPソリューションは、ここに掲載されている他のほとんどのソリューションよりも大幅に優れています。予想通り、最良のものは短絡動作のあるもの、特にJonClementsによって投稿されたソリューションです。少なくともTrue、長いリストの2つの初期値の場合。

ここでは、True値がまったくない場合も同じです。

$ 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

統計的有意性は確認しませんでしたが、興味深いことに、今回はFJが提案したアプローチ、特にJonClementsが提案したアプローチが明らかに優れているようです。


4
うーん-初期の本当のタイミングを見て-は0.446最速ではありませんか?
JonClements

2
@JonClementsそれが私が最も書いた理由です、今それをより明確にしました。(投稿されたもののほとんど、テストされたもののほとんどではありません...)
moooeeeep 2013年

1
ほとんどがanyCで実装されているため、JonClementは非常に高速だと思います
Matthew

1
+1オープニングライン用。すべての答えはsum... OPのシンプルで直接的なコードよりも、実際に悪化している
WIM

2
@MarkAmery読みやすさと優雅さ(確かに短いもの)とパフォーマンス評価の両方のセクションを追加しました。賢さを求める質問なので、両方の側面を検討する必要があると思います。私が見ているように、私はこれらの関連する側面の両方に対処するための答えを提供しました。この回答が役に立たないと感じた場合は、遠慮なく反対票を投じてください。
moooeeeep 2013

259

インポートを必要としないもの:

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

あるいは、おそらくもっと読みやすいバージョン:

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

この:

  • i真の価値があることを確認するように見えます
  • 他に真の価値がないことを確認するために、反復可能でそのポイントから探し続けます

34
無@MatthewScouten ...私たちはここのiterableから消費している... ...コードを実行してみてください
ジョン・クレメンツ

12
iterableの消費ごとの@MatthewScouten。anyドキュメントに従って、false以外の値が見つかるとすぐにTrueを返します。その後、再び真の値を探し、見つかった場合は失敗として扱います...したがって、これは空のリスト、リスト/その他のシーケンス、および反復可能なものに対して機能します...
JonClements

12
@MathewScouten副作用はすべての定理を破ります!x and not x = Falsex参照透過性である場合にのみ正しいです。
ベン

14
@wimこれは、実装の詳細ではありません。これはany()、関数の文書化された機能であり、Python仕様に準拠する実装の保証された機能です。
Gareth Latty 2013年

17
これが読みやすい解決策ではないと考える人は、これを検討する必要があります。これは簡潔であり、Pythonの既知の動作と一般的な構造のみに依存しています。初心者がそれを理解しないという理由だけで、それを読みやすくしません。それはまた、それがどのように機能するかを知らない人々に即座の好奇心を刺激するので、何を知っておくべきかを教えるための優れた手段です。
dansalmo 2013年

49

値を探しているだけなのかTrueTrue論理的に評価される他の値(11またはなど"hello")も探しているのかによって異なります。前者の場合:

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

後者の場合:

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

これにより、新しいリストを作成しなくても、1回の反復でカウントと変換の両方が実行されるためです。


2
Python 3の場合:list(map(bool, l)).count(True)
突く

これは、リテラルTrueのみを検出し、他のTrue値は検出しません(つまり、正のintは空のコンテナーではないなど)
Matthew Scouten 2013年

6
OPを指摘するだけで、複数の「True」値が見つかった場合でも短絡しない可能性が高いため、特定の状況では、それらのコードによって効率が向上する可能性があります。
NominSim 2013年

2
2番目の関数はreturn sum(bool(e) for e in l) == 1。と書くことができます。boolサブクラスintとTrue / Falseは、算術に関して1/0として動作します。

1
私が使用して避けるだろうl(それはあまりにも多くのように見える変数名として1ここでは)、と私は書き換えうsum(bool(e) for e in l)としてsum(1 for e in l if e)
WIM

22

短絡動作を保持する1行の回答:

from itertools import ifilter, islice

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

これは、比較的早い段階で2つ以上の真の値を持つ非常に大きな反復可能オブジェクトの場合、ここでの他の選択肢よりも大幅に高速になります。

ifilter(None, itr)(のみtruthy要素をもたらすことイテラブルを与えるx場合truthyあるbool(x)戻りますTrue)。 islice(itr, 2)の最初の2つの要素のみを生成する反復可能オブジェクトを提供しitrます。これをリストに変換し、長さが1に等しいことを確認することで、2つ見つかった後に追加の要素を確認する必要なしに、正確に1つの真の要素が存在することを確認できます。

タイミングの比較は次のとおりです。

  • セットアップコード:

    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
    
  • 短絡動作の表示:

    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
    
  • 短絡が発生しない大きなリスト:

    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
    
  • 小さなリスト:

    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
    

したがって、このsum()アプローチは非常に小さいリストの場合は高速ですが、入力リストが大きくなると、短絡が不可能な場合でも私のバージョンの方が高速になります。大きな入力で短絡が発生する可能性がある場合、性能の違いは明らかです。


5
痛い。他の選択肢が理解できる限り、私を3回連れて行った。短絡が重要な場合は、OPのコードを使用します。これは、はるかに明白で、ほぼ同じくらい効率的だからです。

1
スタイルに賛成票を投じ、短絡を維持します。しかし、これは読みにくいです。
マシュースカウテン2013年

1
+1。OPの短絡の完全な意図を再現する唯一のもの。
NominSim 2013年

1
timeitOPソリューションとの客観的なパフォーマンスの比較のために実験を提供する場合は+1 。
moooeeeep 2013年

@moooeeeep素朴に、True「早い段階で」どこかに2つの値を持つ無限の反復可能オブジェクトがある場合、これは終了します。他の回答は、カウントを取得しようと永遠に回転します。
NominSim 2013年

15

私はネクロマンサーバッジを獲得したかったので、Jon Clementsの優れた答えを一般化し、ロジックの短絡とすべての述語チェックの高速化の利点を維持しました。

したがって、ここにあります:

N(trues)= n

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

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

11
>>> l = [0, 0, 1, 0, 0]
>>> has_one_true = len([ d for d in l if d ]) == 1
>>> has_one_true
True

4
なぜこれが反対票を投じられたのですか?私はそれがすべての中で最も単純で最も読みやすいと思います。
dansalmo 2013年

1
@dansalmo:もちろん確かなことは難しいですが、私の理論では、多くのn00b pythonプログラマー(特にJavaのバックグラウンドを持つプログラマー)は、より長い構文に慣れていると感じています。(私自身、私は、5〜10年前のような少しでしたが、今日はそれが専門外と無知を検討してください。)1
ジョナスByström

5

できるよ:

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

または

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

@JoranBeasleyの方法に基づいて構築:

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

5
if sum([bool(x) for x in list]) == 1

(すべての値がbooleanishであると仮定します。)

これはおそらくそれを合計するだけでより速くなるでしょう

sum(list) == 1   

リスト内のデータ型によっては問題が発生する場合がありますが。


1
ここでは、大文字と句読点を使用すると便利です。
Steven Rumbalski 2013年


4

これは機能しているようで、反復可能なものだけでなく、すべてを処理できるはずです。list sです。効率を最大化するために、可能な限り短絡します。Python2と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])))

出力:

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

Iこの方法の周りのロジックを再加工に関するアプローチなどiter(x for x in my_list if x)して、使用してnext使用してより多分よりよい、maplist.index
WIM

@wim:私はあなたが提案したアプローチを使用しませんでしたが、あなたのコメントは私のオリジナルの答えを修正し、それがさらに増分自然の中で作るために私を触発し、を取り除くmaplist.index
martineau 2013年

3

@ JonClements`ソリューションは最大N個の真の値に拡張されました

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

編集:より良いバージョン

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

edit2:少なくともm個のTrue最大n個のTrueを含める

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

1
いいえ、せいぜい意味します。最大でN真値の値が存在する場合にはTrueを返す:例えば3 Truesを1000年のリストになるだろうiterable.count(True) = 3NTrue(iterable, 1) = FalseNTrue(iterable, 2) = FalseNTrue(iterable, 3) = TrueNTrue(iterable, 4) = True、...それは基本的に拡張and not any(i)部分にand not any(i) and not any(i) and not...
Nisan.H

1
all(any(i) for i in xrange(n)) and not any(i)ここでは動作しませんか?
エリック

@Ericは、正確にn個のtrueに対してのみTrueを返します。anyしかし、それは私にsを合計するという考えを与えました。
nisan.H 2013年

あなたはむしろ意味any(i) and not all(any(i) for x in xrange(n))ですか?
moooeeeep 2013

@moooeeeepTrue and not all(<n booleans>)論理的には同じではありませんcount(True) <= nか?アイデアは、可能な限り最小のセットをテストし、最初の障害状態で中断することです。
nisan.H 2013

2
def only1(l)
    sum(map(lambda x: 1 if x else 0, l)) == 1

説明:mapこの関数は、別のリストにリストをマップやっTrue => 1False => 0。TrueまたはFalseの代わりに0と1のリストができました。ここで、このリストを単純に合計します。1の場合、True値は1つだけです。


1

これはあなたが探しているものですか?

sum(l) == 1

これはリストでは失敗します:[2]、要素がTrueとFalse、または1と0のみである必要があることを作成者が指定しなかったため
vtlinh 2014

1

完全を期すため、およびforループ反復のためのPythonの制御フローの高度な使用法を示すために、受け入れられた回答で余分なアカウンティングを回避して、これをわずかに高速化できます。

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.

上記の単純な制御フローは、Pythonの洗練されたループ機能を利用していますelse。セマンティクスは、消費しているイテレータをbreak削除せずに反復処理を終了すると、次のように入力することです。elseブロックにです。

これが受け入れられた答えです。これはもう少しアカウンティングを使用します。

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

これらの時間を計る:

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

したがって、受け入れられた回答には少し時間がかかります(1.5パーセントよりわずかに多い)。

当然、anyCで記述された組み込みのを使用すると、はるかに高速になります(実装については、Jon Clementの回答を参照してください。これは短い形式です)。

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

0
import collections

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

線形時間。組み込みのCounterクラスを使用します。これは、カウントをチェックするために使用する必要があるものです。

質問を読み直すと、実際には、1つの値ではなく、1つの真の値しかないことを確認したいようですTrue。これを試して:

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

最良の場合のパフォーマンスを向上させることはできますが、最悪の場合のパフォーマンスを向上させるものはありません。これも短くて読みやすいです。

最良のパフォーマンスのために最適化されたバージョンは次のとおりです。

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

最悪の場合のパフォーマンスは高いですkO(kn+c))が、それは完全に一般的です。

パフォーマンスを試すためのアイデアは次のとおりです:http://ideone.com/ZRrv2m


0

これは、短絡はありませんが、真実のことなら何でも機能するはずです。相互に排他的な議論を禁止するためのクリーンな方法を探しているときに、それを見つけました。

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

0

どうですか:

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

ブール値のTrue値のみをカウントする場合。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.