繁体   English   中英

查找包含来自另一个列表的子字符串的列表元素的有效方法

[英]An efficient way to find elements of a list that contain substrings from another list

list1 = ["happy new year", "game over", "a happy story", "hold on"]
list2 = ["happy", "new", "hold"]

假设我有两个字符串列表,我想使用一个新列表来存储这两个列表的匹配对,如下所示:

list3=[["happy new year","happy"],["happy new year","new"],["a happy story","happy"],["hold on","hold"]]

这意味着我需要获取一个列表中的所有字符串对,以及另一个列表中的子字符串。

其实那是关于一些中国古文字资料。 第一个列表包含 10 至 13 世纪的人名,第二个列表包含该时期所有诗歌的标题。 中国古代人经常在作品的标题中记载他们的社会关系。 例如,某人可能会写一首诗,题为“为我的朋友王安石”。 在这种情况下,第一个列表中的人“王安石”应该与此标题匹配。 还有像《为了我的朋友王安石和苏轼》这样的案例,标题中包含了不止一个人。 所以基本上这是一项巨大的工作,涉及 30,000 人和 160,000 首诗。

以下是我的代码:

list3 = []

for i in list1:
        for j in list2:
            if str(i).count(str(j)) > 0:
                list3.append([i,j])

我使用 str(i) 是因为 python 总是将我的中文字符串作为浮点数。 这段代码确实有效,但太慢了。 我必须想出另一种方法来做到这一点。 谢谢!

通过re模块 ,使用正则表达式进行搜索。 正则表达式引擎可以比通过嵌套的for循环更好地搜索文本中的匹配元素。

我将在此处使用更好的变量名,以更清楚地知道列表的位置。 titles是您要搜索的诗歌标题,并为您要匹配的事物names 要生成的(title, name)matched

import re

titles = ["happy new year", "game over", "a happy story", "hold on"]
names = ["happy", "new", "hold"]

by_reverse_length = sorted(names, key=len, reverse=True)
pattern = "|".join(map(re.escape, by_reverse_length))
any_name = re.compile("({})".format(pattern))
matches = []

for title in titles:
    for match in any_name.finditer(title):
        matches.append((title, match.group()))

上面产生了所需的输出:

>>> matches
[('happy new year', 'happy'), ('happy new year', 'new'), ('a happy story', 'happy'), ('hold on', 'hold')]

名称按照长度相反的顺序排列,以便在具有相同前缀的较短名称之前找到较长的名称; Hollander之前发现Holland之前发现Holl

pattern字符串是根据您的名字创建的,形成一个...|...|... 替代模式,这些模式中的任何一个都可以匹配,但是regex引擎会在顺序中找到较早列出的那些,因此需要按长度反向排序。 整个名称模式的(...)括号告诉正则表达式引擎捕获一组文本中的该部分。 然后,循环中的match.group()调用可以提取匹配的文本。

调用re.escape()可以防止名称中的“元字符”,具有特殊含义的字符(例如^$())被解释为其特殊的正则表达式含义。

然后, re.finditer()函数 (以及编译模式中的方法)按从左到右的顺序查找不重叠的匹配项,因此它将永远不会匹配较短的子字符串,并为我们提供了为每个子字符串提取匹配对象的机会。 如果您想知道比赛和其他元数据的开始位置,则可以给您更多选择。 否则, re.findall()也可以在这里使用。

如果要在带有西文字母的文本上而不是中文上使用以上内容 ,那么您可能还想添加单词边界标记\\b

any_name = re.compile("\b({})\b".format(pattern))

否则,可以匹配较大单词的子字符串部分。 由于中文没有单词边界字符(例如空格和标点符号),因此您不想在此类文本中使用\\b

如果列表较长,则可能需要为给定单词出现的句子建立某种“索引”。创建索引所需的时间与在list1所有句子中从list2中找到第一个单词一样长(必须循环遍历所有句子中的所有单词),创建后,您可以在O(1)中更快地获得包含单词的句子。

list1 = ["happy new year", "game over", "a happy story", "hold on"]    
list2 = ["happy", "new", "hold"]

import collections    
index = collections.defaultdict(list)

for sentence in list1:
    for word in sentence.split():
        index[word].append(sentence)

res = [[sentence, word] for word in list2 for sentence in index[word]]

结果:

[['happy new year', 'happy'],
 ['a happy story', 'happy'],
 ['happy new year', 'new'],
 ['hold on', 'hold']]

这使用str.split在空格处分割单词,但是如果句子更复杂(例如,如果它们包含标点符号),则可以使用带有单词边界\\b的正则表达式,并可能对句子进行规范化(例如,转换为小写字母或应用词干分析器,但不确定是否适用于中文)。

这可以以绝对直接的方式轻松完成。

选项A:寻找“所有”可能的组合:要找到一个列表包含从另一个列表中的子字符串的所有字符串,遍历所有的字符串list1 (字符串评估)对于每个元素检查它是否包含的子list2

list1 = ["happy new year", "game over", "a happy story", "hold on"]
list2 = ["happy", "new", "hold"]
[(string, substring) for string in list1 for substring in list2 if substring in string]
>>> [('happy new year', 'happy'), ('happy new year', 'new'), ('a happy story', 'happy'), ('hold on', 'hold')]

(不过,我确实认为您的问题的标题有点误导,因为您不仅要求包含另一个列表的子字符串的列表元素,而且根据您的代码示例,您正在寻找“所有可能的组合” .)

因此选项 B:查找“任何”组合:更简单和更快,如果您真的只需要问题所说的内容,则可以通过仅查找“任何”匹配来提高性能:

[string for string in list1 if ( substring in string for substring in list2)]

选项 B 还可以让您提高性能。 如果列表很长,您可以先运行 B,创建一个子集(只有实际与子字符串匹配的字符串),然后再次扩展以捕获“全部”而不是任何。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM