如何在谓词为第一个False的位置将列表拆分为两个

Lau*_*low 9 python

我一直在想应该有这样的功能,但我已经找遍了可能的地方(谷歌,itertools文档,列表法,其他做题),但无处发现相当有什么我一直在寻找.

天真和工作实施:

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'])
Run Code Online (Sandbox Code Playgroud)

它有效,但感觉不对.应该有更好的方法来做到这一点!

编辑:我在审核答案后最终使用了一个稍微修改过的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([])
Run Code Online (Sandbox Code Playgroud)

它简洁而优雅,输出是两个迭代器,无论输入(字符串,列表,迭代器),作为奖励,它甚至可以使用以下输入:

from itertools import count
split_at_pred(lambda x: x == 5, count())
Run Code Online (Sandbox Code Playgroud)

其他解决方案,那些与迭代器一起工作的解决方案,将使用此输入耗尽内存.(请注意,这只是一个奖金.无限的迭代器是我还没有考虑,甚至当我写这个问题)

sen*_*rle 13

这似乎是itertools的工作.

>>> first = list(itertools.takewhile(str.isalpha, l))
>>> second = list(itertools.dropwhile(str.isalpha, l))
>>> first
['a', 'b', 'c']
>>> second
['1', 'a', '2', 'b']
Run Code Online (Sandbox Code Playgroud)

如果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']
Run Code Online (Sandbox Code Playgroud)

的缺点tee是,所述初始值被缓存,并测试两次(由两个takewhiledropwhile).这太浪费了.但是,如果要同时接受和返回迭代器,则缓存值是不可避免的.

但是,如果你可以从迭代器返回列表,我可以想到一个不会产生额外副本或测试的解决方案,它与你的非常接近:

>>> 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'])
Run Code Online (Sandbox Code Playgroud)

唯一鬼鬼祟祟的是,通常会有一个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']
Run Code Online (Sandbox Code Playgroud)

  • 你可以使用`itertools.tee`来避免这种情况. (2认同)

mul*_*ces 7

这个怎么样?

def split_at_first_false(pred, seq):
    for i, item in enumerate(seq):
        if not pred(item):
            return seq[:i], seq[i:]
Run Code Online (Sandbox Code Playgroud)

  • 我真的很喜欢这一个.唯一的缺点是,如果seq是迭代器,它就不起作用. (2认同)
  • 将其重命名为_split_at_pred_,检查pred(item).更自然的感觉界面.然后,您可以简单地否定谓词以实现相同的功能. (2认同)