1. ホーム
  2. python

[解決済み] Python の `string.split()` のジェネレータバージョンはありますか?

2022-06-29 21:16:34

質問

string.split() リスト のインスタンスを返します。を返すバージョンはありますか? ジェネレータ で代用できますか?ジェネレータバージョンを持つことに反対する理由はありますか?

どのように解決するのですか?

高い確率で re.finditer はかなり最小限のメモリオーバーヘッドを使用します。

def split_iter(string):
    return (x.group(0) for x in re.finditer(r"[A-Za-z']+", string))

デモです。

>>> list( split_iter("A programmer's RegEx test.") )
['A', "programmer's", 'RegEx', 'test']

を編集してください。 私のテスト方法が正しいと仮定して、python 3.2.1では一定のメモリを消費することを確認しました。私は非常に大きなサイズ(1GBかそこら)の文字列を作成し、その後、イテラブルを通して for ループで反復処理しました(リスト内包ではありません、余分なメモリが発生します)。これは、メモリの顕著な増加をもたらしませんでした (つまり、メモリの増加があったとしても、それは 1GB の文字列よりもはるかに少ないものでした)。

より一般的なバージョン。

コメントへの返答として "私は str.split との関連性が見出せないので、より一般的なものを紹介します。

def splitStr(string, sep="\s+"):
    # warning: does not yet work if sep is a lookahead like `(?=b)`
    if sep=='':
        return (c for c in string)
    else:
        return (_.group(1) for _ in re.finditer(f'(?:^|{sep})((?:(?!{sep}).)*)', string))

    # alternatively, more verbosely:
    regex = f'(?:^|{sep})((?:(?!{sep}).)*)'
    for match in re.finditer(regex, string):
        fragment = match.group(1)
        yield fragment


という考え方です。 ((?!pat).)* は、パターンがマッチし始めるだろうところまで貪欲にマッチすることを保証することによって、グループを「否定」します(ルークヘッドは正規表現の有限状態マシンの文字列を消費しません)。疑似コードでは、繰り返し消費される ( begin-of-string xor {sep} ) + as much as possible until we would be able to begin again (or hit end of string)

デモです。

>>> splitStr('.......A...b...c....', sep='...')
<generator object splitStr.<locals>.<genexpr> at 0x7fe8530fb5e8>

>>> list(splitStr('A,b,c.', sep=','))
['A', 'b', 'c.']

>>> list(splitStr(',,A,b,c.,', sep=','))
['', '', 'A', 'b', 'c.', '']

>>> list(splitStr('.......A...b...c....', '\.\.\.'))
['', '', '.A', 'b', 'c', '.']

>>> list(splitStr('   A  b  c. '))
['', 'A', 'b', 'c.', '']


(注意すべきは str.split は醜い振る舞いをします。 sep=None を最初に str.strip を実行して、先頭と末尾の空白を削除します。上記の例では意図的にそうしていません。最後の例で sep= "\s+" .)

(これを実装しようとすると、いろいろなバグ(内部的なre.errorを含む)に遭遇しました...。Negative lookbehindは固定長の区切り文字に制限されるので、それは使っていません。上記の正規表現以外のほとんどのものは、文字列の先頭と末尾のエッジケースでエラーになるようでした (例. r'(.*?)($|,)'',,,a,,b,c' を返す ['', '', '', 'a', '', 'b', 'c', ''] を返し、最後に余計な空文字列が付きます。編集履歴を見れば、一見正しく見える正規表現に実は微妙なバグがあることがわかります)。

(もし、より高いパフォーマンスのためにこれを自分で実装したい場合、(heavweightではありますが、正規表現は最も重要なことはCで実行されます)、固定長のデリミタに対して以下の疑似コードで、いくつかのコードを書くでしょう(typesを使う?それでジェネレータを動かす方法はよく分からない?)。長さLの区切り文字をハッシュ化し、O(1)の更新時間で実行中のハッシュアルゴリズムを使って文字列をスキャンしながら、長さLの実行中のハッシュを維持する。ハッシュが区切り文字と等しくなる可能性がある場合、過去数文字が区切り文字であったかどうかを手動でチェックする。文字列の先頭と末尾の特殊なケース。これは、O(N)個のテキスト検索を行う教科書的アルゴリズムのジェネレータバージョンである。マルチプロセッシング版も可能である。これらはやりすぎに見えるかもしれませんが、この質問は、本当に巨大な文字列を扱っていることを暗示しています...。その時点で、バイト オフセットが少ない場合はキャッシュする、ディスク バックアップされたバイト文字列ビュー オブジェクトを使用してディスクから作業する、より多くの RAM を購入するなど、クレイジーなことを検討できるかもしれません。)