Làm cách nào để kiểm tra danh sách có một và chỉ một giá trị trung thực?


82

Trong python, tôi có một danh sách nên có một và chỉ một giá trị trung thực (nghĩa là bool(value) is True). Có cách nào thông minh để kiểm tra điều này không? Ngay bây giờ, tôi chỉ đang lặp lại danh sách và kiểm tra theo cách thủ công:

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

Điều này có vẻ không lịch sự và không phải là rất quan trọng. Có cách nào thông minh hơn để làm điều này không?


2
Tôi nghĩ rằng giải pháp của bạn là khá tốt và nó là pythonic!
wim

1
Common Lisp: (= 1 (count-if #'identity list)).
Kaz

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

Cần phải rõ ràng: bạn muốn kiểm tra xem chỉ có một Truehay chỉ một giá trị trung thực?
Marcin

Câu trả lời:


44

Giải pháp dài dòng nhất không phải lúc nào cũng là giải pháp nhẹ nhàng nhất. Do đó, tôi chỉ thêm một sửa đổi nhỏ (để tiết kiệm một số đánh giá boolean dư thừa):

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

Dưới đây là một số thời gian để so sánh:

# 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

Đầu ra của tôi:

$ 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

Có thể thấy, giải pháp OP tốt hơn đáng kể so với hầu hết các giải pháp khác được đăng ở đây. Như mong đợi, những giải pháp tốt nhất là những giải pháp có hành vi ngắn mạch, đặc biệt là giải pháp được đăng bởi Jon Clements. Ít nhất là đối với trường hợp hai Truegiá trị ban đầu trong một danh sách dài.

Ở đây giống nhau không có Truegiá trị nào cả:

$ 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

Tôi đã không kiểm tra ý nghĩa thống kê, nhưng thú vị là lần này các phương pháp tiếp cận do FJ đề xuất và đặc biệt là phương pháp của Jon Clements lại tỏ ra vượt trội hơn rõ ràng.


4
Umm - nhìn vào thời gian thực ban đầu - không phải 0.446là nhanh nhất sao?
Jon Clements

2
@JonClements đó là lý do tại sao tôi đã viết hầu hết , làm cho nó rõ ràng hơn bây giờ. (hầu hết các bài đã đăng, không phải hầu hết các bài đã kiểm tra ...)
moooeeeep

1
Tôi nghi ngờ rằng JonClement's quá nhanh vì hầu hết anyđược triển khai trong C
Matthew Scouten

1
+1 cho dòng mở đầu của bạn. Tất cả các câu trả lời với sumthực sự tồi tệ hơn so với mã đơn giản và trực tiếp của OP ..
wim

2
@MarkAmery Tôi đã thêm cả một phần về khả năng đọc và sự sang trọng (thừa nhận là một phần ngắn) và đánh giá hiệu suất. Tôi nghĩ vì câu hỏi đặt ra cho sự thông minh nên cả hai khía cạnh đều phải được xem xét. Như tôi thấy, tôi đã đưa ra câu trả lời để giải quyết cả hai khía cạnh liên quan này. Nếu bạn cảm thấy câu trả lời này không hữu ích, vui lòng phản đối.
moooeeeep

256

Một cái không yêu cầu nhập:

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

Ngoài ra, có lẽ là một phiên bản dễ đọc hơn:

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ều này:

  • Hình như để đảm bảo icó bất kỳ giá trị thực nào
  • Tiếp tục tìm kiếm từ thời điểm đó trong giá trị có thể lặp lại để đảm bảo không có giá trị thực nào khác

34
@MatthewScouten không ... chúng tôi đang sử dụng từ một tệp có thể lặp lại ở đây ... hãy thử chạy mã ...
Jon Clements

12
@MatthewScouten theo mức tiêu thụ của tệp có thể lặp lại. anytheo tài liệu sẽ trả về True ngay khi tìm thấy giá trị không false. Sau đó, chúng tôi tìm kiếm giá trị true một lần nữa, và nếu được tìm thấy, hãy coi đó là lỗi ... Vì vậy, điều này sẽ hoạt động đối với các danh sách trống, danh sách / chuỗi khác và bất kỳ giá trị nào có thể lặp lại ...
Jon Clements

12
@MathewScouten Tác dụng phụ phá vỡ mọi định lý! x and not x = Falsechỉ đúng nếu xminh bạch về mặt tham chiếu.
Ben

14
@wim Đây không phải là chi tiết triển khai của any()- nó là một tính năng được lập thành văn bản của hàm và chức năng được đảm bảo của bất kỳ triển khai nào tuân theo thông số kỹ thuật Python.
Gareth Latty

17
Bất kỳ ai nghĩ rằng đây không phải là một giải pháp dễ đọc nên cân nhắc điều này: Nó ngắn gọn và chỉ dựa trên các hành vi đã biết và cấu trúc phổ biến của Python. Chỉ vì một noob sẽ không hiểu nó, không làm cho nó có thể đọc được. Nó cũng là một phương tiện tuyệt vời để dạy những gì nên biết vì nó kích thích sự tò mò ngay lập tức ở những người không biết nó hoạt động như thế nào.
dansalmo

49

Nó phụ thuộc vào việc bạn chỉ đang tìm kiếm giá trị Truehay cũng đang tìm kiếm các giá trị khác có thể đánh giá một Truecách hợp lý (như 11hoặc "hello"). Nếu trước đây:

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

Nếu sau này:

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

vì điều này sẽ thực hiện cả việc đếm và chuyển đổi trong một lần lặp lại mà không cần phải tạo danh sách mới.


2
Trong Python 3:list(map(bool, l)).count(True)
chọc

Điều này chỉ tìm thấy các giá trị True theo nghĩa đen, không phải các giá trị true khác (nghĩa là: số nguyên dương không phải vùng chứa rỗng, v.v.)
Matthew Scouten

6
Chỉ để chỉ ra cho OP điều này có khả năng sẽ không ngắn mạch khi tìm thấy nhiều hơn một giá trị "Đúng", vì vậy mã của chúng có thể mang lại cho chúng hiệu quả hơn trong một số trường hợp nhất định.
NominSim

2
Hàm thứ hai có thể được viết dưới dạng return sum(bool(e) for e in l) == 1. boolcác lớp con intvà True / False hoạt động như 1/0 liên quan đến số học.

1
Tôi sẽ tránh sử dụng lnhư một tên biến (nó trông giống quá 1ở đây) và tôi sẽ viết lại sum(bool(e) for e in l)thànhsum(1 for e in l if e)
wim

22

Câu trả lời một dòng duy trì hành vi đoản mạch:

from itertools import ifilter, islice

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

Điều này sẽ nhanh hơn đáng kể so với các lựa chọn thay thế khác ở đây cho các vòng lặp rất lớn có hai hoặc nhiều giá trị thực tương đối sớm.

ifilter(None, itr)đưa ra một giá trị có thể lặp lại sẽ chỉ mang lại các phần tử true ( xlà true nếu bool(x)trả về True). islice(itr, 2)cung cấp một có thể lặp lại sẽ chỉ mang lại hai phần tử đầu tiên của itr. Bằng cách chuyển đổi danh sách này thành một danh sách và kiểm tra xem độ dài có bằng một, chúng ta có thể xác minh rằng tồn tại chính xác một phần tử trung thực mà không cần kiểm tra thêm bất kỳ phần tử nào sau khi chúng tôi đã tìm thấy hai phần tử.

Dưới đây là một số so sánh về thời gian:

  • Mã thiết lập:

    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
    
  • Biểu hiện hành vi ngắn mạch:

    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
    
  • Danh sách lớn không xảy ra hiện tượng đoản mạch:

    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
    
  • Danh sách nhỏ:

    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
    

Vì vậy, sum()cách tiếp cận nhanh hơn đối với các danh sách rất nhỏ, nhưng khi danh sách đầu vào lớn hơn, phiên bản của tôi nhanh hơn ngay cả khi không thể xảy ra đoản mạch. Khi có thể xảy ra hiện tượng đoản mạch trên một đầu vào lớn, sự khác biệt về hiệu suất là rõ ràng.


5
Ôi! Cho tôi ba lần miễn là các tùy chọn khác để hiểu. Nếu đoản mạch là quan trọng, tôi sẽ lấy mã của OP vì nó rõ ràng hơn và gần như hiệu quả hơn nhiều.

1
Lên bầu chọn phong cách, và giữ lại mạch ngắn. Nhưng điều này khó đọc hơn.
Matthew Scouten

1
+1. Chiếc duy nhất tái hiện toàn bộ ý định đoản mạch của OP.
NominSim

1
+1 nếu bạn cung cấp một số timeitthử nghiệm để so sánh hiệu suất khách quan với giải pháp OP.
moooeeeep

@moooeeeep Ngại ngùng nếu bạn có một Truegiá trị lặp lại vô hạn có hai giá trị ở đâu đó "sớm", điều này sẽ kết thúc, so với các câu trả lời khác quay bánh xe của họ mãi mãi để cố gắng đếm.
NominSim

15

Tôi muốn kiếm được huy hiệu thuật sĩ, vì vậy tôi đã khái quát câu trả lời xuất sắc của Jon Clements, bảo toàn lợi ích của logic đoản mạch và kiểm tra vị từ nhanh chóng với bất kỳ và tất cả.

Vì vậy, đây là:

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 (điểm) <= 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
Tại sao điều này bị phản đối? Tôi nghĩ nó là đơn giản nhất và dễ đọc nhất.
dansalmo

1
@dansalmo: Tất nhiên rất khó để chắc chắn, nhưng lý thuyết của tôi là nhiều lập trình viên python n00b - có lẽ là những người có nền tảng Java nói riêng - cảm thấy thoải mái hơn với cú pháp dài hơn. (Bản thân tôi, tôi đã từng có một chút như vậy cách đây 5-10 năm, nhưng ngày nay tôi coi điều đó là thiếu chuyên nghiệp và thiếu hiểu biết.) +1
Jonas Byström

5

Bạn có thể làm:

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

Hoặc là

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

Xây dựng dựa trên phương pháp của @ JoranBeasley:

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

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

(Giả sử tất cả các giá trị của bạn là booleanish.)

Điều này có lẽ sẽ nhanh hơn chỉ cần tính tổng

sum(list) == 1   

mặc dù nó có thể gây ra một số vấn đề tùy thuộc vào các loại dữ liệu trong danh sách của bạn.


1
Một số viết hoa và dấu câu sẽ rất hay ở đây.
Steven Rumbalski

@StevenRumbalski Ok: P
tckmn

4

Nếu chỉ có một True, thì độ dài của Trues phải là một:

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

2
Có lẽ bạn có thể giải thích câu trả lời của bạn?
Linus Caldwell

4

Điều này dường như hoạt động và có thể xử lý bất kỳ tệp nào có thể lặp lại, không chỉ lists. Nó ngắn mạch bất cứ khi nào có thể để tối đa hóa hiệu quả. Hoạt động trên cả Python 2 và 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])))

Đầu ra:

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

Tôi thích cách tiếp cận này, làm thế nào về việc làm lại logic xung quanh iter(x for x in my_list if x)và sau đó sử dụng next, có thể đẹp hơn so với sử dụng maplist.index
wim 29/05

@wim: Mặc dù tôi không sử dụng cách tiếp cận mà bạn đề xuất, nhưng nhận xét của bạn đã truyền cảm hứng cho tôi sửa đổi câu trả lời ban đầu của mình và làm cho nó thậm chí còn gia tăng hơn về bản chất và loại bỏ dấu maplist.index.
martineau

3

Giải pháp @ JonClements` được mở rộng cho nhiều nhất N giá trị 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)

chỉnh sửa: phiên bản tốt hơn

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

edit2: bao gồm ít nhất m Truenhiều nhất 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
Không, ý tôi là nhiều nhất. Nó trả về True nếu ít nhất N giá trị đích thực có giá trị tồn tại: ví dụ 3 Trues trong một danh sách 1000 sẽ nhận được iterable.count(True) = 3, NTrue(iterable, 1) = False, NTrue(iterable, 2) = False, NTrue(iterable, 3) = True, NTrue(iterable, 4) = True, ... Nó cơ bản mở rộng and not any(i)một phầnand not any(i) and not any(i) and not...
Nisan.H

1
Không all(any(i) for i in xrange(n)) and not any(i)hoạt động ở đây?
Eric

@Eric sẽ chỉ trả về True cho đúng n true. Tuy nhiên, nó đã cho tôi ý tưởng để tổng hợp lại các anys.
Nisan.H

Ý bạn là any(i) and not all(any(i) for x in xrange(n))?
moooeeeep

@moooeeeep Không phải True and not all(<n booleans>)về mặt logic giống như count(True) <= n? Ý tưởng vẫn là kiểm tra tập hợp nhỏ nhất có thể và phá vỡ ở điều kiện hỏng hóc đầu tiên.
Nisan.H

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

Giải thích: mapHàm ánh xạ một danh sách sang một danh sách khác, làm True => 1False => 0. Bây giờ chúng ta có một danh sách các số 0 và 1 thay vì Đúng hoặc Sai. Bây giờ chúng ta chỉ cần tổng hợp danh sách này và nếu nó là 1, chỉ có một giá trị True.


1

Đây có phải là những gì bạn đang tìm kiếm?

sum(l) == 1

Điều này không cho một danh sách: [2], kể từ khi tác giả không nêu rõ rằng các yếu tố duy nhất phải là True và False, hoặc 1 và 0
vtlinh

1

Vì lợi ích của sự hoàn chỉnh và để chứng minh việc sử dụng nâng cao luồng điều khiển của Python để lặp lại vòng lặp, người ta có thể tránh tính toán thêm trong câu trả lời được chấp nhận, làm cho điều này nhanh hơn một chút.:

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.

Luồng điều khiển đơn giản ở trên sử dụng tính năng phức tạp của Python trong các vòng lặp: the else. Ngữ nghĩa là nếu bạn hoàn tất việc lặp qua trình lặp mà bạn đang sử dụng mà không breakbỏ qua nó, thì bạn nhập elsekhối.

Đây là câu trả lời được chấp nhận, sử dụng kế toán nhiều hơn một chút.

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

tính đến thời gian này:

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

Vì vậy, chúng tôi thấy rằng câu trả lời được chấp nhận mất nhiều thời gian hơn một chút (hơn một phần trăm rưỡi một chút).

Đương nhiên, việc sử dụng cài sẵn any, được viết bằng C, nhanh hơn nhiều (xem câu trả lời của Jon Clement để biết cách triển khai - đây là dạng rút gọn):

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

Thời gian tuyến tính. Sử dụng lớp Bộ đếm được tích hợp sẵn, là lớp bạn nên sử dụng để kiểm tra số lượng.

Đọc lại câu hỏi của bạn, có vẻ như bạn thực sự muốn kiểm tra xem chỉ có một giá trị trung thực chứ không phải một Truegiá trị. Thử đi:

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

Mặc dù bạn có thể có được hiệu suất trường hợp tốt nhất tốt hơn, nhưng không có gì có hiệu suất trường hợp xấu nhất tốt hơn. Điều này cũng ngắn và dễ đọc.

Đây là phiên bản được tối ưu hóa cho hiệu suất trong trường hợp tốt nhất:

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

Trường hợp xấu nhất có hiệu suất cao k(như trong O(kn+c)), nhưng nó hoàn toàn chung chung.

Đây là một lý tưởng để thử nghiệm với hiệu suất: http://ideone.com/ZRrv2m


0

Đây là một cái gì đó phải hoạt động cho bất cứ điều gì trung thực, mặc dù nó không có đoản mạch. Tôi đã tìm thấy nó trong khi đang tìm một cách rõ ràng để cấm các tranh luận loại trừ lẫn nhau:

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

0

Thế còn:

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

Nếu bạn chỉ muốn đếm các giá trị True của boolean.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.