首页 > 解决方案 > 使用正则表达式将多个单词与文本匹配

问题描述

我正在尝试将列表中的 n-grams/(多个单词)匹配到文本/字符串。

我的示例匹配列表包含以下单词:-

matching_list = ['Data Scientist',
 'Associate Research Scientist',
 'Post Doctoral Research Fellow',
 'Research Scientist',
 'Assistant Professor', 
 'c# developer', 
 '.net engineer']

解析后的示例文本包含以下单词:-

text = '我是一名企业客户经理,具有数据科学家、副研究员、博士后研究员、研究员、助理教授、.Net 工程师、c# 开发人员的经验'

我遵循将匹配列表和文本转换为小写字母的过程,然后使用以下代码进行搜索。

import re

# Uncomment when Matching 4-gram words
#findnames = re.compile(r'([A-Z]\w*(?:\s[A-Z]\w*(?:\s[A-Z]\w*(?:\s[A-Z]\w*)?)?)?)')

# Uncomment when Matching tri-gram words
#findnames = re.compile(r'([A-Z]\w*(?:\s[A-Z]\w*(?:\s[A-Z]\w*)?)?)')

# Uncomment when Matching bi-gram words
findnames = re.compile(r'([A-Z]\w*(?:\s[A-Z]\w*)?)')
def is_name_in_text(text, matching_list):
        for possible_name in set(findnames.findall(text)):
            if possible_name in matching_list:
                print(possible_name)
        return possible_name
is_name_in_text(text, matching_list)

我期待双克匹配得到

    Research Scientist
    Data Scientist
    Assistant Professor
    c# developer
    .net engineer

但是,我得到以下输出

     Data Scientist
     Assistant Professor

1)我无法匹配特殊字符。

2)此外,匹配是为二元词跳2个词,为三元词跳3个词,依此类推。它不是在整个句子中逐字移动匹配的短语,而是,我觉得二元词一次跳 2 个词,如果找不到匹配项,三元词一次跳 3 个词。如果二元语法从奇数位置开始,而克从偶数位置开始等等,这会导致问题。

我的列表由 7 个特殊字符组成,例如 #、@、+、.、_、- 和 *

我需要修复语料库中的特殊字符和逐字模式匹配。我无法想出像 re.compile(r'([AZ]\w*(?:\s[AZ]\w*)?)') 这样的合适的 re 表达式。

我也不确定 tri-gram 和 4-gram 的 re 表达式。

标签: pythonpython-3.x

解决方案


您正在寻找匹配单词级别的 n-gram,特别是单词级别的二元组。

但是,您提供的正则表达式:匹配任何前面有to([A-Z]\w*(?:\s[A-Z]\w*)?)范围内的字符的单词字符串,可选地后跟一个空格和另一个这样的字符串。AZ

使用该正则表达式永远不会 match c# developer,因为它不以AtoZ和 contains开头#。它也不会匹配.net engineer,因为它以 . 开头.。此外,您正在匹配.net engineer,但它在文本中为.Net engineer.

此外,通过使用该正则表达式 and findall,正则表达式将成对使用大写单词的字符串,从而防止重用。因此,在匹配之后Corporate Account,它永远不会匹配Account Manager,因为该Account部分已经被消耗掉了。您正在使用非捕获组,但这仍然会导致正则表达式消耗字符串的那部分。

假设您确实想要匹配不区分大小写的单词级别 n-gram,并且您需要匹配特殊字符,例如#,我认为您无法使用单个正则表达式实现您想要的,但是一些相当基本的 Python 代码可以帮助您实现目标。

考虑到过滤掉任何一部分不完全由单词字符或您喜欢的特殊字符组成的 n-gram 可能效率不高。为什么不简单地将字符串拆分为间距并找到您正在寻找的 n-gram 呢?

import re

text = 'I am a Corporate Account Manager with experience as Data Scientist' \
       ' Associate Research Scientist Post Doctoral Research Fellow Research' \
       ' Scientist Assistant Professor .Net engineer c# developer'

matching_list = [
    'Data Scientist',
    'Associate Research Scientist',
    'Post Doctoral Research Fellow',
    'Research Scientist',
    'Assistant Professor',
    'c# developer',
    '.net engineer'
]


def get_ngrams(words, n):
    return zip(*[words[m:len(words)-(1-m)] for m in range(n)])


def main():
    # simply split up the text, you could also just go words = text.split()
    regex = re.compile(r'[^\s]+')
    words = regex.findall(text.lower())
    # turn the list of words into ngrams of the needed length
    ngrams = list(get_ngrams(words, 2))
    # also create ngrams for the phrases in matching_list 
    # then link them to the phrases in a dict for easy reference
    matching_ngrams = {
        k: v for k, v in zip(
            [tuple(x.lower().split()) for x in matching_list], matching_list 
        )
    }

    # find all the matching ones and print the matching phrase when found
    for find_this in ngrams:
        if find_this in matching_ngrams:
            print(matching_ngrams[find_this])


main()

请注意,这仍然会产生重复,您表示您只期望每个结果一次。您可以通过翻转循环和比较来实现:

    for find_this in matching_ngrams:
        if find_this in ngrams:
            print(matching_ngrams[find_this])

这将更频繁地通过更长的列表,花费更多时间,但如果每个短语在文本中,它只会打印一次。或者,您可以创建一个函数,返回所有匹配项并将它们放入set.

为了避免列表、查找的低效率和不必要的re,我更喜欢这个:

def get_ngrams(words, n):
    return zip(*[words[m:len(words) - (1 - m)] for m in range(n)])


def find_matching_ngrams(text, phrases, n):
    ngrams_phrases = {
        k: v for k, v in zip(
            [tuple(x.lower().split()) for x in phrases], phrases
        )
    }

    for ngram in get_ngrams(text.lower().split(), n):
        if ngram in ngrams_phrases :
            yield ngrams_phrases[ngram]


def main():
    text = 'I am a Corporate Account Manager with experience as Data Scientist' \
           ' Associate Research Scientist Post Doctoral Research Fellow Research' \
           ' Scientist Assistant Professor .Net engineer c# developer'

    matching_list = [
        'Data Scientist',
        'Associate Research Scientist',
        'Post Doctoral Research Fellow',
        'Research Scientist',
        'Assistant Professor',
        'c# developer',
        '.net engineer'
    ]

    print(set(find_matching_ngrams(text, matching_list, 2)))


main()

并且可能更有效:

def get_ngrams(words, n):
    for m in range(len(words)-(n-1)):
        yield tuple(words[m:m+n])

推荐阅读