我刚刚发明了一个愚蠢的小助手功能:
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很小,但它只是感觉很奇怪。 我在这里忘记了一个成语,这使我不得不打破这个助手吗?
@Stephan202为辅助函数提出了一个非常酷的习惯用法,@Martin v. Löwis在假设谓词返回bool的情况下提出了一个更简单的内联习语。感谢@大家的帮助!
答案 0 :(得分:10)
如何在迭代器(Python 2.x和3.x兼容)上调用any
两次?
>>> 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
答案 1 :(得分: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
函数相比,并且与列表推导代码类似,它更容易概括为其他计数。我已经通过在变量中添加所需的项目来证明这一点(希望没有错误...)。
答案 2 :(得分: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)
答案 3 :(得分:2)
不确定它是否比你提出的版本更好,但是......
如果保证谓词仅返回True / False,那么
sum(map(predicate, seq)) == 1
会做(虽然它不会停在第二个元素)
答案 4 :(得分: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])
答案 5 :(得分: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
>>>
答案 6 :(得分:0)
这是修改后的@Stephan202's answer:
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()
默认为None。其含义与内置filter()
和stdlib的itertools.ifilter()
函数相同。
更明确的功能和参数名称(这是主观的)。
repeat()
允许使用大型n
。
示例:
if exactly_n_is_true(seq, 1, predicate):
# predicate() is true for exactly one item from the seq
答案 7 :(得分: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()
将其留给调用者 - 如果将它传递给生成器,则很容易需要的。
任意[开始,停止]是一个很好的奖励,但它并不像我想的那样通用。很有可能通过stop=None
来模仿,例如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()
帮助器。