一个循环迭代影响另一个循环迭代的优雅方式是什么?

时间:2015-04-01 15:25:49

标签: python text-processing

我现在需要处理配置文件。由于它的生成方式,它包含这样的行:

---(more 15%)---

第一步是剥离这些不需要的线。稍微扭曲一下,这些线条中的每一条都跟着一条空白线,我也想剥离它。我创建了一个快速Python脚本来执行此操作:

skip_next = False
for line in sys.stdin:
    if skip_next:
        skip_next = False
        continue    
    if line.startswith('---(more'):
        skip_next = True
        continue    
    print line,

现在,这有效,但它比我希望的更黑。难点在于,当循环遍历行时,我们希望一行的内容影响后续行。因此我的问题是:一个循环迭代影响另一个循环迭代的优雅方式是什么?

3 个答案:

答案 0 :(得分:11)

这让人觉得尴尬的原因是你从根本上做错了。 for循环应该是对系列的每个元素的顺序迭代。如果你正在做一些调用continue的东西,甚至没有查看当前元素,基于系列的前一个元素中发生的事情,你就打破了那个基本的抽象。然后,您需要使用额外的移动部件来处理尴尬,以便处理您正在设置的方形圆孔解决方案。

相反,请尝试将操作保持接近导致该操作的条件。我们知道for循环只是while循环的特殊情况的语法糖,所以让我们使用它。伪代码,因为我不熟悉Python的I / O子系统:

while not sys.stdin.eof: //or whatever
    line = sys.stdin.ReadLine()
    if line.startswith('---(more'):
        sys.stdin.ReadLine() //read the next line and ignore it
        continue    
    print line

答案 1 :(得分:7)

另一种方法是使用itertools.tee,它允许您将迭代器拆分为两个。然后,您可以将一个迭代器前进一步,将一个迭代器放在另一个前面一行。然后,您可以压缩两个迭代器,并在for循环的每一步查看前一行和当前行(我使用izip_longest,因此它不会丢弃最后一行) :

from itertools import tee, izip_longest
in1, in2 = tee(sys.stdin, 2)
next(in2)
for line, prevline in izip_longest(in1, in2, fillvalue=''):
    if line.startswith('---(more') or prevline.startswith('---(more'):
        continue
    print line

这也可以作为等效的生成器表达式来完成:

from itertools import tee, izip_longest
in1, in2 = tee(sys.stdin, 2)
next(in2)
pairs = izip_longest(in1, in2, fillvalue='')
res = (line for line, prevline in pairs
       if not line.startswith('---(more') and not prevline.startswith('---(more'))
for line in res:
    print line

或者您可以使用filter,它允许您在条件不为真时删除迭代器项。

from itertools import tee, izip_longest
in1, in2 = tee(sys.stdin, 2)
next(in2)
pairs = izip_longest(in1, in2, fillvalue='')
cond = lambda pair: not pair[0].startswith('---(more') and not pair[1].startswith('---(more')
res = filter(cond, pairs)
for line in res:
    print line

如果您愿意访问python标准库,toolz包使这更容易。它提供了sliding_window函数,允许您将a b c d e f之类的迭代器拆分为(a,b), (b,c), (c,d), (d,e), (e,f)之类的函数。这与上面的tee方法基本相同,只是将三行合并为一个:

from toolz.itertoolz import sliding_window
for line, prevline in sliding_wind(2, sys.stdin):
    if line.startswith('---(more') or prevline.startswith('---(more'):
        continue
    print line

你可以另外使用remove,基本上与filter相反,删除项目而不需要for循环:

from tools.itertoolz import sliding_window, remove
pairs = sliding_window(2, sys.stdin)
cond = lambda x: x[0].startswith('---(more') or x[1].startswith('---(more')
res = remove(cond, pairs)
for line in res:
    print line

答案 2 :(得分:0)

在这种情况下,我们可以通过手动推进迭代器来跳过一行。这导致代码有点类似于Mason Wheeler的解决方案,但仍然使用迭代语法。有一个相关的Stack Overflow question

for line in sys.stdin:
    if line.startswith('---(more'):
        sys.stdin.next()
        continue    
    print line,