2012-05-03 127 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之后的行。

我尝试以下:

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,则结果为垃圾。
  • 其他边缘情况?

我可以请一些建议如何使这项工作/使其更强大?或者,如果我正在重新发明轮子,请随时告诉我。

+0

这是你不能用切片完成的事情吗? –

+0

@BurhanKhalid:我可能正在使用不能倒带的迭代。 –

+0

为什么使用'iterable .__ iter __()'而不是'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

有一个微妙的错误 - 从'it'消耗太多物品,因为'zip(它,range(after))'接受'it'的一个元素,然后检查'range(after)'中是否有任何内容。如果你交换参数的顺序,它的工作正常。我已更正您的代码。 –

+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 

我把改变输出的自由,以使其更清晰什么匹配谓词和之前什么来和之后:

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

函数句柄:

012 (如果您想返回别的东西的功能,最后一行)
  • 之前元素不能完全满足
    • 没有找到,返回None(即。找到的元素过于接近开始真正才把它N元素)
    • 后元素不能完全满足(同为太接近了尾声)
    • items_before或items_after设置为0(无上下文那个方向)

    它使用:

    • 对于一个简单的循环缓冲区的前元素,其被旋转到位以获得正确的顺序
    • 一个简单的列表中的元素对于之前元素
    • 可迭代的,不需要可转位的集合,不一一列举一次以上的任何元素,并找到所需要的背景下
  • +0

    你可以说服一个python deque充当一个循环缓冲区(通过用max_length参数初始化它)。这可能会比滚动你自己的循环缓冲区更好一点。 ;) –

    2

    您可以用得到的背景下环形缓冲区后,将停止一个collections.deque对象。为了得到+/- 2行内容,初始化这样的:

    context = collections.deque(maxlen=5) 
    

    然后遍历任何你喜欢的,称此为每一行:

    context.append(line) 
    

    比赛上context[2],输出全为每场比赛提供内容。

    +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 
    

    这第二个可能返回太多如果列表的其余部分短于after参数,则在“元素之前”。

    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 
    
    相关问题