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は、述語が bool を返すという仮定の下で、より単純なインライン イディオムを思いつきました。助けてくれてありがとう@みんな!

4

8 に答える 8

10

anyイテレータ (Python 2.x および 3.x 互換) で 2 回呼び出すのはどうですか?

>>> 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イテレータから評価される最大1 つの要素を取ります。True1 回目は成功し、2 回目は失敗した場合、述語に一致する要素は 1 つだけです。

編集: 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

これはリスト内包表記の例にかなり似ていますが、1つのシーケンスに対して1回のパスのみが必要です。2番目の関数と比較してhas_one、リスト内包コードと同様に、他のカウントに簡単に一般化できます。必要なアイテム数の変数を追加することで、これを(うまくいけばエラーなしで...)示しました。

于 2009-10-17T23:01:04.753 に答える
3

私はStephan202の答えが好きでしたが、これは1行ではなく2行ですが、もう少し気に入っています。私はそれが好きです.

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

ただし、提案したバージョンよりも優れているかどうかはわかりません...

述語がTrue/Falseのみを返すことが保証されている場合は、

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

(2番目の要素で停止することはありませんが)

于 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()デフォルトではNoneです。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)テーマのバリエーションがありますが、表面上は魔法のようには見えませんが、実際にはデバッグ/変更するときに同様に壊れやすいです (順序を交換することはできません。短い方法を理解する必要があります)。 -2andつの短絡するコンシューマ間で 1 つのイテレータをハンドオフするサーキット...)。

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 に答える