목록에 하나의 진실한 값만 있는지 어떻게 확인할 수 있습니까?


82

파이썬에서는 단 하나의 진실 된 값 (즉, 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
나는 당신의 솔루션이 아주 훌륭하고 비단뱀이라고 생각합니다!
2013 년

1
일반적인 Lisp : (= 1 (count-if #'identity list)).
Kaz

7
sum(lst) == 1
Pål GD

명확하게 True말하면, 진실한 값 이 하나만 있는지 또는 하나만 있는지 확인하고 싶 습니까?
Marcin

답변:


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 솔루션은 여기에 게시 된 대부분의 다른 솔루션보다 훨씬 낫습니다. 예상대로 가장 좋은 것은 단락 동작이있는 사용자, 특히 Jon Clements가 게시 한 솔루션입니다. 적어도 True긴 목록 에 두 개의 초기 값이있는 경우 .

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가 제안한 접근 방식, 특히 Jon Clements가 제안한 접근 방식이 다시 분명히 우수 해 보입니다.


4
음-초기 실제 타이밍을 보면- 0.446가장 빠르지 않습니까?
Jon Clements

2
@JonClements가 내가 가장 많이 쓴 이유 입니다. (게시물의 대부분, 테스트 된 대부분이 아닙니다 ...)
moooeeeep

1
대부분이 anyC로 구현 되었기 때문에 JonClement가 너무 빠르다고 생각합니다
Matthew Scouten 2013 년

1
+1 시작 라인. 모든 답변 sum은 실제로 OP의 간단하고 직접적인 코드보다 더 나쁩니다 ..
wim

2
@MarkAmery 나는 가독성과 우아함 (짧은 부분)과 성능 평가에 대한 섹션을 모두 추가했습니다. 영리함을 요구하는 질문은 두 가지 측면 모두 고려되어야한다고 생각합니다. 제가보기에, 저는 이러한 두 가지 관련 측면을 모두 해결하기 위해 답변을 제공했습니다. 이 답변이 유용하지 않다고 생각되면 언제든지 반대 투표하십시오.
moooeeeep 2013-07-08

256

수입이 필요하지 않은 것 :

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 아니 ... 우리는 반복 가능한 여기에서 소비하고 있습니다 ... 코드를 실행 해보세요 ...
Jon Clements

12
iterable의 소비에 따라 @MatthewScouten. any문서에 따라 거짓이 아닌 값이 발견되는 즉시 True를 반환합니다. 그 후, 우리는 다시 참 값을 찾고, 발견되면 실패로 처리합니다 ... 따라서 이것은 빈 목록, 목록 / 기타 시퀀스 및 반복 가능한 모든 항목에서 작동합니다.
Jon Clements

12
@MathewScouten 부작용은 모든 정리를 깨뜨립니다! 참조 적으로 투명한 x and not x = False경우에만 정확합니다 x.
Ben

14
@wim 구현 세부 사항 이 아닙니다any() . 함수의 문서화 된 기능이며 Python 사양을 준수하는 모든 구현의 보장 된 기능입니다.
Gareth Latty 2013 년

17
이것이 읽을 수있는 해결책이 아니라고 생각하는 사람은 누구나 이것을 고려해야합니다. 이것은 간결하고 알려진 동작과 파이썬의 일반적인 구조에만 의존합니다. 멍청이가 그것을 이해하지 못한다고해서 그것을 읽을 수있게 만들지는 않습니다. 또한 그것이 어떻게 작동하는지 알지 못하는 사람들에게 즉각적인 호기심을 불러 일으키기 때문에 알아야 할 것을 가르치는 훌륭한 수단이기도합니다.
dansalmo

49

단순히 True값을 찾고 있는지 아니면 True논리적으로 평가되는 다른 값 ( 11또는 등 "hello")을 찾고 있는지에 따라 다릅니다 . 전자의 경우 :

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

후자의 경우 :

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

새 목록을 만들지 않고도 단일 반복으로 계산과 변환을 모두 수행 할 수 있기 때문입니다.


2
Python 3 :list(map(bool, l)).count(True)
poke

이것은 다른 참 값이 아닌 리터럴 True 만 찾습니다 (예 : 빈 컨테이너가 아닌 양의 정수 등)
Matthew Scouten

6
OP를 지적하기 위해 이것은 하나 이상의 "True"값이 발견 될 때 단락되지 않을 가능성이 높으므로 해당 코드는 특정 상황에서 더 많은 효율성을 제공 할 수 있습니다.
NominSim

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

단락 동작을 유지하는 한 줄 답변 :

from itertools import ifilter, islice

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

상대적으로 초기에 두 개 이상의 실제 값을 갖는 매우 큰 이터 러블의 경우 여기에있는 다른 대안보다 훨씬 빠릅니다.

ifilter(None, itr)(단 truthy 요소를 얻을 것이라는 반복자를 제공 x하는 경우 truthy입니다 bool(x)반환 True). islice(itr, 2)의 처음 두 요소 만 산출하는 iterable을 제공합니다 itr. 이것을리스트로 변환하고 길이가 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
아야. 이해해야 할 다른 옵션보다 세 배나 오래 걸렸습니다. 단락이 중요한 경우 OP의 코드가 훨씬 더 명확하고 대략적으로 효율적이므로 사용합니다.

1
스타일에 찬성 투표하고 단락을 유지하십시오. 그러나 이것은 읽기가 더 어렵습니다.
Matthew Scouten 2013 년

1
+1. OP의 완전한 단락 의도를 재현하는 유일한 사람.
NominSim

1
timeitOP 솔루션에 대한 객관적인 성능 비교를위한 실험을 제공하는 경우 +1 .
moooeeeep 2013 년

@moooeeeep 순진하게 True"초기에"어딘가에 두 개의 값 이있는 무한 반복 가능 항목 이있는 경우이 작업이 완료되고 다른 답변이 계속해서 카운트를 얻으려고 시도합니다.
NominSim

15

나는 네크로맨서 배지를 받고 싶었 기 때문에 Jon Clements의 탁월한 답변을 일반화하여 단락 논리의 이점을 보존하고 모든 사람에게 빠른 술어 검사를 수행했습니다.

따라서 여기에 있습니다 :

N (참) = n

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

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

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

m <= N (참) <= 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 파이썬 프로그래머 (특히 Java 배경 지식이있는 사람들)가 더 긴 구문에 더 익숙하다는 것입니다. (나 자신은 5-10 년 전에는 그와 조금 비슷했지만 오늘은 그것이 비전문적이고 무지하다고 생각합니다.) +1
Jonas 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

(모든 값이 부울이라고 가정합니다.)

합산하는 것이 더 빠를 것입니다.

sum(list) == 1   

목록의 데이터 유형에 따라 몇 가지 문제가 발생할 수 있습니다.


1
여기에서는 대문자와 구두점을 사용하는 것이 좋습니다.
Steven Rumbalski

@StevenRumbalski Ok : P
tckmn 2013 년

4

하나만있는 경우 s True의 길이는 True1이어야합니다.

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

2
답을 설명해 주시겠습니까?
Linus Caldwell

4

이것은 작동하는 것처럼 보이며 lists 뿐만 아니라 반복 가능한 모든 것을 처리 할 수 ​​있어야합니다 . 효율성을 극대화하기 위해 가능할 때마다 단락됩니다. Python 2와 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

나는이 방법처럼, 어떻게 주위의 논리를 재 작업에 대해 iter(x for x in my_list if x)다음 사용 next아마도, 사용하는 것보다 더 좋은 maplist.index

@wim : 귀하가 제안한 접근 방식을 사용하지 않았지만 귀하의 의견은 원래 답변을 수정하고 본질적으로 더욱 점진적으로 만들고 maplist.index.
martineau 2013 년

3

@ JonClements` 솔루션 은 최대 N 개의 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)

편집 : 더 나은 버전

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

EDIT2 : 포함 적어도 사실을 해요 그리고 대부분의 N 사실에

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) = 3, NTrue(iterable, 1) = False, NTrue(iterable, 2) = False, NTrue(iterable, 3) = True, NTrue(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를 반환하는 @Eric . any그래도 s 를 합산하는 아이디어를 얻었습니다 .
Nisan.H 2013 년

당신은 오히려 의미 any(i) and not all(any(i) for x in xrange(n))합니까?
moooeeeep

@moooeeeep True and not all(<n booleans>)논리적으로 동일 하지 count(True) <= n않습니까? 아이디어는 여전히 가능한 가장 작은 세트를 테스트하고 첫 번째 실패 조건에서 중단하는 것입니다.
Nisan.H

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

설명 : map기능하고, 다른 목록에 목록을 매핑 True => 1하고 False => 0. 이제 True 또는 False 대신 0과 1의 목록이 있습니다. 이제 우리는이 목록을 합산하고 1이면 True 값이 하나뿐입니다.


1

이것이 당신이 찾고있는 것입니까?

sum(l) == 1

이것은 목록에 대해 실패합니다 : [2], 작성자가 요소가 참과 거짓 또는 1과 0이어야한다고 지정하지 않았기 때문에
vtlinh

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 클래스를 사용합니다.

질문을 다시 읽으면 실제로 하나의 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

최악의 경우 성능은 높지만 k(에서와 같이 O(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.