web-dev-qa-db-ja.com

Pythonリストの条件に一致する最初のN個のアイテムを削除します

関数matchCondition(x)がある場合、その条件に一致するPythonリスト内の最初のnアイテムを削除するにはどうすればよいですか?

1つの解決策は、各アイテムを反復処理し、削除対象としてマークする(たとえば、Noneに設定する)ことで、リストを包括的にフィルター処理することです。これには、リストを2回繰り返し、データを変更する必要があります。これを行うためのより慣用的または効率的な方法はありますか?

n = 3

def condition(x):
    return x < 5

data = [1, 10, 2, 9, 3, 8, 4, 7]
out = do_remove(data, n, condition)
print(out)  # [10, 9, 8, 4, 7] (1, 2, and 3 are removed, 4 remains)
60
Thomas Johnson

_itertools.filterfalse_ および _itertools.count_ を使用する1つの方法:

_from itertools import count, filterfalse

data = [1, 10, 2, 9, 3, 8, 4, 7]
output = filterfalse(lambda L, c=count(): L < 5 and next(c) < 3, data)
_

次にlist(output)を使用すると、次のことができます。

_[10, 9, 8, 4, 7]
_
63
Jon Clements

反復可能、条件、およびドロップする量を取るジェネレーターを作成します。データを反復処理し、条件を満たさないアイテムを生成します。条件が満たされている場合、カウンターをインクリメントし、値を生成しません。カウンターがドロップする量に達したら、常にアイテムを生成します。

def iter_drop_n(data, condition, drop):
    dropped = 0

    for item in data:
        if dropped >= drop:
            yield item
            continue

        if condition(item):
            dropped += 1
            continue

        yield item

data = [1, 10, 2, 9, 3, 8, 4, 7]
out = list(iter_drop_n(data, lambda x: x < 5, 3))

これはリストの余分なコピーを必要とせず、リストに対して1回だけ反復し、各アイテムに対して条件を1回だけ呼び出します。リスト全体を実際に表示する場合を除き、結果に対するlist呼び出しを省略し、返されたジェネレーターを直接反復処理します。

29
davidism

受け入れられた答えは、私の好みには少し魔法すぎました。以下は、フローを少しわかりやすく説明したものです。

def matchCondition(x):
    return x < 5


def my_gen(L, drop_condition, max_drops=3):
    count = 0
    iterator = iter(L)
    for element in iterator:
        if drop_condition(element):
            count += 1
            if count >= max_drops:
                break
        else:
            yield element
    yield from iterator


example = [1, 10, 2, 9, 3, 8, 4, 7]

print(list(my_gen(example, drop_condition=matchCondition)))

davidism answerのロジックと似ていますが、すべてのステップでドロップカウントを超えているかどうかをチェックする代わりに、残りのループを短絡させます。

注:持っていない場合 yield from が利用可能です。iteratorの残りのアイテムに対する別のforループに置き換えてください。

24
wim

突然変異が必要な場合:

def do_remove(ls, N, predicate):
    i, delete_count, l = 0, 0, len(ls)
    while i < l and delete_count < N:
        if predicate(ls[i]):
           ls.pop(i) # remove item at i
           delete_count, l = delete_count + 1, l - 1 
        else:
           i += 1
    return ls # for convenience

assert(do_remove(l, N, matchCondition) == [10, 9, 8, 4, 7])
4
ferhat elmas

簡単なPython:

N = 3
data = [1, 10, 2, 9, 3, 8, 4, 7]

def matchCondition(x):
    return x < 5

c = 1
l = []
for x in data:
    if c > N or not matchCondition(x):
        l.append(x)
    else:
        c += 1

print(l)

必要に応じて、これは簡単にジェネレーターに変換できます。

def filter_first(n, func, iterable):
    c = 1
    for x in iterable:
        if c > n or not func(x):
            yield x
        else:
            c += 1

print(list(filter_first(N, matchCondition, data)))
2
CoDEmanX

Python 3.8の開始、および 割り当て式の導入(PEP 572):=演算子)を使用して、リスト内包内の変数をインクリメントできます。

# items = [1, 10, 2, 9, 3, 8, 4, 7]
total = 0
[x for x in items if not (x < 5 and (total := total + 1) <= 3)]
# [10, 9, 8, 4, 7]

この:

  • 変数をtotalから0に初期化します。これは、リスト内包内で以前に一致したオカレンスの数を象徴します
  • 両方の場合、各アイテムをチェックします:
    • 除外条件に一致します(x < 5
    • そして、まだフィルターで除外したいアイテムの数を超えて破棄していない場合:
      • 代入式を介したtotaltotal := total + 1)のインクリメント
      • 同時に、totalの新しい値を廃棄するアイテムの最大数(3)と比較します
0
Xavier Guihot

リスト内包表記の使用:

n = 3
data = [1, 10, 2, 9, 3, 8, 4, 7]
count = 0
def counter(x):
    global count
    count += 1
    return x

def condition(x):
    return x < 5

filtered = [counter(x) for x in data if count < n and condition(x)]

これは、ブール短絡のおかげでn要素が見つかった後に条件のチェックも停止します。

0
tpbarron