57

mystring文字列で始まるかどうかを確認する方法は次のとおりです。

>>> mystring.lower().startswith("he")
True

問題は、それmystringが非常に長い(数千文字)ため、lower()操作に時間がかかることです。

質問:もっと効率的な方法はありますか?

私の失敗した試み:

>>> import re;
>>> mystring.startswith("he", re.I)
False
4

7 に答える 7

63

次のように正規表現を使用できます。

In [33]: bool(re.match('he', 'Hello', re.I))
Out[33]: True 

In [34]: bool(re.match('el', 'Hello', re.I))
Out[34]: False 

2000文字の文字列では、これは次の約20倍高速ですlower()

In [38]: s = 'A' * 2000

In [39]: %timeit s.lower().startswith('he')
10000 loops, best of 3: 41.3 us per loop

In [40]: %timeit bool(re.match('el', s, re.I))
100000 loops, best of 3: 2.06 us per loop

同じプレフィックスを繰り返し一致させる場合、正規表現を事前にコンパイルすると大きな違いが生じる可能性があります。

In [41]: p = re.compile('he', re.I)

In [42]: %timeit p.match(s)
1000000 loops, best of 3: 351 ns per loop

短いプレフィックスの場合、小文字に変換する前に文字列からプレフィックスをスライスすると、さらに高速になる可能性があります。

In [43]: %timeit s[:2].lower() == 'he'
1000000 loops, best of 3: 287 ns per loop

もちろん、これらのアプローチの相対的なタイミングは、プレフィックスの長さに依存します。私のマシンでは、損益分岐点は約6文字のようです。これは、プリコンパイルされた正規表現が最速の方法になるときです。

私の実験では、すべての文字を個別にチェックする方がさらに高速になる可能性があります。

In [44]: %timeit (s[0] == 'h' or s[0] == 'H') and (s[1] == 'e' or s[1] == 'E')
1000000 loops, best of 3: 189 ns per loop

ただし、このメソッドは、コードを記述しているときに認識されているプレフィックスに対してのみ機能し、より長いプレフィックスには適していません。

于 2012-11-27T06:59:08.127 に答える
32

これはどう:

prefix = 'he'
if myVeryLongStr[:len(prefix)].lower() == prefix.lower()
于 2012-11-27T07:02:10.013 に答える
2

.lower()のパフォーマンスによっては、プレフィックスが十分に小さければ、同等性を複数回チェックする方が速い場合があります。

s =  'A' * 2000
prefix = 'he'
ch0 = s[0] 
ch1 = s[1]
substr = ch0 == 'h' or ch0 == 'H' and ch1 == 'e' or ch1 == 'E'

タイミング(NPEと同じ文字列を使用):

>>> timeit.timeit("ch0 = s[0]; ch1 = s[1]; ch0 == 'h' or ch0 == 'H' and ch1 == 'e' or ch1 == 'E'", "s = 'A' * 2000")
0.2509511683747405

= 0.25 us per loop

既存の方法との比較:

>>> timeit.timeit("s.lower().startswith('he')", "s = 'A' * 2000", number=10000)
0.6162763703208611

= 61.63 us per loop

(もちろん、これは恐ろしいことですが、コードのパフォーマンスが非常に重要である場合は、それだけの価値があるかもしれません)

于 2012-11-27T07:14:13.313 に答える
0

Python 3.8 では、最速のソリューションには、この回答で提案されているように、プレフィックスをスライスして比較することが含まれます。

def startswith(a_source: str, a_prefix: str) -> bool:
    source_prefix = a_source[:len(a_prefix)]
    return source_prefix.casefold() == a_prefix.casefold()

2 番目に高速なソリューションは、ctypes (例: _wcsicmp ) を使用します。注: これは Windows の例です。

import ctypes.util

libc_name = ctypes.util.find_library('msvcrt')
libc = ctypes.CDLL(libc_name)

libc._wcsicmp.argtypes = (ctypes.c_wchar_p, ctypes.c_wchar_p)

def startswith(a_source: str, a_prefix: str) -> bool:
    source_prefix = a_source[:len(a_prefix)]
    return libc._wcsicmp(source_prefix, a_prefix) == 0

コンパイルされたreソリューションは、コンパイルのコストを含めて 3 番目に高速なソリューションです。この回答regexで提案されているように、モジュールが完全な Unicode サポートに使用されている場合、そのソリューションはさらに遅くなります。連続する各一致のコストは、各 ctypes 呼び出しとほぼ同じです。

lower()これらの関数は、大文字とcasefold()小文字を区別せずにソース文字列の各文字を反復処理し、それに応じてマッピングすることで新しい Unicode 文字列を作成するため、コストがかかります。(参照:組み込み関数はどのようにstr.lower()実装されていますか? ) そのループで費やされる時間は文字ごとに増加するため、短いプレフィックスと長い文字列を扱っている場合は、プレフィックスに対してのみこれらの関数を呼び出します。

于 2020-07-25T01:27:53.670 に答える