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

时间:2019-06-16 21:15:05

标签: python list substring

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人和16万首诗。

以下是我的代码:

list3 = []

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

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

3 个答案:

答案 0 :(得分:2)

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

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

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引擎会找到较早列出的模式顺序放在后面的顺序上,因此需要按长度反向排序。整个名称模式的...|...|...括号告诉正则表达式引擎以组的形式捕获。然后,循环中的(...)调用可以提取匹配的文本。

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

re.finditer() function(以及编译模式的方法)然后按从左到右的顺序查找不重叠的匹配项,因此它将永远不会匹配较短的子字符串,并为我们提供了提取{{3} }。如果您想了解match object和其他元数据,则可以给您更多选择。否则,这里也可以使用starting positions of the matches

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

)

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

答案 1 :(得分:0)

如果列表较长,可能值得为给定单词出现的句子建立某种“索引”。创建索引所需的时间与在所有list2中找到第一个单词所需的时间相同。 list1中的句子(它必须遍历所有句子中的所有单词),创建之后,您可以在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的正则表达式,并且可以对句子进行规范化(例如,转换为小写字母或应用词干分析器,但不确定是否适用于中文)。

答案 2 :(得分:0)

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

选项 A:查找“所有”可能的组合: 要在一个列表中查找包含来自另一个列表的子字符串的所有字符串,请遍历 set potato = NULLIF(potato,'') 的所有字符串(要评估)并检查每个元素是否包含 list1 的子字符串:

list2

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

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

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 还可让您提高性能。如果列表很长,您可以先运行 B,创建一个子集(只有实际会与子字符串匹配的字符串),然后再次扩展以捕获“所有”而不是任何。