0

私は、言語が混在するテキストを扱っています。これは、すでに処理を行っており、単一の文字 (「文字」と呼ばれる) のリストの形式になっています。大文字と小文字の区別があるかどうかをテストするだけで、各文字がどの言語であるかを知ることができます (「test_lang」と呼ばれる小さな関数を使用)。次に、異なるタイプの文字の間にスペースを挿入したいので、文字タイプが混在する単語はありません。同時に、単語と句読点 (「punc」というリストで定義したもの) の間にスペースを挿入したいと考えています。私はこれを非常に簡単な方法で実行するスクリプトを作成しましたが、これは私には理にかなっていますが (以下)、信じられないほど遅いため、明らかに間違った方法です。

これを行うためのより良い方法を誰か教えてもらえますか?

# Add a space between Arabic/foreign mixes, and between words and punc
cleaned = ""
i = 0
while i <= len(letters)-2: #range excludes last letter to avoid Out of Range error for i+1
    cleaned += letters[i]
    # words that have case are Latin; otherwise Arabic
    if test_lang(letters[i]) != test_lang(letters[i+1]):
        cleaned += " "
    if letters[i] in punc or letters[i+1] in punc:
        cleaned += " "
    i += 1
cleaned += letters[len(letters)-1] # add in last letter
4

6 に答える 6

4

ここでいくつかのことが起こっています:

  • 文字列内のすべての文字を 2 回呼び出しますtest_lang()。これがおそらくこれが遅い主な理由です。
  • Python での文字列の連結はあまり効率的ではありません。代わりにリストまたはジェネレーターを使用してからstr.join()(ほとんどの場合、''.join()) を使用する必要があります。

を使用して、私が取るアプローチは次のitertools.groupby()とおりです。

from itertools import groupby
def keyfunc(letter):
    return (test_lang(letter), letter in punc)

cleaned = ' '.join(''.join(g) for k, g in groupby(letters, keyfunc))

これにより、文字が同じ言語の連続した文字にグループ化され、句読点であるかどうかにかかわらず、''.join(g)各グループが文字列に変換され、' '.join()これらの文字列が結合され、各文字列の間にスペースが追加されます。

また、DSM のコメントに記載されているように、それpuncがセットであることを確認してください。

于 2013-01-25T20:20:39.110 に答える
2

文字列連結を実行するたびに、新しい文字列が作成されます。文字列が長くなるほど、各連結にかかる時間が長くなります。

http://en.wikipedia.org/wiki/Schlemiel_the_Painter's_algorithm

出力の文字を格納するのに十分な大きさのリストを宣言し、それらを最後に結合する方がよい場合があります。

于 2013-01-25T20:18:40.343 に答える
1

非常に高速な、まったく異なるソリューションを提案します。

import re
cleaned = re.sub(r"(?<!\s)\b(?!\s)", " ", letters, flags=re.LOCALE)

これにより、空白の隣の単語境界でない限り、すべての単語境界にスペースが挿入されます (単語を「現在のロケールのアクセント付き文字を含む英数字のシーケンス」として定義します)。

これは、ラテン文字とアラビア文字の間、およびラテン文字と句読点の間で分割する必要があります。

于 2013-01-25T20:26:13.253 に答える
0

OP の元のコードの基本的なロジックを維持しながら、[i] と [i+1] のインデックス付けをすべて行わないことで高速化します。文字列をスキャンするprevおよびnext参照を使用し、 nextの1 文字後ろにprevを維持します。

# Add a space between Arabic/foreign mixes, and between words and punc
cleaned = ''
prev = letters[0]
for next in letters[1:]:
    cleaned += prev
    if test_lang(prev) != test_lang(next):
        cleaned += ' '
    if prev in punc or next in punc:
        cleaned += ' '
    prev = next
cleaned += next

1,000 万文字の文字列をテストしたところ、これは OP コードの約 2 倍の速度であることがわかりました。他の人が指摘しているように、「文字列の連結が遅い」という苦情は時代遅れです。''.join(...) メタファを使用してテストを再度実行すると、文字列連結を使用した場合よりもわずかに遅い実行が示されます。

test_lang() 関数を呼び出すのではなく、いくつかの単純なコードをインライン化することで、さらに高速化できる可能性があります。test_lang() が何をするのかよくわからないのでコメントできません:)。

編集:そこにあるはずのない「return」ステートメントを削除しました(テストの残骸!)。

編集: 同じ文字で test_lang() を 2 回呼び出さないことで高速化することもできます ( 1 つのループでnextを呼び出し、次のループでprevを呼び出します)。test_lang(next) の結果をキャッシュします。

于 2013-01-26T02:18:53.480 に答える
0

がボトルネックではないと仮定test_langして、私は試してみます:

''.join(
    x + ' '
    if x in punc or y in punc or test_lang(x) != test_lang(y)
    else x
    for x, y in zip(letters[:-1], letters[1:])
)
于 2013-01-25T23:36:39.927 に答える
0

Here is a solution that uses yield. I would be interested to know whether this runs any faster than your original solution.

This avoids all the indexing in the original. It just iterates through the input, holding onto a single previous character.

This should be easy to modify if your requirements change in the future.

ch_sep = ' '

def _sep_chars_by_lang(s_input):
    itr = iter(s_input)
    ch_prev = next(itr)

    yield ch_prev

    while True:
        ch = next(itr)
        if test_lang(ch_prev) != test_lang(ch) or ch_prev in punc:
            yield ch_sep
        yield ch
        ch_prev = ch

def sep_chars_by_lang(s_input):
    return ''.join(_sep_chars_by_lang(s_input))
于 2013-01-25T23:53:54.867 に答える