簡體   English   中英

正則表達式獲取具有特定字母的所有單詞列表(unicode字形)

[英]Regex to get list of all words with specific letters (unicode graphemes)

我正在為FOSS語言學習計划編寫Python腳本。 假設我有一個XML文件(或保持簡單,一個Python列表),其中包含特定語言的單詞列表(在我的例子中,單詞是泰米爾語,它使用基於Brahmi的印度語腳本)。

我需要繪制那些可以使用這些字母拼寫的單詞的子集。

一個英文例子:

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代碼點和字形之間的區別,但我不確定它們是如何在正則表達式中處理的。

在這種情況下,我想只匹配由輸入列表中的特定字素組成的那些單詞,而不是其他任何內容(即字母后面的標記只應該跟隨該字母,但字母本身可以出現在任何字母中。訂購)。

要支持可以跨越多個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'ட'])))
# -> படம்

它假設一個單詞中可以使用相同的字符零次或多次。

如果您只想要包含確切給定字符的單詞:

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 ,因為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

編輯:好的,不要使用這里的任何答案。 我寫這些都是在思考Python正則表達式沒有單詞邊界標記時,我試圖解決這個缺點。 然后@Mark Tolonen添加了一條評論,說Python有\\b作為單詞邊界標記! 所以我發布了另一個簡短的答案,使用\\b 我會留在這里以防萬一有人有興趣看到解決方案解決缺乏\\b ,但我真的不希望任何人。


可以很容易地創建一個只匹配特定字符集的字符串的正則表達式。 你需要使用的是一個“字符類”,只包含你想要匹配的字符。

我會用英語做這個例子。

[ocat]這是一個與集合[o, c, a, t]中的單個字符匹配的字符類。 人物的順序無關緊要。

[ocat]+在末尾添加+使其與集合中的一個或多個字符匹配。 但這本身還不夠; 如果你有“教練”這個詞,這將匹配並返回“coac”。

遺憾的是,“單詞邊界”沒有正則表達式功能。 [編輯:事實證明這不是正確的,正如我在第一段中所說的那樣。]我們需要制作自己的一個。 有兩個可能的單詞開頭:一行的開頭,或者將單詞與前一單詞分開的空格。 類似地,有兩個可能的單詞結尾:一行的結尾,或者將我們的單詞與下一個單詞分開的空格。

由於我們將匹配一些我們不想要的額外內容,我們可以在我們想要的模式部分放置括號。

為了匹配兩個備選方案,我們可以在括號中創建一個組,並使用豎線分隔備選方案。 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']正如我預期的那樣......但這是因為我的模式在occo之后匹配了空格。 匹配組沒有收集空格,但是匹配完全相同,並且因為re.findall()希望匹配之間沒有重疊,所以空格“用完”並且不適用於cttc

解決方案是使用我以前從未使用過的Python正則表達式的特性:特殊語法,表示“不能以”開頭“或”不得跟隨“。 序列\\S匹配任何非空格,所以我們可以使用它。 但標點符號是非空白的,我認為我們確實希望標點符號來划分單詞。 還有“必須先於”或“必須后跟”的特殊語法。 所以我認為這是我們能做的最好的事情:

構建一個字符串,表示“當字符類字符串位於行的開頭並且后跟空格時,或者當字符類字符串前面有空格並后跟空格時,或者當字符類字符串前面有空格,后跟結束時,匹配line,或者當字符類字符串前面有行的開頭,后跟行尾“。

這是使用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() ,並將結果連接在一起。

編輯:好的,這是構建四個模式並嘗試每個模式的代碼。 我認為這是一個進步。

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']

編輯:好的,這是一個非常不同的方法。 我最喜歡這個。

首先,我們將匹配文本中的所有單詞。 單詞被定義為一個或多個不是標點符號且不是空格的字符。

然后,我們使用過濾器從上面刪除單詞; 我們只保留僅由我們想要的字符組成的單詞。

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中的最佳解決方案。

可以很容易地創建一個只匹配特定字符集的字符串的正則表達式。 你需要使用的是一個“字符類”,只包含你想要匹配的字符。

我會用英語做這個例子。

[ocat]這是一個與集合[o, c, a, t]中的單個字符匹配的字符類。 人物的順序無關緊要。

[ocat]+在末尾添加+使其與集合中的一個或多個字符匹配。 但這本身還不夠; 如果你有"coach"這個詞,這將匹配並返回"coac"

\\b[ocat]+\\b' Now it only matches on word boundaries. (Thank you very much @Mark Tolonen for educating me about \\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"]

我不會使用正則表達式來解決這個問題。 我寧願使用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)查找兩個計數器之間的交集,或min(c [x],f [x])。 如果此交集等同於您的單詞,則您希望將該單詞作為匹配返回。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM