5

リストがあるとしましょう:

list=['plu;ean;price;quantity','plu1;ean1;price1;quantity1']

リストを繰り返し処理し、リストを「;」で分割したい 次のように if 句を挿入します。

for item in list:
    split_item=item.split(";")
    if split_item[0] == "string_value" or split_item[1] == "string_value":
        do something.....

これが可能な限り最速の方法であるかどうか疑問に思っていましたか?私の最初のリストがはるかに大きいとしましょう (より多くのリスト項目があります)。リスト内包表記で試しました:

item=[item.split(";") for item in list if item.split(";")[0] == "string_value" or item.split(";")[1] == "string_value"]

しかし、これは実際にはより遅い結果をもたらしています。最初のケースでは平均 90 ミリ秒、2 番目のケースでは平均 130 ミリ秒です。リストの理解が間違っていますか? より速い解決策はありますか?

4

4 に答える 4

3

これが可能な限り最速の方法であるかどうか疑問に思っていましたか?

いいえ、もちろん違います。Python よりも手作業でコーディングしたアセンブリの方がはるかに高速に実装できます。だから何?

「何かをする...」が自明ではなく、多くの一致がある場合、何かを100000回実行するコストは、500000回ループするコストよりもはるかに高くつくため、ループする最速の方法を見つけることはできません全然構いません。

実際、split結果を記憶して再利用するのではなく、ループごとに 2 ~ 3 回呼び出すだけで、反復のコストが大幅に削減されます。


したがって、間違ったことを最適化しようとしています。しかし、他のすべてを修正した後で、反復のコストが本当に重要であることが判明した場合はどうなるでしょうか?

内包表記は、何かを実行するためのステートメントではなく、値を返す式のためのものだからです。

しかし、コードを見ると、実際には 3 つのことを行っていることがわかります。各文字列を分割し、一致しないものを除外し、「何かを実行」します。したがって、最初の 2 つの部分に内包表記を使用できます。その後はfor、フィルターを通過したはるかに小さな値のリストに対してのみ低速ループを使用しています。

これを試したようですが、2 つの間違いを犯しました。

まず、リスト内包表記よりもジェネレーター式の方が適しています。ここではリストは必要ありません。イテレーターで何かを処理するだけなので、リストを構築するために料金を支払う必要はありません。

split第二に、文字列を 3 回したくありません。splitおそらく、1 回の理解で 1 回で完了するための複雑な方法を見つけることができますが、わざわざする必要はありません。各ステップを独自のステップとして記述するだけです。

そう:

split_items = (item.split(';') for item in items)
filtered_items = (item for item in split_items 
                  if item[0] == "string_value" or item[1] == "string_value")
for item in filtered_items:
    do something...

これは実際に速くなりますか?反復がボトルネックであることを示す実際のテスト データと「何かを実行する」コードを取得できれば、その実際のデータとコードでテストできます。それまでは、テストするものは何もありません。

于 2013-09-25T18:57:31.250 に答える
1

編集: Regex キャッシュは競合他社に対して少し不公平だったことが判明しました。私の悪い。Regex はほんのわずかな割合で高速です。

速度を求めている場合は、hcwhsa の回答で十分です。もう少し必要な場合は、 を参照してreください。

import re
from itertools import chain

lis = ['plu;ean;price;quantity'*1000, 'plu1;ean1;price1;quantity1'*100]*1000

matcher = re.compile('^(?:plu(?:;|$)|[^;]*;ean(?:;|$))').match
[l.split(';') for l in lis if matcher(l)]

ほとんどが肯定的な結果のタイミング (別名split、速度低下の主な原因):

SETUP="
import re
from itertools import chain
matcher = re.compile('^(?:plu(?:;|$)|[^;]*;ean(?:;|$))').match

lis = ['plu1;ean1;price1;quantity1'+chr(i) for i in range(10000)] + ['plu;ean;price;quantity' for i in range(10000)]
"

python -m timeit -s "$SETUP" "[[x] + [y] + z.split(';') for x, y, z in (item.split(';', 2) for item in lis) if x== 'plu' or y=='ean']"
python -m timeit -s "$SETUP" "[l.split(';') for l in lis if matcher(l)]"

私のほうが少し速いことがわかります。

10 loops, best of 3: 55 msec per loop
10 loops, best of 3: 49.5 msec per loop

ほとんどが否定的な結果の場合 (ほとんどのものはフィルター処理されます):

SETUP="
import re
from itertools import chain
matcher = re.compile('^(?:plu(?:;|$)|[^;]*;ean(?:;|$))').match

lis = ['plu1;ean1;price1;quantity1'+chr(i) for i in range(1000)] + ['plu;ean;price;quantity' for i in range(10000)]
"

python -m timeit -s "$SETUP" "[[x] + [y] + z.split(';') for x, y, z in (item.split(';', 2) for item in lis) if x== 'plu' or y=='ean']"
python -m timeit -s "$SETUP" "[l.split(';') for l in lis if matcher(l)]"

リードはもう少し上です。

10 loops, best of 3: 40.9 msec per loop
10 loops, best of 3: 35.7 msec per loop

結果が常に一意になる場合は、使用します

next([x] + [y] + z.split(';') for x, y, z in (item.split(';', 2) for item in lis) if x== 'plu' or y=='ean')

またはより高速な正規表現バージョン

next(filter(matcher, lis)).split(';')

( itertools.ifilterPython 2 で使用)。

タイミング:

SETUP="
import re
from itertools import chain
matcher = re.compile('^(?:plu(?:;|$)|[^;]*;ean(?:;|$))').match

lis = ['plu1;ean1;price1;quantity1'+chr(i) for i in range(10000)] + ['plu;ean;price;quantity'] + ['plu1;ean1;price1;quantity1'+chr(i) for i in range(10000)]
"

python -m timeit -s "$SETUP" "[[x] + [y] + z.split(';') for x, y, z in (item.split(';', 2) for item in lis) if x== 'plu' or y=='ean']"
python -m timeit -s "$SETUP" "next([x] + [y] + z.split(';') for x, y, z in (item.split(';', 2) for item in lis) if x== 'plu' or y=='ean')"

python -m timeit -s "$SETUP" "[l.split(';') for l in lis if matcher(l)]"
python -m timeit -s "$SETUP" "next(filter(matcher, lis)).split(';')"

結果:

10 loops, best of 3: 31.3 msec per loop
100 loops, best of 3: 15.2 msec per loop
10 loops, best of 3: 28.8 msec per loop
100 loops, best of 3: 14.1 msec per loop

したがって、これにより、両方の方法が大幅に強化されます。

于 2013-09-25T20:24:51.930 に答える
1

ここで良い代替手段を見つけました。

マップとフィルターを組み合わせて使用​​できます。これを試して:

>>>import itertools
>>>splited_list = itertools.imap(lambda x: x.split(";"), your_list)
>>>result = filter(lambda x: filter(lambda x: x[0] == "plu" or x[1] == "string_value", lista)

最初の項目は、要素の反復子を作成します。そして2番目のものはそれをフィルタリングします。IPython Notebook シェルで小さなベンチマークを実行したところ、次の結果が得られました。

最初のテスト:

ここに画像の説明を入力

小さいサイズでは、1 行のソリューションの方がうまく機能します

2回目のテスト:

ここに画像の説明を入力

より大きなリストでは、マップ/フィルター ソリューションがわずかに優れています

3回目のテスト:

ここに画像の説明を入力

大きなリストと大きな要素を使用すると、マップ/フィルター ソリューションの方がはるかに優れています。

パフォーマンスの差は、リストのサイズが大きくなるにつれて増加し続け、66% の時間 (10000 要素リストの試行) でピークに達するまで続くと思います。

マップ/フィルター ソリューションとリスト内包表記ソリューションの違いは、.split() への呼び出しの数です。Ones はアイテムごとに 3 回呼び出しますが、もう 1 つは 1 回だけ呼び出します。私はリスト内包表記をよく使っていましたが、ラムダが何であるかを知らないと思っていました。マップとリストの内包表記が同じものであることを発見するまで。

メモリ使用量を気にしない場合は、imap の代わりに通常のマップを使用できます。一度に分割してリストを作成します。保存するためにより多くのメモリを使用しますが、わずかに高速です。

実際、メモリ使用量を気にしない場合は、2 つのリスト内包表記を使用してマップ/フィルター ソリューションを記述し、まったく同じ結果を得ることができます。チェックアウト:

ここに画像の説明を入力

于 2013-09-25T19:49:34.367 に答える