什么是在匹配元素处划分列表的Pythonic和有效方法?

什么是在匹配元素处划分列表的Pythonic和有效方法?,python,Python,这与谓词非常相似,但不是基于谓词将单个元素划分为两个列表,而是在谓词失败的第一个元素处将列表分为两部分 >>> divide_list(lambda x: x < 7, list(range(10))) ([0, 1, 2, 3, 4, 5, 6], [7, 8, 9]) >>> divide_list(lambda x: x < 7, [1, 3, 5, 7, 9, 5]) ([1, 3, 5], [7, 9, 5]) >>>

这与谓词非常相似,但不是基于谓词将单个元素划分为两个列表,而是在谓词失败的第一个元素处将列表分为两部分

>>> divide_list(lambda x: x < 7, list(range(10)))
([0, 1, 2, 3, 4, 5, 6], [7, 8, 9])

>>> divide_list(lambda x: x < 7, [1, 3, 5, 7, 9, 5])
([1, 3, 5], [7, 9, 5])

>>> divide_list(lambda x: x < 7, [7, 9, 5])
([], [7, 9, 5])

>>> divide_list(lambda x: x < 7, [1, 3, 5])
([1, 3, 5], [])

>>> divide_list(lambda x: x['a'], [{'a': True, 'b': 1}, {'a': True}, {'a': False}])
([{'a': True, 'b': 1}, {'a': True}], [{'a': False}])
>>划分列表(λx:x<7,列表(范围(10)))
([0, 1, 2, 3, 4, 5, 6], [7, 8, 9])
>>>除法列表(λx:x<7[1,3,5,7,9,5])
([1, 3, 5], [7, 9, 5])
>>>除法列表(λx:x<7,[7,9,5])
([], [7, 9, 5])
>>>除法列表(λx:x<7[1,3,5])
([1, 3, 5], [])
>>>除法列表(λx:x['a'],[{'a':真,'b':1},{'a':真},{'a':假}])
([{'a':真,'b':1},{'a':真}],{'a':假}])
注意事项:

  • 输入列表可能无法排序
  • 输入列表可能包含重复的元素
  • 理想情况下,我们不希望多次评估条件(对于每个元素,如果值重复,则可以)
  • 理想情况下,它将接受迭代器作为输入(即,只能对输入数据进行一次传递)
  • 返回迭代器是可以接受的

一个让事情顺利进行的简单实现:

def divide_list(pred, lst):
    before, after = [], []
    found = False
    for item in lst:
        if not found:
            if pred(item):
                before.append(item)
            else:
                found = True
        if found:
            after.append(item)
    return before, after

以下是我相对有效的尝试:

from collections import Hashable

def divide_list(pred, list):
    # The predicate may be expensive, so we can
    # store elements that have already been checked
    # in a set for fast verification.
    elements_checked = set()

    # Assuming that every element of the list is of
    # the same type and the list is nonempty, we can
    # store a flag to check if an element is hashable.
    hashable = isinstance(list[0], Hashable)

    for index, element in enumerate(list):
        if hashable and element in elements_checked:
            continue

        if not pred(element):
            return list[:index], list[index:]

        if hashable:
            elements_checked.add(element)

    return list, []
如果你将这与其他答案进行比较,我认为这将是最快的

顺便说一句,我喜欢这个问题

我认为,除非您确实需要迭代器作为输出,否则,这可能是最好的方法。如果您的输入流是迭代器,并且您没有足够的内存来一次性实现整个过程,那么这可能会很有用,等等

在这种情况下,我认为
itertools
很棒。我最初的直觉是这样做:

# broken  :-(
def divide_iter(pred, lst):
    i = iter(lst)
    yield itertools.takewhile(lst, pred)
    yield i
不幸的是,由于各种原因,这不起作用。最值得注意的是,它删除了一个元素。即使没有,如果在进入下一个列表之前没有使用整个
takewhile
iterable,也可能会遇到问题。我认为第二个问题将是我们在使用迭代器时遇到的一个问题,所以这是一个很糟糕的问题,但这是我们为逐个元素处理而付出的代价,而不是一次具体化整个列表

相反,让我们考虑根据谓词是否返回true来对项进行分组。然后,
groupby
变得更加吸引人——唯一的问题是我们需要跟踪谓词是否返回True。有状态函数不是很有趣,因此我们可以使用类并将绑定方法作为关键参数传递给
groupby

import itertools

class _FoundTracker(object):
    def __init__(self, predicate):
        self.predicate = predicate
        self._found = False

    def check_found(self, value):
        if self._found:
            return True
        else:
           self._found = self.predicate(value)
           return self._found

def split_iterable(iterable, predicate):
    tracker = _FoundTracker(predicate)
    for i, (k, group) in enumerate(itertools.groupby(iterable, key=tracker.check_found)):
        yield group
    if i == 0:
        yield iter(())

if __name__ == '__main__':
    for group in split_iterable(xrange(10), lambda x: x < 5):
        print(list(group))
你会发现你有一些非常奇怪的行为:-)。或者:

g1, g2 = map(list, split_iterable(range(10), lambda x: x > 5))
print(g1)
print(g2)

应该可以正常工作。

这基本上是您天真的尝试,但不使用单独的布尔标志来确定谓词何时失败;它只是使用对第一个列表的引用,然后是对另一个列表的引用来进行追加

def divide_list(pred, lst):
     a, b = [], []
     curr = a
     for x in lst:
         if curr is a and not pred(x):
             curr = b
         curr.append(x)
     return a, b

@downvoter——如果你对为什么你认为这个答案不有用或者有什么问题发表评论,我很乐意尝试纠正任何错误或消除任何误解。事实上,我不确定你认为它有什么问题,所以我不确定我的改进工作应该集中在哪里。我认为这太复杂了,如果一个元素在列表中出现两次,我不太担心检查pred两次,更重要的是,我不想对每个元素检查两次pred,就像很多过滤器/过滤器的解决方案一样。set()意味着如果列表元素不可散列,这将不起作用,是吗?如果元素不可散列,那么它将检查谓词中的重复元素,而不是缓存它,这基本上等同于删除集合。理想情况下,您希望保留该集,因为如果存在许多具有昂贵谓词的重复项,它将使其变得高效。如果您愿意,我可以添加一个检查哈希性的标志,并对其进行优化,以使用或不使用该集。我指的是除法列表(lambda x:x['a'],[{'a':True,'b':1},{'a':True},{'a':False})引发TypeError:Unhabable类型:“dict”(将其添加到示例案例中,以便为其他人澄清)
def divide_list(pred, lst):
     a, b = [], []
     curr = a
     for x in lst:
         if curr is a and not pred(x):
             curr = b
         curr.append(x)
     return a, b