web-dev-qa-db-ja.com

文字列の行を反復処理する

私はこのように定義された複数行の文字列を持っています:

foo = """
this is 
a multi-line string.
"""

この文字列は、私が書いているパーサーのテスト入力として使用しました。パーサー関数はfile- objectを入力として受け取り、それを繰り返し処理します。また、next()メソッドを直接呼び出して行をスキップするため、反復可能ではなく、入力として反復子が本当に必要です。 file- objectがテキストファイルの行を処理するように、その文字列の個々の行を繰り返し処理するイテレータが必要です。もちろん次のようにできます:

lineiterator = iter(foo.splitlines())

これを行う直接的な方法はありますか?このシナリオでは、文字列は分割のために一度通過し、その後パーサーによって再度通過する必要があります。私のテストケースでは、文字列が非常に短いので、それは問題ではありません。私は好奇心から尋ねています。 Pythonにはそのようなもののための非常に多くの便利で効率的なビルトインがありますが、私はこのニーズに合うものを見つけることができませんでした。

104
Björn Pollex

次の3つの可能性があります。

foo = """
this is 
a multi-line string.
"""

def f1(foo=foo): return iter(foo.splitlines())

def f2(foo=foo):
    retval = ''
    for char in foo:
        retval += char if not char == '\n' else ''
        if char == '\n':
            yield retval
            retval = ''
    if retval:
        yield retval

def f3(foo=foo):
    prevnl = -1
    while True:
      nextnl = foo.find('\n', prevnl + 1)
      if nextnl < 0: break
      yield foo[prevnl + 1:nextnl]
      prevnl = nextnl

if __== '__main__':
  for f in f1, f2, f3:
    print list(f())

これをメインスクリプトとして実行すると、3つの機能が同等であることを確認できます。 timeit(およびfoo* 100を使用すると、より正確な測定のための実質的な文字列を取得できます):

$ python -mtimeit -s'import asp' 'list(asp.f3())'
1000 loops, best of 3: 370 usec per loop
$ python -mtimeit -s'import asp' 'list(asp.f2())'
1000 loops, best of 3: 1.36 msec per loop
$ python -mtimeit -s'import asp' 'list(asp.f1())'
10000 loops, best of 3: 61.5 usec per loop

イテレータが構築されるだけでなくトラバースされるようにするには、list()呼び出しが必要です。

IOW、素朴な実装は非常に高速で、面白くさえありません:find呼び出しでの試行よりも6倍高速で、低レベルのアプローチよりも4倍高速です。

維持するための教訓:測定は常に良いことですが(正確でなければなりません)。 splitlinesなどの文字列メソッドは非常に高速に実装されます。非常に低いレベルでのプログラミング(特に、非常に小さな部分の+=のループ)によって文字列をまとめることは、非常に遅くなる可能性があります。

編集:@Jacobの提案を追加し、他と同じ結果が得られるようにわずかに変更しました(行の末尾の空白は保持されます)。

from cStringIO import StringIO

def f4(foo=foo):
    stri = StringIO(foo)
    while True:
        nl = stri.readline()
        if nl != '':
            yield nl.strip('\n')
        else:
            raise StopIteration

測定の結果:

$ python -mtimeit -s'import asp' 'list(asp.f4())'
1000 loops, best of 3: 406 usec per loop

.findベースのアプローチほど良くはありませんが、小さなオフバイワンバグ(+1と-1の出現を見るループなどは発生しにくいため、覚えておく価値があります)上記のf3は、自動的にオフバイワンの疑いを引き起こします-そして、そのような調整を欠いているはずの多くのループがそうするべきです-出力を確認できたので、私のコードも正しいと思いますが他の機能と」)。

しかし、分割ベースのアプローチは依然としてルールです。

余談:f4のより良いスタイルは:

from cStringIO import StringIO

def f4(foo=foo):
    stri = StringIO(foo)
    while True:
        nl = stri.readline()
        if nl == '': break
        yield nl.strip('\n')

少なくとも、少し冗長です。残念ながら、末尾の\nsを除去する必要があるため、whileループをreturn iter(stri)iter部分で置き換えることは、Pythonの最新バージョンでは冗長です) 2.3または2.4以降ですが、これも無害です)。試す価値があるかもしれません:

    return itertools.imap(lambda s: s.strip('\n'), stri)

またはそのバリエーション-しかし、これはstripベースの、最も単純で最速の理論的演習であるため、ここで停止します。

127
Alex Martelli

「その後、パーサーによって」という意味がわかりません。分割が完了すると、stringの走査は行われず、list分割文字列。文字列のサイズが絶対的に大きくない限り、これはおそらく実際にこれを達成するための最速の方法でしょう。 pythonが不変文字列を使用するということは、mustが常に新しい文字列を作成することを意味するため、これはある時点で実行する必要がありますとにかく。

文字列が非常に大きい場合、欠点はメモリ使用量にあります。元の文字列と分割文字列のリストを同時にメモリに保持し、必要なメモリを2倍にします。イテレータアプローチはこれを節約し、必要に応じて文字列を作成しますが、それでも「分割」ペナルティは支払われます。ただし、文字列がそれほど大きい場合は、通常、メモリ内にnsplit文字列が存在することを避けたいと思います。ファイルから文字列を読み取るだけの方が良いでしょう。すでに文字列を行として繰り返し処理することができます。

ただし、既にメモリに巨大な文字列がある場合、1つのアプローチはStringIOを使用することです。これは、行ごとの反復を許可することを含む、文字列へのファイルのようなインターフェイスを提供します(内部で次の改行を見つけるために.findを使用します)。次に、以下を取得します。

import StringIO
s = StringIO.StringIO(myString)
for line in s:
    do_something_with(line)
47
Brian

正規表現ベースの検索は、ジェネレータアプローチよりも高速である場合があります。

RRR = re.compile(r'(.*)\n')
def f4(arg):
    return (i.group(1) for i in RRR.finditer(arg))
3
socketpair

Modules/cStringIO.cを正しく読んだ場合、これは非常に効率的です(多少冗長ですが)。

from cStringIO import StringIO

def iterbuf(buf):
    stri = StringIO(buf)
    while True:
        nl = stri.readline()
        if nl != '':
            yield nl.strip()
        else:
            raise StopIteration
3
Jacob Oscarson

私はあなた自身を転がすことができると思います:

def parse(string):
    retval = ''
    for char in string:
        retval += char if not char == '\n' else ''
        if char == '\n':
            yield retval
            retval = ''
    if retval:
        yield retval

この実装がどれほど効率的かはわかりませんが、それは文字列を1回だけ反復します。

うーん、ジェネレーター。

編集:

もちろん、実行したい種類の解析アクションを追加することもできますが、それは非常に簡単です。

1
Wayne Werner