【问题标题】:What's an elegant way for one loop iteration to affect another?一个循环迭代影响另一个循环迭代的优雅方式是什么?
【发布时间】:2015-04-04 01:17:33
【问题描述】:

我刚才需要处理一个配置文件。由于它的生成方式,它包含如下行:

---(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,

现在,这可行,但它比我希望的更hacky。难点在于,在循环行的时候,我们希望一行的内容影响下一行。因此我的问题是:一个循环迭代影响另一个循环迭代的优雅方式是什么?

【问题讨论】:

  • 有什么理由不能独立丢弃空行?我的意思是,我无法想象它们在文件的其他部分更有意义。 (虽然使用 Python,谁知道呢?空格可能是有意义的......)
  • @Telastyn - 在这种情况下,这会起作用。我对其他想法很感兴趣,因为我不时看到类似的问题,并且从未提出比“skip_next”等布尔变量更好的解决方案
  • 你可以使用 grep 和 regex 来过滤,可能比使用 python 更容易
  • @omouse: ...然后他会有两个问题。这句话成为惯用语是有原因的。
  • 你想在 '---(more' 之前去掉 just 个空格还是可以去掉 all 个空行?如果是后者,那么您可以通过单独忽略两者来使事情变得更容易。

标签: python text-processing


【解决方案1】:

这感觉尴尬的原因是你从根本上做错了。 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

【讨论】:

  • 谢谢,我喜欢你的想法,虽然我不会一直在这段代码中调用 ReadLine()。原来你可以调用 sys.stdin.next() 来获得类似的效果,同时仍然使用“for line in sys.stding”,这看起来很方便。
【解决方案2】:

另一种方法是使用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

【讨论】:

    【解决方案3】:

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

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

    【讨论】:

      猜你喜欢
      • 2015-07-09
      • 1970-01-01
      • 2011-03-16
      • 2021-04-09
      • 2013-05-29
      • 1970-01-01
      • 2014-12-23
      • 2018-05-14
      • 2015-08-27
      相关资源
      最近更新 更多