Pythonでは、文字列に特定の文字のみが含まれているかどうかを確認するにはどうすればよいですか?
A..z、0..9、および。のみを含む文字列を確認する必要があります。 (ピリオド)およびその他の文字はありません。
各文字を反復処理して、文字がa..zまたは0..9、またはであることを確認できます。しかし、それは遅いでしょう。
正規表現を使用してそれを行う方法は今はわかりません。
これは正しいです?より単純な正規表現またはより効率的なアプローチを提案できますか。
#Valid chars . a-z 0-9
def check(test_str):
import re
#http://docs.python.org/library/re.html
#re.search returns None if no position in the string matches the pattern
#pattern to search for any character other then . a-z 0-9
pattern = r'[^\.a-z0-9]'
if re.search(pattern, test_str):
#Character other then . a-z 0-9 was found
print 'Invalid : %r' % (test_str,)
else:
#No character other then . a-z 0-9 was found
print 'Valid : %r' % (test_str,)
check(test_str='abcde.1')
check(test_str='abcde.1#')
check(test_str='ABCDE.12')
check(test_str='_-/>"!@#12345abcde<')
'''
Output:
>>>
Valid : "abcde.1"
Invalid : "abcde.1#"
Invalid : "ABCDE.12"
Invalid : "_-/>"!@#12345abcde<"
'''
最終(?)編集
注釈付きの対話型セッションを使用して、関数にまとめられた回答:
>>> import re
>>> def special_match(strg, search=re.compile(r'[^a-z0-9.]').search):
... return not bool(search(strg))
...
>>> special_match("")
True
>>> special_match("az09.")
True
>>> special_match("az09.\n")
False
# The above test case is to catch out any attempt to use re.match()
# with a `$` instead of `\Z` -- see point (6) below.
>>> special_match("az09.#")
False
>>> special_match("az09.X")
False
>>>
注:この回答のさらに下でre.match()を使用した場合との比較があります。さらにタイミングを合わせると、match()がはるかに長い文字列で勝つことがわかります。最終回答がTrueの場合、match()はsearch()よりもはるかに大きなオーバーヘッドがあるようです。これは不可解です(おそらく、NoneではなくMatchObjectを返すためのコストでしょう)。
==== Earlier text ====
[以前]受け入れられていた答えには、いくつかの改善点があります。
(1)プレゼンテーションは、インタラクティブなPythonセッションの結果であるかのように見えます:
reg=re.compile('^[a-z0-9\.]+$')
>>>reg.match('jsdlfjdsf12324..3432jsdflsdf')
True
しかし、match()はTrue
を返しません
(2)match()で使用する場合、パターンの先頭の^
は冗長であり、^
なしの同じパターンよりも若干遅いように見えます
(3)再パターンに対して生の文字列の使用を思いがけずに自動的に促進する必要があります
(4)ドット/ピリオドの前のバックスラッシュは冗長です
(5)OPのコードより遅い!
Prompt>rem OP's version -- NOTE: OP used raw string!
Prompt>\python26\python -mtimeit -s"t='jsdlfjdsf12324..3432jsdflsdf';import
re;reg=re.compile(r'[^a-z0-9\.]')" "not bool(reg.search(t))"
1000000 loops, best of 3: 1.43 usec per loop
Prompt>rem OP's version w/o backslash
Prompt>\python26\python -mtimeit -s"t='jsdlfjdsf12324..3432jsdflsdf';import
re;reg=re.compile(r'[^a-z0-9.]')" "not bool(reg.search(t))"
1000000 loops, best of 3: 1.44 usec per loop
Prompt>rem cleaned-up version of accepted answer
Prompt>\python26\python -mtimeit -s"t='jsdlfjdsf12324..3432jsdflsdf';import
re;reg=re.compile(r'[a-z0-9.]+\Z')" "bool(reg.match(t))"
100000 loops, best of 3: 2.07 usec per loop
Prompt>rem accepted answer
Prompt>\python26\python -mtimeit -s"t='jsdlfjdsf12324..3432jsdflsdf';import
re;reg=re.compile('^[a-z0-9\.]+$')" "bool(reg.match(t))"
100000 loops, best of 3: 2.08 usec per loop
(6)間違った答えを出すことができます!!
>>> import re
>>> bool(re.compile('^[a-z0-9\.]+$').match('1234\n'))
True # uh-oh
>>> bool(re.compile('^[a-z0-9\.]+\Z').match('1234\n'))
False
以下は、単純な、純粋なPythonの実装です。パフォーマンスが重要でない場合に使用する必要があります(将来のGoogle社員に含まれます)。
import string
allowed = set(string.ascii_lowercase + string.digits + '.')
def check(test_str):
set(test_str) <= allowed
パフォーマンスに関しては、おそらく反復が最速の方法でしょう。正規表現はステートマシンを反復処理する必要があり、セット等価ソリューションは一時セットを作成する必要があります。ただし、違いはそれほど重要ではありません。この関数のパフォーマンスが非常に重要な場合は、switchステートメント(ジャンプテーブルにコンパイルされる)を含むC拡張モジュールとして記述します。
これは、スペースの制約のためにifステートメントを使用するCの実装です。ほんの少し余分な速度が絶対に必要な場合は、スイッチケースを書きます。私のテストでは、非常によく機能します(正規表現に対するベンチマークでは2秒対9秒)。
#define PY_SSIZE_T_CLEAN
#include <Python.h>
static PyObject *check(PyObject *self, PyObject *args)
{
const char *s;
Py_ssize_t count, ii;
char c;
if (0 == PyArg_ParseTuple (args, "s#", &s, &count)) {
return NULL;
}
for (ii = 0; ii < count; ii++) {
c = s[ii];
if ((c < '0' && c != '.') || c > 'z') {
Py_RETURN_FALSE;
}
if (c > '9' && c < 'a') {
Py_RETURN_FALSE;
}
}
Py_RETURN_TRUE;
}
PyDoc_STRVAR (DOC, "Fast stringcheck");
static PyMethodDef PROCEDURES[] = {
{"check", (PyCFunction) (check), METH_VARARGS, NULL},
{NULL, NULL}
};
PyMODINIT_FUNC
initstringcheck (void) {
Py_InitModule3 ("stringcheck", PROCEDURES, DOC);
}
Setup.pyに含めます:
from distutils.core import setup, Extension
ext_modules = [
Extension ('stringcheck', ['stringcheck.c']),
],
使用:
>>> from stringcheck import check
>>> check("abc")
True
>>> check("ABC")
False
より簡単なアプローチ?もう少しPythonic?
>>> ok = "0123456789abcdef"
>>> all(c in ok for c in "123456abc")
True
>>> all(c in ok for c in "hello world")
False
それは確かに最も効率的ではありませんが、確かに読みやすいです。
EDIT:A-Zを除外するように正規表現を変更しました
正規表現ソリューションは、これまでで最も高速な純粋なpythonソリューションです
reg=re.compile('^[a-z0-9\.]+$')
>>>reg.match('jsdlfjdsf12324..3432jsdflsdf')
True
>>> timeit.Timer("reg.match('jsdlfjdsf12324..3432jsdflsdf')", "import re; reg=re.compile('^[a-z0-9\.]+$')").timeit()
0.70509696006774902
他のソリューションと比較して:
>>> timeit.Timer("set('jsdlfjdsf12324..3432jsdflsdf') <= allowed", "import string; allowed = set(string.ascii_lowercase + string.digits + '.')").timeit()
3.2119350433349609
>>> timeit.Timer("all(c in allowed for c in 'jsdlfjdsf12324..3432jsdflsdf')", "import string; allowed = set(string.ascii_lowercase + string.digits + '.')").timeit()
6.7066690921783447
空の文字列を許可する場合は、次のように変更します。
reg=re.compile('^[a-z0-9\.]*$')
>>>reg.match('')
False
リクエストに応じて、答えの残りの部分を返します。ただし、以下がA-Zの範囲を受け入れることに注意してください。
isalnum を使用できます
test_str.replace('.', '').isalnum()
>>> 'test123.3'.replace('.', '').isalnum()
True
>>> 'test123-3'.replace('.', '').isalnum()
False
[〜#〜] edit [〜#〜]isalnumの使用は、設定されたソリューションよりもはるかに効率的です。
>>> timeit.Timer("'jsdlfjdsf12324..3432jsdflsdf'.replace('.', '').isalnum()").timeit()
0.63245487213134766
EDIT2ジョンは上記が機能しない例を挙げました。エンコードを使用してこの特殊なケースを克服するためにソリューションを変更しました
test_str.replace('.', '').encode('ascii', 'replace').isalnum()
そして、それはまだ設定されたソリューションのほぼ3倍高速です
timeit.Timer("u'ABC\u0131\u0661'.encode('ascii', 'replace').replace('.','').isalnum()", "import string; allowed = set(string.ascii_lowercase + string.digits + '.')").timeit()
1.5719811916351318
私の意見では、正規表現を使用することがこの問題を解決するのに最適です
これはすでに十分に回答されていますが、実際にこの問題に遭遇した人のために、これを達成するためのいくつかの異なる方法のプロファイリングを行いました。私の場合、大文字の16進数が必要なので、必要に応じて変更してください。
私のテスト実装は次のとおりです。
import re
hex_digits = set("ABCDEF1234567890")
hex_match = re.compile(r'^[A-F0-9]+\Z')
hex_search = re.compile(r'[^A-F0-9]')
def test_set(input):
return set(input) <= hex_digits
def test_not_any(input):
return not any(c not in hex_digits for c in input)
def test_re_match1(input):
return bool(re.compile(r'^[A-F0-9]+\Z').match(input))
def test_re_match2(input):
return bool(hex_match.match(input))
def test_re_match3(input):
return bool(re.match(r'^[A-F0-9]+\Z', input))
def test_re_search1(input):
return not bool(re.compile(r'[^A-F0-9]').search(input))
def test_re_search2(input):
return not bool(hex_search.search(input))
def test_re_search3(input):
return not bool(re.match(r'[^A-F0-9]', input))
そして、Python 3.4.0でのMac OS Xのテスト:
import cProfile
import pstats
import random
# generate a list of 10000 random hex strings between 10 and 10009 characters long
# this takes a little time; be patient
tests = [ ''.join(random.choice("ABCDEF1234567890") for _ in range(l)) for l in range(10, 10010) ]
# set up profiling, then start collecting stats
test_pr = cProfile.Profile(timeunit=0.000001)
test_pr.enable()
# run the test functions against each item in tests.
# this takes a little time; be patient
for t in tests:
for tf in [test_set, test_not_any,
test_re_match1, test_re_match2, test_re_match3,
test_re_search1, test_re_search2, test_re_search3]:
_ = tf(t)
# stop collecting stats
test_pr.disable()
# we create our own pstats.Stats object to filter
# out some stuff we don't care about seeing
test_stats = pstats.Stats(test_pr)
# normally, stats are printed with the format %8.3f,
# but I want more significant digits
# so this monkey patch handles that
def _f8(x):
return "%11.6f" % x
def _print_title(self):
print(' ncalls tottime percall cumtime percall', end=' ', file=self.stream)
print('filename:lineno(function)', file=self.stream)
pstats.f8 = _f8
pstats.Stats.print_title = _print_title
# sort by cumulative time (then secondary sort by name), ascending
# then print only our test implementation function calls:
test_stats.sort_stats('cumtime', 'name').reverse_order().print_stats("test_*")
次の結果が得られました。
50335004の関数呼び出しは13.428秒以内です。 ____。] ncalls tottime percall cumtime percall filename:lineno(function) 10000 0.005233 0.000001 0.367360 0.000037:1(test_re_match2) 10000 0.006248 0.000001 0.378853 0.000038:1(test_re_match3) 10000 0.010710 0.000001 0.395770 0.000040:1(test_re_match1) 10000 0.004578 0.000000 0.467386 0.000047:1(test_re_search2) 10000 0.005994 0.000001 0.475329 0.000048:1(test_re_search3) 10000 0.008100 0.000001 0.482209 0.000048: 1(test_re_search1) 10000 0.863139 0.000086 0.863139 0.000086:1(test_set) 10000 0.007414 0.000001 9.962580 0.000996:1(test_not_any)
ここで:
実際に関心のある列はcumtimeとpercallで、関数の入力から終了までにかかった実際の時間を示しています。ご覧のとおり、正規表現の一致と検索に大きな違いはありません。
毎回コンパイルする場合は、正規表現をコンパイルする必要はありません。 1回コンパイルするのは毎回約7.5%高速ですが、コンパイルしないよりもコンパイルするのは2.5%だけ高速です。
test_setはre_searchの2倍遅く、re_matchの3倍遅い
test_not_anyはtest_setよりも一桁遅い
TL; DR:re.matchまたはre.searchを使用します
python hm ...データのセットを比較する必要がある場合に設定します。文字列は文字のセットとして非常に高速に表現できます。 、2番目ではありません。
In [17]: timeit.Timer("allowed = set('0123456789+-() ');p = set('+7(898) 64-901-63 ');p.issubset(allowed)").timeit()
Out[17]: 0.8106249139964348
In [18]: timeit.Timer("allowed = set('0123456789+-() ');p = set('+7(950) 64-901-63 фыв');p.issubset(allowed)").timeit()
Out[18]: 0.9240323599951807
避けられる場合は、正規表現を使用しないでください。