什么是在匹配元素处划分列表的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