3

我刚刚发明了一个愚蠢的小辅助函数:

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.

因为我能想到的最易读/惯用的内联内容是:

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

...这对我来说很好,因为我知道 seq 很小,但感觉很奇怪。是否有一个我在这里忘记的习语阻止我不得不打破这个助手?

澄清

回想起来,这是一个很糟糕的问题,尽管我们得到了一些很好的答案!我一直在寻找:

  • 一个明显且可读的内联习语或 stdlib 函数,在这种情况下可以接受急切的评估。
  • 一个更明显和可读的辅助函数——因为它打破了一个完整的其他函数,所以只有最小的评估量似乎是可以接受的。

@Stephan202为辅助函数提出了一个非常酷的成语, @ Martin v. Löwis在谓词返回布尔值的假设下提出了一个更简单的内联成语。谢谢@大家的帮助!

4

8 回答 8

10

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如果第一次成功而第二次失败,则只有一个元素与谓词匹配。

编辑:我看到Robert Rossney提出了一个通用版本,它检查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
于 2009-10-17T23:09:55.433 回答
3

也许这样的东西更合你的口味?

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函数相比,和列表理解代码一样,它更容易泛化到其他计数。我已经证明了这一点(希望没有错误......)通过添加一个变量来表示想要的项目数量。

于 2009-10-17T23:01:04.753 回答
3

我喜欢 Stephan202 的答案,但我更喜欢这个答案,即使它是两行而不是一行。我喜欢它,因为它同样疯狂,但更明确地说明了它的疯狂是如何运作的:

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

编辑:

这是一个支持谓词的更通用的版本:

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)
于 2009-10-17T23:37:36.570 回答
2

不确定它是否比您建议的版本更好,但是...

如果谓词保证只返回真/假,那么

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

会做(虽然它不会停在第二个元素)

于 2009-10-17T22:59:08.700 回答
1

怎么样 ...

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])
于 2009-10-22T19:02:25.857 回答
1

看,妈!没有 rtfm("itertools"),不依赖于 predicate() 返回一个布尔值,最小评估,就可以了!

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
>>>
于 2009-11-24T21:56:20.533 回答
0

这里修改了@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)

差异:

  1. predicate()默认为无。含义与内置函数filter()和 stdlib 的itertools.ifilter()函数相同。

  2. 更明确的函数和参数名称(这是主观的)。

  3. repeat()允许n使用大。

例子:

if exactly_n_is_true(seq, 1, predicate):
   # predicate() is true for exactly one item from the seq
于 2009-11-24T21:08:06.310 回答
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()其留给调用者 - 如果需要,很容易传递生成器表达式。

采取任意 [start, stop) 是一个不错的奖励,但它不像我想要的那样通用。stop=None通过模拟 eg很诱人any(),它有效,但总是消耗所有输入;正确的仿真有点尴尬:

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)

采用可变数量的边界并指定哪个应该返回 True/False 会失控。
也许一个简单的饱和计数器是最好的原语:

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()仍然需要否定输入或匹配的count_false()助手。

于 2013-01-18T05:17:22.107 回答