计算文本文件中的名称出现次数,对重复项敏感
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
优点是正则表达式自然会确定最准确的顺序,因此您无需手动计算。这里的缺点是它很笨重,难以调试,并且可能会影响您自己不知道的名称值之间的关系。
我有一个姓名列表,想计算整个文本文件语料库中出现的次数。
我正在使用带有字典的简单正则表达式搜索来执行此操作:
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
优点是正则表达式自然会确定最准确的顺序,因此您无需手动计算。这里的缺点是它很笨重,难以调试,并且可能会影响您自己不知道的名称值之间的关系。