计算文本文件中的名称出现次数,对重复项敏感

Counting name occurence in textfile, being sensitive to duplicates

我有一个姓名列表,想计算整个文本文件语料库中出现的次数。

我正在使用带有字典的简单正则表达式搜索来执行此操作:

    for k,v in eng_names_dict.items():
        for i in v:
            pattern = re.compile(str(i).strip(' '))
            matches = re.search(pattern, text)
            if matches:
                namesDict[k] += 1
                break
    return

收获:

我混合使用了标题和名称(具有不同的名称格式,如下例所示),它们之间有一些重复。

例如: 我的名单包括两个不同的人 - "Dr. Mark"(头衔 + 姓氏)和 "Mark Smith"(名字 + 姓氏)。

如果文本文件包含字符串 "Dr. Mark Smith said that...",我的函数会标记两个人的计数(而不是仅针对 "Mark Smith")。

有什么方法可以保证每个子串只计数一次吗?

啊,感谢您提供数据结构。我认为您需要的是正则表达式中的 "or" 功能。考虑这个例子

regex = r'Mr\. John Smith|John Smith'
re.findall(regex, "I hate Mr. John Smith)

# -> ['Mr. John Smith'] 

所以解释一下,正则表达式中的管道充当 "or",即匹配这个或那个但不匹配两者,如果它们之间存在嵌套,正则表达式是贪婪的将匹配最长的模式.

在我给出的示例中,"Mr. John Smith" 和 "John Smith" 都是匹配项,但正则表达式选择匹配较长的那个。还要注意 findall() returns 所有匹配项的列表。因此,将此应用于您的案例:

for k,v in eng_names_dict.items():

    # Convert list of matches into one regex string
    regex = r'|'.join(v)
    matches = re.findall(regex, text)
    namesDict[k] += len(matches)

编辑

好的,从您的评论看来,eng_names_dict 的不同键的值之间可能存在歧义,而到目前为止,我的回答仅处理一个键内的值之间的歧义。

这里有两种处理这种情况的方法,以及每种方法的局限性。使用正则表达式,有时会出现歧义,必须使用硬编码规则来解决。

场景一:少数此类模棱两可的案例。

如果值之间的重叠量很小且易于管理,您可以根据偏好对正则表达式语句进行排序,并一点一点地删除文本中的匹配短语。

例如,如果我们有:

{'Mark Smith': ['Dr. Mark Smith', 'Mark Smith'],
 'Andrew Mark': ['Dr. Mark', 'Andrew Mark']

请注意,我假设 Mark Smith 在某处有一个值 "Dr. Mark Smith",即使您没有说情况一定如此。因为如果这不是真的,那么问题就完全不同了(在那种情况下,它将是如何匹配 'Mark Smith' 和不匹配 'Dr. Mark Smith'.

我们可以清楚地看到,Andrew 的一个值嵌套在 Mark 的一个值中。所以我们可以选择先做标记(根据某种规则),然后从文本中删除短语。

from collections import OrderedDict 

od = OrderedDict()
od['Mark Smith'] = eng_names_dict['Mark_Smith']
od['Andrew Mark'] = eng_names_dict['Andrew Mark']

for k,v in eng_names_dict.items():

    # Convert list of matches into one regex string
    regex = r'|'.join(v)
    matches = re.findall(regex, text)
    for match in set(matches):
        text=re.sub(r'{}'.format(match, '', text)
    namesDict[k] += len(matches)

此处的缺点是需要手动确定使用 eng_name_dicts 条目的操作顺序。

场景二:案件数量太多


在这种情况下,我们可以继续使用正则表达式的自然行为来选择与 "or" 匹配的最长字符串。稍微修改原始解决方案。让我们为每个可能的值创建一个非常大的正则表达式,而不是为每个 eng_names_dict 键创建一个小的正则表达式。正则表达式将为我们决定正确的顺序。

# Create one list containing all values from dict
match_vals = []
for dict_val in list(eng_names_dict.values()):
    for match_val in dict_val:
        match_vals.extend(match_val)

# Do a match on this full regex
regex = r'|'.join(match_vals)
matches = re.findall(regex, text)

# Loop through every match, and count it if it's in the vals of an entry's key
for match in matches:
    for k, v in eng_names_dict.items():
        # Nested loops will be slow; open to suggestions to improve
        if match in v:
            namesDict[k] + 1
            # Any match is unique to one person; break loop after match found
            break

优点是正则表达式自然会确定最准确的顺序,因此您无需手动计算。这里的缺点是它很笨重,难以调试,并且可能会影响您自己不知道的名称值之间的关系。