7

FOSS 言語学習イニシアチブ用の Python スクリプトを作成しています。特定の言語の単語のリストを含む XML ファイル (または単純にするために Python リスト) があるとします (私の場合、単語はタミル語であり、ブラフミー語に基づくインド文字を使用しています)。

それらの文字だけを使用してつづることができる単語のサブセットを引き出す必要があります。

英語の例:

words = ["cat", "dog", "tack", "coat"] 

get_words(['o', 'c', 'a', 't']) should return ["cat", "coat"]
get_words(['k', 'c', 't', 'a']) should return ["cat", "tack"]

タミル語の例:

words = [u"மரம்", u"மடம்", u"படம்", u"பாடம்"]

get_words([u'ம', u'ப', u'ட', u'ம்')  should return [u"மடம்", u"படம்")
get_words([u'ப', u'ம்', u'ட') should return [u"படம்"] 

単語が返される順序、または文字が入力される順序に違いはありません。

Unicode コードポイントと書記素の違いは理解できますが、それらが正規表現でどのように処理されるかはわかりません。

この場合、入力リスト内の特定の書記素で構成される単語のみに一致させたいと思います (つまり、文字に続くマーキングはその文字にのみ続く必要がありますが、書記素自体は任意の文字列に出現する可能性があります)。注文)。

4

4 に答える 4

5

複数の Unicode コードポイントにまたがる文字をサポートするには:

# -*- coding: utf-8 -*-
import re
import unicodedata
from functools import partial

NFKD = partial(unicodedata.normalize, 'NFKD')

def match(word, letters):
    word, letters = NFKD(word), map(NFKD, letters) # normalize
    return re.match(r"(?:%s)+$" % "|".join(map(re.escape, letters)), word)

words = [u"மரம்", u"மடம்", u"படம்", u"பாடம்"]
get_words = lambda letters: [w for w in words if match(w, letters)]

print(" ".join(get_words([u'ம', u'ப', u'ட', u'ம்'])))
# -> மடம் படம்
print(" ".join(get_words([u'ப', u'ம்', u'ட'])))
# -> படம்

単語内で同じ文字を 0 回以上使用できることを前提としています。

指定された文字を正確に含む単語のみが必要な場合:

import regex # $ pip install regex

chars = regex.compile(r"\X").findall # get all characters

def match(word, letters):
    return sorted(chars(word)) == sorted(letters)

words = ["cat", "dog", "tack", "coat"]

print(" ".join(get_words(['o', 'c', 'a', 't'])))
# -> coat
print(" ".join(get_words(['k', 'c', 't', 'a'])))
# -> tack

注:この場合、指定されたすべての文字を使用しないcatため、出力に no はありません。cat


正常化するとはどういう意味ですか? re.match() 正規表現の構文を説明していただけますか?

>>> import re
>>> re.escape('.')
'\\.'
>>> c = u'\u00c7'
>>> cc = u'\u0043\u0327'
>>> cc == c
False
>>> re.match(r'%s$' % (c,), cc) # do not match
>>> import unicodedata
>>> norm = lambda s: unicodedata.normalize('NFKD', s)
>>> re.match(r'%s$' % (norm(c),), norm(cc)) # do match
<_sre.SRE_Match object at 0x1364648>
>>> print c, cc
Ç Ç

正規化なしでは一致cccません。文字はunicodedata.normalize()docsからのものです。

于 2013-01-28T06:23:20.443 に答える
3

編集:わかりました、ここからの回答は使用しないでください。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

お見せしたパターンは と で一致しますがoccocttcそれらは実際には単語ではありません。からの文字だけでできた文字列[ocat]です。

したがって、Unicode 文字列でも同じことを行うだけです。(Python 3.x を使用している場合、すべての文字列は Unicode 文字列なので、ここまでです。) 文字クラスにタミル文字を入れれば、準備完了です。

これには紛らわしい問題re.findall()があります。すべての可能な一致を返すわけではありません。

編集:わかりました、何が私を混乱させていたのかわかりました。

私たちが望むのは、re.findall()すべての単語を収集できるようにパターンを操作することです。ただし、重複しないパターンre.findall()のみを見つけます。私の例では、返されただけで、期待どおりではありませんでした...しかし、これは、パターンが の後の空白と一致していたためです。一致グループは空白を収集しませんでしたが、すべて同じように一致し、一致間で重複したくないため、空白は「使い果たされ」、.re.findall()['occo']['occo', 'cttc']occore.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']

編集:今、私は上記の解決策のどれも好きではありません。\bPython での最適なソリューションについては、を使用した他の回答を参照してください。

于 2013-01-27T05:45:35.720 に答える
3

特定の文字セットの文字列のみに一致する正規表現を簡単に作成できます。使用する必要があるのは、一致させたい文字だけを含む「文字クラス」です。

この例を英語で行います。

[ocat]これは、セットの 1 文字に一致する文字クラスです[o, c, a, t]。文字の順番は問いません。

[ocat]+末尾に + を付けると、セットの 1 つ以上の文字に一致します。しかし、これだけでは十分ではありません。という単語がある場合、"coach"これは一致して を返し"coac"ます。

\b[ocat]+\b' Now it only matches on word boundaries. (Thank you very much @Mark Tolonen for educating me about\b`.)

したがって、実行時に目的の文字セットのみを使用して、上記のようなパターンを構築するだけで済みます。このパターンはre.findall()またはで使用できますre.finditer()

import re

words = ["cat", "dog", "tack", "coat"]

def get_words(chars_seq, words_seq=words):
    s_chars = ''.join(chars_seq)
    s_pat = r'\b[' + s_chars + r']+\b'
    pat = re.compile(s_pat)
    return [word for word in words_seq if pat.match(word)]

assert get_words(['o', 'c', 'a', 't']) == ["cat", "coat"]
assert get_words(['k', 'c', 't', 'a']) == ["cat", "tack"]
于 2013-01-27T22:41:22.657 に答える
2

この問題を解決するために正規表現を使用することはありません。私はむしろcollections.Counterを次のように使用したいと思います:

>>> from collections import Counter
>>> def get_words(word_list, letter_string):
    return [word for word in word_list if Counter(word) & Counter(letter_string) == Counter(word)]
>>> words = ["cat", "dog", "tack", "coat"]
>>> letters = 'ocat'
>>> get_words(words, letters)
['cat', 'coat']
>>> letters = 'kcta'
>>> get_words(words, letters)
['cat', 'tack']

このソリューションは、他の言語でも機能するはずです。Counter(word) & Counter(letter_string)2つのカウンター間の交点、またはmin(c [x]、f [x])を見つけます。この交差点があなたの単語と同等である場合、あなたはその単語を一致として返したいと思います。

于 2013-01-27T03:59:47.323 に答える