3

私のコードの一般的なパターンは、「特定の要素が見つかるまでリストを検索し、その前後にある要素を調べる」です。

例として、重要なイベントがアスタリスクでマークされているログ ファイルを調べて、重要なイベントのコンテキストを引き出したいと思うかもしれません。

次の例では、ハイパードライブが爆発した理由を知りたい:

  Spinning up the hyperdrive
  Hyperdrive speed 100 rpm
  Hyperdrive speed 200 rpm
  Hyperdrive lubricant levels low (100 gal.)
* CRITICAL EXISTENCE FAILURE
  Hyperdrive exploded

get_item_with_context()アスタリスクが付いた最初の行を見つけて、その前の行とその次の行を表示できるn関数が必要ですm

私の試みは以下です:

import collections, itertools
def get_item_with_context(predicate, iterable, items_before = 0, items_after = 0):
    # Searches through the list of `items` until an item matching `predicate` is found.
    # Then return that item.
    # If no item matching predicate is found, return None.
    # Optionally, also return up to `items_before` items preceding the target, and
    # `items after` items after the target.
    #
    # Note:
    d = collections.deque (maxlen = items_before + 1 + items_after)
    iter1 = iterable.__iter__()
    iter2 = itertools.takewhile(lambda x: not(predicate(x)), iter1)    
    d.extend(iter2)

    # zero-length input, or no matching item
    if len(d) == 0 or not(predicate(d[-1])):
        return None

    # get context after match:
    try:
        for i in xrange(items_after):
            d.append(iter1.next())
    except StopIteration:
        pass

    if ( items_before == 0 and items_after == 0):
        return d[0]
    else:
        return list(d)

使用法は次のようにする必要があります。

>>> get_item_with_context(lambda x: x == 3, [1,2,3,4,5,6],
                          items_before = 1, items_after = 1)
[2, 3, 4]

これに関する問題:

  • を使用して、実際に一致が見つかったことを確認するためのチェックはnot(predicate(d[-1]))、何らかの理由で機能しません。常に false を返します。
  • items_after一致する項目が見つかった後にリスト内の項目が少ない場合、結果はごみです。
  • 他のエッジケース?

これを機能させる/より堅牢にする方法についてアドバイスをお願いできますか? または、車輪の再発明をしている場合は、それも遠慮なく教えてください。

4

8 に答える 8

2

collections.dequeオブジェクトを使用して、コンテキストのリング バッファを取得できます。+/- 2 行のコンテキストを取得するには、次のように初期化します。

context = collections.deque(maxlen=5)

次に、好きなものを繰り返し、すべての行に対してこれを呼び出します。

context.append(line)

で一致しcontext[2]、一致するたびに deque コンテンツ全体を出力します。

于 2012-05-03T09:53:39.990 に答える
2

これは、エッジケースを正しく処理しているようです:

from collections import deque

def item_with_context(predicate, seq, before=0, after=0):
    q = deque(maxlen=before)
    it = iter(seq)

    for s in it:
        if predicate(s):
            return list(q) + [s] + [x for _,x in zip(range(after), it)]
        q.append(s)
于 2012-05-03T13:11:23.337 に答える
1

これはおそらく完全に「非Python的」な解決策です。

import itertools

def get_item_with_context(predicate, iterable, items_before = 0, items_after = 0):
    found_index = -1
    found_element = None

    before = [None] * items_before # Circular buffer

    after = []
    after_index = 0

    for element, index in zip(iterable, itertools.count()):
        if found_index >= 0:
            after += [element]
            if len(after) >= items_after:
                break
        elif predicate(element):
            found_index = index
            found_element = element
            if not items_after:
                break
        else:
            if items_before > 0:
                before[after_index] = element
                after_index = (after_index + 1) % items_before

    if found_index >= 0:
        if after_index:
            # rotate the circular before-buffer into place
            before = before[after_index:] + before[0:after_index]
        if found_index - items_before < 0:
            # slice off elements that "fell off" the start
            before = before[items_before - found_index:]
        return before, found_element, after

    return None

for index in range(0, 8):
    x = get_item_with_context(lambda x: x == index, [1,2,3,4,5,6], items_before = 1, items_after = 2)
    print(index, x)

出力:

0 None
1 ([], 1, [2, 3])
2 ([1], 2, [3, 4])
3 ([2], 3, [4, 5])
4 ([3], 4, [5, 6])
5 ([4], 5, [6])
6 ([5], 6, [])
7 None

述語に一致するものと前後に何が来るかを明確にするために、出力を自由に変更しました。

([2], 3, [4, 5])
  ^   ^    ^
  |   |    +-- after the element
  |   +------- the element that matched the predicate
  +----------- before the element

関数は以下を処理します:

  • アイテムが見つかりません、戻りますNone(他のものを返したい場合は関数の最後の行)
  • Before-要素が完全に満たされていません(つまり、見つかった要素が開始に近すぎて、実際にそのN前の要素を取得できませんでした)
  • 後の要素が完全に満たされていません(終わりに近すぎる場合も同じです)
  • items_beforeまたはitems_afterが0に設定されている(その方向にコンテキストがない)

それは使用しています:

  • 正しい順序で要素を取得するために所定の位置に回転される、before要素の単純な循環バッファ
  • before-elementsの簡単なリスト
  • 反復可能で、インデックス可能なコレクションを必要とせず、要素を2回以上列挙せず、必要なコンテキストを見つけた後に停止します
于 2012-05-03T09:39:17.853 に答える
1
from itertools import takewhile, tee, chain
from collections import deque

def contextGet(iterable, predicate, before, after):
    iter1, iter2 = tee(iterable)

    beforeLog = deque(maxlen = before)
    for item in takewhile(lambda x: not(predicate(x)), iter1):
        beforeLog.append(item)
        iter2.next()

    afterLog = []
    for i in xrange(after + 1):
        try:
            afterLog.append(iter2.next())
        except StopIteration:
            break

    return chain(beforeLog, afterLog)

または:

def contextGet(iterable, predicate, before, after):
    it1, it2 = tee(it)
    log = deque(maxlen = (before + after + 1))
    for i in chain(dropwhile(lambda x: not predicate(x), it1), xrange(after + 1)):
        try:
            log.append(it2.next())
        except StopIteration:
            break
    return log

リストの残りの部分がパラメーターよりも短い場合、この 2 番目の要素は「前」の要素を返しすぎる可能性がありafterます。

于 2012-05-03T10:08:58.507 に答える
0

ここにもっと短いものがあります:

import collections
from itertools import islice

def windowfilter(pred, it, before=0, after=0):
        size = before + 1 + after
        q = collections.deque(maxlen=size)
        it = iter(it)
        for x in it:
                q.append(x)
                if pred(x):
                        # ok we got the item, add the trailing lines
                        more = list(islice(it, after))
                        q.extend(more)

                        # maybe there were too few items left
                        got = before + 1 + len(more)

                        # slice from the end
                        return tuple(q)[-got:]

テスト結果:

seq = [1,2,3,4,5,6]
for elem in range(8):
        print elem, windowfilter((lambda x:x==elem), seq, 2, 1)

# Output:
0 None
1 (1, 2)
2 (1, 2, 3)
3 (1, 2, 3, 4)
4 (2, 3, 4, 5)
5 (3, 4, 5, 6)
6 (4, 5, 6)
7 None
于 2012-05-03T12:40:06.410 に答える
0

問題に何か欠けているかどうかはわかりませんが、これは次のように簡単に実行できます

>>> def get_item_with_context(predicate, iterable, items_before = 0, items_after = 0):
    queue = collections.deque(maxlen=items_before+1)
    found = False
    for e in iterable:
        queue.append(e)
        if not found and predicate(e):
            queue = collections.deque(queue,items_before+1+items_after)
            found = True
        if found:
            if not items_after : break
            items_after-=1
    if not found:
        queue.clear()
    return list(queue)

>>> get_item_with_context(lambda x: x == 0, [1,2,3,4,5,6],items_before = 2, items_after = 1)
[]
>>> get_item_with_context(lambda x: x == 4, [1,2,3,4,5,6],items_before = 2, items_after = 1)
[2, 3, 4, 5]
>>> get_item_with_context(lambda x: x == 1, [1,2,3,4,5,6],items_before = 2, items_after = 1)
[1, 2]
>>> get_item_with_context(lambda x: x == 6, [1,2,3,4,5,6],items_before = 2, items_after = 1)
[4, 5, 6]
>>> get_item_with_context(lambda x: x == 4, [1,2,3,4,5,6],items_before = 20, items_after = 10)
[1, 2, 3, 4, 5, 6]
于 2012-05-03T10:07:39.057 に答える
0
import collections

def context_match(predicate, iterable, before = 0, after = 0):
    pre = collections.deque(maxlen = before + 1)
    post = []
    match = 0
    for el in iterable:
        if not match:
            pre.append(el)
            if predicate(el):
                match = 1
        elif match:
            if len(post) == after:
                break
            post.append(el)
    if not match:
        return
    output = list(pre)
    output.extend(post)
    return output

for val in xrange(8):
    print context_match(lambda x: x == val, [1,2,3,4,5,6],before = 2, after = 2)
#Output:
None
[1, 2, 3]
[1, 2, 3, 4]
[1, 2, 3, 4, 5]
[2, 3, 4, 5, 6]
[3, 4, 5, 6]
[4, 5, 6]
None
于 2012-05-03T12:28:41.330 に答える