哨兵连续出现两次时停止迭代的简洁方法
Concise way of stopping iteration when sentinel occurs twice in a row
我正在寻找一种生成迭代器的方法,该迭代器采用可迭代对象并仅传递值,直到标记值直接连续出现两次。与 iter(a.__next__, sentinel)
类似,只是标记必须出现两次。
以下相当平淡的代码可以解决问题,但肯定有一个不那么冗长的解决方案?
所以把它放在一个具体的问题中:
有没有办法避免使用成熟的生成器并使用 itertools
或生成器表达式来实现同样的效果?
>>> def repeat_offenders(a, sentinel):
... ia = iter(a)
... for x in ia:
... if x==sentinel:
... try:
... y = next(ia)
... except StopIteration:
... yield x
... raise
... if y==sentinel:
... raise StopIteration
... yield x
... yield y
... else:
... yield x
这里有两个例子:
>>> ''.join(repeat_offenders('ABCABCAABBCC', 'B'))
'ABCABCAA'
>>> ''.join(repeat_offenders('ABABAB', 'B'))
'ABABAB'
注意 this question 类似但缺少发电机角度。
您可以根据 iwindow
、滑动 window 配方(它可以在任何可迭代对象上工作,而不仅仅是序列)和通常的 iter(callable, sentinel)
成语:
import itertools as IT
def iwindow(iterable, n=2):
"""
Returns a sliding window (of width n) over data from the iterable.
s -> (s0,s1,...s[n-1]), (s1,s2,...,sn), ..., (sk, None, ..., None)
"""
iterables = IT.tee(iterable, n)
iterables = (IT.islice(it, pos, None) for pos, it in enumerate(iterables))
yield from IT.zip_longest(*iterables)
def repeat_offenders(iterable, sentinel, repeat=2):
return (item[0] for item in iter(iwindow(iterable, repeat).__next__,
(sentinel,)*repeat))
print(''.join(repeat_offenders('ABCABCAABBCC', 'B', 2)))
# ABCABCAA
print(''.join(repeat_offenders('ABABAB', 'B', 2)))
# ABABAB
iwindow
是 itertools 文档中显示的 pairwise
recipe 的概括。通过将 repeat_offenders
写成 iwindow
,我们可以将概念概括为在 n
几乎免费重复后停止。
不确定是否有帮助,但我相信您的代码可以写得更简洁,如下所示:
from itertools import zip_longest
def repeat_offenders_jp(a, s):
for i, j in zip_longest(a, a[1:]):
if i == j and i == s:
break
else:
yield i
''.join(repeat_offenders_jp('ABCABCAABBCC', 'B')) # 'ABCABCAA'
''.join(repeat_offenders_jp('ABABAB', 'B')) # 'ABABAB'
此处 zip_longest
为@jp_data_analysis 建议但为 "one-liner" 和 takewhile
:
from itertools import zip_longest, takewhile
sentinel = 'B'
string = 'ABCABCAABBCC'
"".join(t[0] for t
in takewhile(lambda t: t[0] != sentinel or t[0] != t[1],
zip_longest(string, string[1:])))
# 'ABCABCAA'
string = 'ABABAB'
"".join(t[0] for t
in takewhile(lambda t: t[0] != sentinel or t[0] != t[1],
zip_longest(string, string[1:])))
# 'ABABAB'
我正在寻找一种生成迭代器的方法,该迭代器采用可迭代对象并仅传递值,直到标记值直接连续出现两次。与 iter(a.__next__, sentinel)
类似,只是标记必须出现两次。
以下相当平淡的代码可以解决问题,但肯定有一个不那么冗长的解决方案?
所以把它放在一个具体的问题中:
有没有办法避免使用成熟的生成器并使用 itertools
或生成器表达式来实现同样的效果?
>>> def repeat_offenders(a, sentinel):
... ia = iter(a)
... for x in ia:
... if x==sentinel:
... try:
... y = next(ia)
... except StopIteration:
... yield x
... raise
... if y==sentinel:
... raise StopIteration
... yield x
... yield y
... else:
... yield x
这里有两个例子:
>>> ''.join(repeat_offenders('ABCABCAABBCC', 'B'))
'ABCABCAA'
>>> ''.join(repeat_offenders('ABABAB', 'B'))
'ABABAB'
注意 this question 类似但缺少发电机角度。
您可以根据 iwindow
、滑动 window 配方(它可以在任何可迭代对象上工作,而不仅仅是序列)和通常的 iter(callable, sentinel)
成语:
import itertools as IT
def iwindow(iterable, n=2):
"""
Returns a sliding window (of width n) over data from the iterable.
s -> (s0,s1,...s[n-1]), (s1,s2,...,sn), ..., (sk, None, ..., None)
"""
iterables = IT.tee(iterable, n)
iterables = (IT.islice(it, pos, None) for pos, it in enumerate(iterables))
yield from IT.zip_longest(*iterables)
def repeat_offenders(iterable, sentinel, repeat=2):
return (item[0] for item in iter(iwindow(iterable, repeat).__next__,
(sentinel,)*repeat))
print(''.join(repeat_offenders('ABCABCAABBCC', 'B', 2)))
# ABCABCAA
print(''.join(repeat_offenders('ABABAB', 'B', 2)))
# ABABAB
iwindow
是 itertools 文档中显示的 pairwise
recipe 的概括。通过将 repeat_offenders
写成 iwindow
,我们可以将概念概括为在 n
几乎免费重复后停止。
不确定是否有帮助,但我相信您的代码可以写得更简洁,如下所示:
from itertools import zip_longest
def repeat_offenders_jp(a, s):
for i, j in zip_longest(a, a[1:]):
if i == j and i == s:
break
else:
yield i
''.join(repeat_offenders_jp('ABCABCAABBCC', 'B')) # 'ABCABCAA'
''.join(repeat_offenders_jp('ABABAB', 'B')) # 'ABABAB'
此处 zip_longest
为@jp_data_analysis 建议但为 "one-liner" 和 takewhile
:
from itertools import zip_longest, takewhile
sentinel = 'B'
string = 'ABCABCAABBCC'
"".join(t[0] for t
in takewhile(lambda t: t[0] != sentinel or t[0] != t[1],
zip_longest(string, string[1:])))
# 'ABCABCAA'
string = 'ABABAB'
"".join(t[0] for t
in takewhile(lambda t: t[0] != sentinel or t[0] != t[1],
zip_longest(string, string[1:])))
# 'ABABAB'