Idiomatic Python has_one

I just came up with a silly little helper function:

def has_one(seq, predicate=bool):
    """Return whether there is exactly one item in `seq` that matches
    `predicate`, with a minimum of evaluation (short-circuit).
    """
    iterator = (item for item in seq if predicate(item))
    try:
        iterator.next()
    except StopIteration: # No items match predicate.
        return False
    try:
        iterator.next()
    except StopIteration: # Exactly one item matches predicate.
        return True
    return False # More than one item matches the predicate.

Because the most read / idiomatic built-in thing I could come up with was this:

[predicate(item) for item in seq].count(True) == 1

... this is good in my case, because I know that seq is small, but it's just weird. Is there an idiom? I forgot here, what prevents me from escaping from this assistant?

Explanation

Looking back, this was a pretty tricky question, although we got some great answers! I searched either:

  • The obvious and readable idiom or stdlib built-in function, in this case an acceptable rating is acceptable.
  • A more obvious and readable auxiliary function - since it pulls out a whole other function, only the minimum amount of the assessment seems acceptable.

@Stephan202 @Martin v. Löwis , bool. @ !

+3
8

, , , ...

True/False,

sum(map(predicate, seq)) == 1

( )

+2

any , ( Python 2.x 3.x)?

>>> def has_one(seq, predicate=bool):
...     seq = (predicate(e) for e in seq)
...     return any(seq) and not any(seq)
... 
>>> has_one([])
False
>>> has_one([1])
True
>>> has_one([0])
False
>>> has_one([1, 2])
False

any , True . , .

: , , n . , all:

>>> def has_n(seq, n, predicate=bool):
...     seq = (predicate(e) for e in seq)
...     return all(any(seq) for _ in range(n)) and not any(seq)
... 
>>> has_n(range(0), 3)
False
>>> has_n(range(3), 3)
False
>>> has_n(range(4), 3)
True
>>> has_n(range(5), 3)
False
+10

, - ?

def has_one(seq,predicate=bool):
    nwanted=1
    n=0
    for item in seq:
        if predicate(item):
            n+=1
            if n>nwanted:
                return False

    return n==nwanted

, . has_one , . (, ...), .

+3

Stephan202, , . , , , :

def has_one(seq):
    g = (x for x in seq)
    return any(g) and not any(g)

Edit:

, :

def has_exactly(seq, count, predicate = bool):
    g = (predicate(x) for x in seq)
    while(count > 0):
        if not any(g):
            return False
        count -= 1
    if count == 0:
        return not any(g)
+3

...

import functools
import operator

def exactly_one(seq):
    """
    Handy for ensuring that exactly one of a bunch of options has been set.
    >>> exactly_one((3, None, 'frotz', None))
    False
    >>> exactly_one((None, None, 'frotz', None))
    True
    """
    return 1 == functools.reduce(operator.__add__, [1 for x in seq if x])
+1

, ! rtfm ( "itertools" ), (), , !

Python 1.5.2 (#0, Apr 13 1999, 10:51:12) [MSC 32 bit (Intel)] on win32
Copyright 1991-1995 Stichting Mathematisch Centrum, Amsterdam
>>> def count_in_bounds(seq, predicate=lambda x: x, low=1, high=1):
...     count = 0
...     for item in seq:
...         if predicate(item):
...             count = count + 1
...             if count > high:
...                 return 0
...     return count >= low
...
>>> seq1 = [0, 0, 1, 0, 1, 0, 1, 0, 0, 0]
>>> count_in_bounds(seq1)
0
>>> count_in_bounds(seq1, low=3, high=3)
1
>>> count_in_bounds(seq1, low=3, high=4)
1
>>> count_in_bounds(seq1, low=4, high=4)
0
>>> count_in_bounds(seq1, low=0, high=3)
1
>>> count_in_bounds(seq1, low=3, high=3)
1
>>>
+1

@Stephan202 :

from itertools import imap, repeat

def exactly_n_is_true(iterable, n, predicate=None):
    it = iter(iterable) if predicate is None else imap(predicate, iterable)
    return all(any(it) for _ in repeat(None, n)) and not any(it)

:

  • predicate() . , filter() stdlib itertools.ifilter().

  • ( ).

  • repeat() n.

:

if exactly_n_is_true(seq, 1, predicate):
   # predicate() is true for exactly one item from the seq
0

> .

, any(g) and not any(g), , , / ( , , and ...).

def cumulative_sums(values):
    s = 0
    for v in values:
        s += v
        yield s

def count_in_bounds(iterable, start=1, stop=2):
    counter = cumulative_sums(bool(x) for x in iterable)
    return (start in counter) and (stop not in counter)

bool, , any() all(), - .

Taking an arbitrary [start, stop) is a great bonus, but it is not as general as we would like. It is tempting to pass on stop=Noneto follow, for example. any()which works, but always consumes all input; awkward correct emulation:

def any(iterable):
  return not count_in_bounds(iterable, 0, 1)

def all(iterable):
  return count_in_bounds((not x for x in iterable), 0, 1)

Taking a variable number of bounds and specifying what should return True / False will get out of control.
Perhaps a simple saturating counter is the best primitive:

def count_true(iterable, stop_at=float('inf')):
    c = 0
    for x in iterable:
        c += bool(x)
        if c >= stop_at:
            break
    return c

def any(iterable):
    return count_true(iterable, 1) >= 1

def exactly_one(iterable):
    return count_true(iterable, 2) == 1

def weird(iterable):
    return count_true(iterable, 10) in {2, 3, 5, 7}

all()still requires denying inputs or a suitable helper count_false().

0
source

Source: https://habr.com/ru/post/1720375/


All Articles