2012-05-03 14 views
3

私のコードの共通パターンは次のようなものです: "特定の要素を見つけるまでリストを検索し、それはその前後に来る。リスト内の要素をマッチさせ、その前に `n`個の要素を返し、その後に` m`個の要素を返します

たとえば、重要なイベントにアスタリスクが付いているログファイルを調べ、重要なイベントのコンテキストを取り出すことができます。

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行です。

私の試みは、以下の通りです:これで

>>> 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]))を使用して、我々は実際に試合を発見したことを確認するチェック

  • 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) 
    

    使い方は次のようにする必要があります何らかの理由で動作しません。常にfalseを返します。

  • 一致するアイテムが見つかった後にリストにitems_after個未満のアイテムがある場合、結果はごみです。
  • 他のエッジケース?

私はこの作業を行う方法/より頑強にするためのアドバイスをしてもらえますか?あるいは、私が車輪を再発明しているなら、それも私に教えてください。

+0

これはスライシングでは達成できないものですか? –

+0

@BurhanKhalid:巻き戻しできないイテラブルを使用している可能性があります。 –

+0

なぜiterable .__ iter __() 'の代わりに' iterable .__ iter __() 'を使用しますか? – jamylak

答えて

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) 
+0

私たちには勝者があります!すべてのテストケースを簡潔に渡します。ありがとうございました。 :)(訳注: 'zip()'の ''最短シーケンスで終わる ''の動作を利用して要素の数を制限してください。) –

+0

微妙なバグがありました。 'zip(it、range(after))'は 'range(after) 'に何かがあるかどうかを調べる前に' it'の要素を取るからです。あなたが引数の順序を入れ替えるとうまくいきます。あなたのコードを修正しました。 –

+0

@ Li-aungYip:バグを示すテストケースを提供できますか?私は上手く理解できていない気がします。 – georg

1

は、これはおそらく完全に「unpythonic」ソリューションです:

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 

後:

関数ハンドル:

012要素が前に、完全に満たされていない
  • (あなたが何かを返したい場合は、関数の最後の行)が見つかりません
    • 項目は、Noneを返します(すなわち。見つかった要素は)終わりに近すぎるために同じ(アフター要素完全に満たされていない
    • )本当に前N要素を取得するには、スタートに近づきすぎた
    • items_beforeまたはitems_afterは0その方向に(なしコンテキストに設定されています)

    これは使用:

    • 正しい順序
    • 単純なリストの要素を取得するために、所定の位置に回転される前の要素のための単純な円形のバッファ、用
    • どれでも反復可能で、インデックス可能なコレクションのための必要は、任意の要素を複数回列挙しません、と
  • +0

    Python dequeを循環バッファとして動作させるように説得することができます(max_length引数で初期化することによって)。これは独自の循環バッファをローリングするよりも少しうまくいくかもしれません。 ;) –

    2

    は、あなたが使用してコンテキストのためのリングバッファを得ることが必要なコンテキストを見つけた後に停止します前に、要素collections.dequeオブジェクトです。このようにそれを初期化し、コンテキストの+/- 2行を取得するには:context[2]

    context.append(line) 
    

    マッチ、および出力全体:

    context = collections.deque(maxlen=5) 
    

    を次にあなたが好きな反復処理、ライン毎にこれを呼び出しますすべてのマッチのデキュコンテンツ。

    +0

    これは実際に私のサンプルコードがしようとしているもので、探している要素が実際に存在し、それの前後に適切な数の要素があると仮定するとうまくいきます。しかし、多くのエッジケースがあります。 –

    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] 
    
    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 
    

    この第二の1があまりにも多く返すことがあります。リストの残りがafterパラメータよりも短い場合、 "before"要素。ここで

    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 
    
    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 
    
    0

    私の答えはこれだろう、

    for k,v in enumerate(iterable): 
        #if cmp(v,predicate) == 0: 
        if v == predicate: 
         if k+items_after < len(iterable): 
          res.append((' '.join(token[(k-items_before):(k+items_after+1)]))) 
         elif k+window == len(token): 
          res.append((' '.join(token[(k-items_before):]))) 
         else: 
          res.append((' '.join(token[(k-items_before):]))) 
    
    return res 
    
    関連する問題