編集:わかりました、ここからの回答は使用しないでください。Python の正規表現には単語境界マーカーがないと考えながら、それらをすべて作成し、この不足を回避しようとしました。次に、@Mark Tolonen が、Python が\b
単語境界マーカーとして持つコメントを追加しました! そこで、を使用して、短くて簡単な別の回答を投稿し\b
ました。の欠如を回避する解決策に興味がある人がいる場合に備えて、これをここに残しておきますが\b
、実際にそうなるとは思っていません。
特定の文字セットの文字列のみに一致する正規表現を簡単に作成できます。使用する必要があるのは、一致させたい文字だけを含む「文字クラス」です。
この例を英語で行います。
[ocat]
これは、セットの 1 文字に一致する文字クラスです[o, c, a, t]
。文字の順番は問いません。
[ocat]+
末尾にa+
を付けると、セットの 1 つまたは複数の文字に一致します。しかし、これだけでは十分ではありません。「coach」という単語がある場合、これは一致して「coac」を返します。
残念ながら、「単語境界」の正規表現機能はありません。[編集: 最初の段落で述べたように、これは正しくないことが判明しました。] 自分で作成する必要があります。単語の始まりには 2 つの可能性があります。行頭、または単語と前の単語を区切る空白です。同様に、単語の終わりには 2 つの可能性があります。行末、または単語と次の単語を区切る空白です。
必要のない余分なものを照合するため、必要なパターンの部分を括弧で囲むことができます。
2 つの選択肢を一致させるには、括弧でグループを作成し、選択肢を縦棒で区切ります。Python の正規表現には、内容を保持したくないグループを作成するための特別な表記法があります。(?:)
というわけで、これが単語の先頭にマッチするパターンです。行頭または空白: (?:^|\s)
これが単語の終わりのパターンです。空白または行末: `(?:\s|$)
すべてをまとめると、これが最終的なパターンです。
(?:^|\s)([ocat]+)(?:\s|$)
これを動的に構築できます。全体をハードコードする必要はありません。
import re
s_pat_start = r'(?:^|\s)(['
s_pat_end = r']+)(?:\s|$)'
set_of_chars = get_the_chars_from_somewhere_I_do_not_care_where()
# set_of_chars is now set to the string: "ocat"
s_pat = s_pat_start + set_of_chars + s_pat_end
pat = re.compile(s_pat)
さて、これは決して有効な単語をチェックしません。次のテキストがある場合:
This is sensible. This not: occo cttc
お見せしたパターンは と で一致しますがocco
、cttc
それらは実際には単語ではありません。からの文字だけでできた文字列[ocat]
です。
したがって、Unicode 文字列でも同じことを行うだけです。(Python 3.x を使用している場合、すべての文字列は Unicode 文字列なので、ここまでです。) 文字クラスにタミル文字を入れれば、準備完了です。
これには紛らわしい問題re.findall()
があります。すべての可能な一致を返すわけではありません。
編集:わかりました、何が私を混乱させていたのかわかりました。
私たちが望むのは、re.findall()
すべての単語を収集できるようにパターンを操作することです。ただし、重複しないパターンre.findall()
のみを見つけます。私の例では、返されただけで、期待どおりではありませんでした...しかし、これは、パターンが の後の空白と一致していたためです。一致グループは空白を収集しませんでしたが、すべて同じように一致し、一致間で重複したくないため、空白は「使い果たされ」、.re.findall()
['occo']
['occo', 'cttc']
occo
re.findall()
cttc
解決策は、これまで使用したことのない Python 正規表現の機能を使用することです。「前に付けてはならない」または「後に付けてはならない」という特殊な構文です。シーケンス\S
は空白以外のすべてに一致するため、それを使用できます。しかし、句読点は空白ではありません。句読点で単語を区切る必要があると思います。「must be beforeed by」または「must be followed by」の特別な構文もあります。ですから、私たちができる最善のことは次のとおりです。
「文字クラス文字列が行頭にあり、その後に空白が続く場合、または文字クラス文字列の前に空白があり、その後に空白が続く場合、または文字クラス文字列の前に空白があり、その後に末尾が続く場合に一致する」ことを意味する文字列を作成します。行、または文字クラス文字列の前に行頭があり、その後に行末がある場合」.
を使用したパターンは次のocat
とおりです。
r'(?:^([ocat]+)(?=\s)|(?<=\s)([ocat]+)(?=\s)|(?<=\s)([ocat]+)$|^([ocat]+)$)'
大変申し訳ありませんが、これが私たちにできる最善のことだと本当に思っていますre.findall()
。
ただし、実際には Python コードではそれほど混乱しません。
import re
NMGROUP_BEGIN = r'(?:' # begin non-matching group
NMGROUP_END = r')' # end non-matching group
WS_BEFORE = r'(?<=\s)' # require white space before
WS_AFTER = r'(?=\s)' # require white space after
BOL = r'^' # beginning of line
EOL = r'$' # end of line
CCS_BEGIN = r'([' #begin a character class string
CCS_END = r']+)' # end a character class string
PAT_OR = r'|'
set_of_chars = get_the_chars_from_somewhere_I_do_not_care_where()
# set_of_chars now set to "ocat"
CCS = CCS_BEGIN + set_of_chars + CCS_END # build up character class string pattern
s_pat = (NMGROUP_BEGIN +
BOL + CCS + WS_AFTER + PAT_OR +
WS_BEFORE + CCS + WS_AFTER + PAT_OR +
WS_BEFORE + CCS + EOL + PAT_OR +
BOL + CCS + EOL +
NMGROUP_END)
pat = re.compile(s_pat)
text = "This is sensible. This not: occo cttc"
pat.findall(text)
# returns: [('', 'occo', '', ''), ('', '', 'cttc', '')]
したがって、クレイジーなことに、一致する可能性のある代替パターンがある場合、一致しなかった代替パターンに対してre.findall()
空の文字列が返されるようです。したがって、結果から長さゼロの文字列を除外する必要があります。
import itertools as it
raw_results = pat.findall(text)
results = [s for s in it.chain(*raw_results) if s]
# results set to: ['occo', 'cttc']
re.findall()
4 つの異なるパターンを作成し、それぞれを実行して、結果を結合する方が混乱が少ないと思います。
編集: 4 つのパターンを作成し、それぞれを試すためのコードは次のとおりです。これは改善だと思います。
import re
WS_BEFORE = r'(?<=\s)' # require white space before
WS_AFTER = r'(?=\s)' # require white space after
BOL = r'^' # beginning of line
EOL = r'$' # end of line
CCS_BEGIN = r'([' #begin a character class string
CCS_END = r']+)' # end a character class string
set_of_chars = get_the_chars_from_somewhere_I_do_not_care_where()
# set_of_chars now set to "ocat"
CCS = CCS_BEGIN + set_of_chars + CCS_END # build up character class string pattern
lst_s_pat = [
BOL + CCS + WS_AFTER,
WS_BEFORE + CCS + WS_AFTER,
WS_BEFORE + CCS + EOL,
BOL + CCS
]
lst_pat = [re.compile(s) for s in lst_s_pat]
text = "This is sensible. This not: occo cttc"
result = []
for pat in lst_pat:
result.extend(pat.findall(text))
# result set to: ['occo', 'cttc']
編集:さて、これは非常に異なるアプローチです。私はこれが一番好きです。
まず、テキスト内のすべての単語を照合します。単語は、句読点でも空白でもない 1 つ以上の文字として定義されます。
次に、フィルタを使用して上記から単語を削除します。必要な文字のみで構成された単語のみを保持します。
import re
import string
# Create a pattern that matches all characters not part of a word.
#
# Note that '-' has a special meaning inside a character class, but it
# is valid punctuation that we want to match, so put in a backslash in
# front of it to disable the special meaning and just match it.
#
# Use '^' which negates all the chars following. So, a word is a series
# of characters that are all not whitespace and not punctuation.
WORD_BOUNDARY = string.whitespace + string.punctuation.replace('-', r'\-')
WORD = r'[^' + WORD_BOUNDARY + r']+'
# Create a pattern that matches only the words we want.
set_of_chars = get_the_chars_from_somewhere_I_do_not_care_where()
# set_of_chars now set to "ocat"
# build up character class string pattern
CCS = r'[' + set_of_chars + r']+'
pat_word = re.compile(WORD)
pat = re.compile(CCS)
text = "This is sensible. This not: occo cttc"
# This makes it clear how we are doing this.
all_words = pat_word.findall(text)
result = [s for s in all_words if pat.match(s)]
# "lazy" generator expression that yields up good results when iterated
# May be better for very large texts.
result_genexp = (s for s in (m.group(0) for m in pat_word.finditer(text)) if pat.match(s))
# force the expression to expand out to a list
result = list(result_genexp)
# result set to: ['occo', 'cttc']
編集:今、私は上記の解決策のどれも好きではありません。\b
Python での最適なソリューションについては、を使用した他の回答を参照してください。