mystring
文字列で始まるかどうかを確認する方法は次のとおりです。
>>> mystring.lower().startswith("he")
True
問題は、それmystring
が非常に長い(数千文字)ため、lower()
操作に時間がかかることです。
質問:もっと効率的な方法はありますか?
私の失敗した試み:
>>> import re;
>>> mystring.startswith("he", re.I)
False
mystring
文字列で始まるかどうかを確認する方法は次のとおりです。
>>> mystring.lower().startswith("he")
True
問題は、それmystring
が非常に長い(数千文字)ため、lower()
操作に時間がかかることです。
質問:もっと効率的な方法はありますか?
私の失敗した試み:
>>> import re;
>>> mystring.startswith("he", re.I)
False
次のように正規表現を使用できます。
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
ただし、このメソッドは、コードを記述しているときに認識されているプレフィックスに対してのみ機能し、より長いプレフィックスには適していません。
これはどう:
prefix = 'he'
if myVeryLongStr[:len(prefix)].lower() == prefix.lower()
.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
(もちろん、これは恐ろしいことですが、コードのパフォーマンスが非常に重要である場合は、それだけの価値があるかもしれません)
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()
実装されていますか? ) そのループで費やされる時間は文字ごとに増加するため、短いプレフィックスと長い文字列を扱っている場合は、プレフィックスに対してのみこれらの関数を呼び出します。