![](/img/trans.png)
[英]Find elements of a list that contain substrings from another list in Python
[英]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.