Python 如何在谓词第一个为False的点将列表一分为二

Python 如何在谓词第一个为False的点将列表一分为二,python,Python,我一直认为应该有一个函数来实现这一点,但我已经搜索了可能的地方(谷歌、itertools文档、列表方法、其他SO问题),但没有找到我想要的 简单有效的实现: def split_at_first_false(pred, seq): first = [] second = [] true_so_far = True for item in seq: if true_so_far and pred(item): first.ap

我一直认为应该有一个函数来实现这一点,但我已经搜索了可能的地方(谷歌、itertools文档、列表方法、其他SO问题),但没有找到我想要的

简单有效的实现:

def split_at_first_false(pred, seq):
    first = []
    second = []
    true_so_far = True
    for item in seq:
        if true_so_far and pred(item):
            first.append(item)
        else:
            true_so_far = False
            second.append(item)
    return first, second

print split_at_first_false(str.isalpha, "abc1a2b")
# (['a', 'b', 'c'], ['1', 'a', '2', 'b'])
这是可行的,但感觉不对劲。应该有更好的方法做到这一点

编辑:在查看答案后,我最终使用了senderle最终建议的一个稍加修改的版本:

from itertools import chain

def split_at_pred(pred, seq):
    head = []
    it = iter(seq)
    for i in it:
        if not pred(i):
            head.append(i)
        else:
            return iter(head), chain([i], it)
    return iter(head), iter([])
它简短而优雅,无论输入是什么(字符串、列表、迭代器),输出都是两个迭代器,作为奖励,它甚至可以与以下输入一起工作:

from itertools import count
split_at_pred(lambda x: x == 5, count())

其他解决方案,那些使用迭代器的解决方案,将使用此输入耗尽内存。(请注意,这只是一个额外的奖励。在我写这个问题时,我甚至没有考虑过无限迭代器)

这似乎是itertools的工作

>>> first = list(itertools.takewhile(str.isalpha, l))
>>> second = list(itertools.dropwhile(str.isalpha, l))
>>> first
['a', 'b', 'c']
>>> second
['1', 'a', '2', 'b']
如果
l
是迭代器而不是序列,则需要更改此选项

>>> def bisect_iter(pred, i):
...     i1, i2 = itertools.tee(i)
...     return itertools.takewhile(pred, i1), itertools.dropwhile(pred, i2)
... 
>>> i1, i2 = bisect_iter(str.isalpha, iter(l))
>>> list(i1)
['a', 'b', 'c']
>>> list(i2)
['1', 'a', '2', 'b']
tee
的缺点是缓存初始值并测试两次(由
takewhile
dropwhile
进行)。那太浪费了。但是,如果要同时接受和返回迭代器,缓存值是不可避免的

但是,如果您可以从迭代器返回列表,我可以想到一个不需要额外复制或测试的解决方案,它非常接近您的解决方案:

>>> def bisect_iter_to_list(pred, it):
...     l1 = []
...     for i in it:
...         if pred(i):
...             l1.append(i)
...         else:
...             l2 = [i]
...             l2.extend(it)
...     return l1, l2
... 
>>> bisect_iter_to_list(str.isalpha, iter(l))
(['a', 'b', 'c'], ['1', 'a', '2', 'b'])
唯一的隐秘之处是,在通常会有
break
语句的地方(即
else
子句之后),我只是使用了迭代器,导致
for
循环提前终止

最后,如果您仍然希望返回迭代器,但不想进行额外的测试,那么我认为上面的一个变体是最佳的

>>> def bisect_any_to_iter(pred, it):
...     it = iter(it)
...     head = []
...     for i in it:
...         if pred(i):
...             head.append(i)
...         else:
...             tail = itertools.chain([i], it)
...             break
...     return iter(head), tail
... 
>>> a, b = bisect_iter_to_iter(str.isalpha, iter(l))
>>> list(a)
['a', 'b', 'c']
>>> list(b)
['1', 'a', '2', 'b']
这个怎么样

def split_at_first_false(pred, seq):
    pos = 0
    for item in seq:
        if not pred(item):
            return seq[:pos], seq[pos:]
        pos += 1
def split_at_first_false(pred, seq):
    for i, item in enumerate(seq):
        if not pred(item):
            return seq[:i], seq[i:]
这个怎么样

def split_at_first_false(pred, seq):
    pos = 0
    for item in seq:
        if not pred(item):
            return seq[:pos], seq[pos:]
        pos += 1
def split_at_first_false(pred, seq):
    for i, item in enumerate(seq):
        if not pred(item):
            return seq[:i], seq[i:]
试试看:

 def split_at_first_false(pred, seq):
        index = 0
        while index < len(seq):
            if not pred(seq[index]):
                 return seq[:index], seq[index+1:]
            index+=1
def split_at_first_false(pred,seq):
索引=0
而指数
请尝试以下代码:

data = "abc1a2b"

def split_at_first_false(pred, seq):
    if not isinstance(seq, list):
       seq = list(seq)
    for i,x in enumerate(seq):
        if not pred(x):
            return seq[:i], seq[i:]
    return seq, []

不要回避迭代器,这是使用迭代器的完美案例。一旦命中第一个false项,使用相同的迭代器将其余项填入第二个列表中

def split_at_false(pred, seq):
    # if seq is not already an iterator, make it one
    if not hasattr(seq,'next'):
        seq = iter(seq)

    first, second = [], []
    for item in seq:
        if not pred(item):
            second.append(item)
            break
        first.append(item)

    # at this point, seq points to the first item
    # after the false item, just add it and all the 
    # rest to the second list
    second.extend(seq)

    return first, second

is_odd = lambda x : x % 2    
print split_at_false(is_odd, [1])    
print split_at_false(is_odd, [1,2,3,4,5])
print split_at_false(is_odd, [2,3,4,5,6])
print split_at_false(is_odd, [])
印刷品:

([1], [])
([1], [2, 3, 4, 5])
([], [2, 3, 4, 5, 6])
([], [])

没有T'ing,没有额外的列表存储,没有在列表上迭代两次,没有切片,只有一个迭代器。

如果pred(item)从不返回False怎么办?我非常喜欢这个。唯一的缺点是,如果seq是迭代器,它将不起作用。请将其重命名为split_at_pred,检查pred(item)。更自然的感觉界面。然后,您可以简单地对谓词求反以实现相同的功能。为什么?因为它将返回一个字符串元组@Artsiom在我的问题中,我没有具体说明序列应该分割成什么样的对象。直观地说,当字符串用作输入时,它也应该输出字符串。列表和迭代器也是一样——你可以把你输入的东西拿出来。你可以通过使用
itertools.tee
来避免这种情况。如果你在
对分iter\u to iter
函数的顶部添加
it=iter(it)
,无论输入是字符串、迭代器还是列表,它都会起同样的作用无需将迭代器读入列表以将其传递给扩展,如
listvar.extend(list(iteratorvar))
只需传递迭代器:
listvar.extend(iteratorvar)
@Paul,说得好——我做了一个不必要的副本!谢谢