我一直认为应该有这个功能,但我搜索了可能的地方(谷歌,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())
其他解决方案,那些与迭代器一起工作的解决方案,将通过此输入耗尽内存。 (注意,这个 只是一个奖励。无限的迭代器是我写这个问题时我甚至没有考虑过的事情)
答案 0 :(得分: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']
如果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']
答案 1 :(得分:7)
这个怎么样?
def split_at_first_false(pred, seq):
for i, item in enumerate(seq):
if not pred(item):
return seq[:i], seq[i:]
答案 2 :(得分:2)
这个怎么样?
def split_at_first_false(pred, seq):
pos = 0
for item in seq:
if not pred(item):
return seq[:pos], seq[pos:]
pos += 1
答案 3 :(得分:2)
不要回避迭代器,这是一个使用它的完美案例。一旦遇到第一个错误项目,使用相同的迭代器将其余项目填入第二个列表。
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])
([], [])
没有tee'ing,没有额外的列表存储,没有在列表上重复两次,没有切片,只是一个迭代器。
答案 4 :(得分:1)
试试:
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
答案 5 :(得分:1)
请尝试以下代码:
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, []