在许多问题中已经回答了找到公共子字符串,即给定字符串列表找到(通常最长或最大字数)子字符串 all 他们。见这里:
Longest common substring from more than two strings - Python
我的问题是,如何在字符串列表中找到最长的模态子字符串?这里的重要规定是该子字符串不一定必须出现在列表中的所有字符串中。
这里的科学有一点点艺术,因为明显的权衡是1)你想要子串出现多少个字符串? 2)你想要子串多长时间?为了解决这个问题,我们假设我们希望所需的子字符串有三个单词(如果这里有一个平局,则取最长的字符串,然后是第一个实例)。
所以给出了列表,
mylist = ["hey how's it going?", "I'm fine thanks.", "Did you get that thing?", "Of course I got that thing, that's why I asked you: how's it going?"]
所需的输出是,
"how's it going"
如果规定是两个字长,那么期望的输出将是,
"that thing"
因为“那个东西”比一个更长的字符串,“它是怎么样”或“它会去”
代码中的上述答案分别是三个和两个单词长的模态子串。
编辑:
由于有一个赏金,我将更加具体地说明一个模态子串。
模态子字符串:对于子字符串中给定长度的单词(这是唯一标识模态子字符串所需的),模态子字符串是子字符串,它对于最大数量的字符串是通用的。列表。如果存在平局(即,对于子串中给定长度的三个字,有两个候选子串都出现在80%的字符串中),则应使用具有最长字符长度的子串。如果在那之后还有一个平局(这应该是非常不可能但是很好的解释),那么只需拿第一个或随机选择。
好的答案会有一个函数返回子字符串中给定数量的单词的模态子字符串(其中单词的数量可以是任意数字)。
令人难以置信的答案会省去“给定数量的单词”限制,而是包含一个标量(比如说\ alpha)来管理子字符串长度(以字为单位)与其次数之间的权衡出现在列表中。接近1的Alpha会选择一个非常长的模态子串(在单词中),但不一定会在列表中多次出现。接近0的Alpha将选择一个模式子字符串,该子字符串尽可能多地出现在列表中,并不关心子字符串长度。我并不是真的期待这个,并且会接受回答原始问题的答案。
答案 0 :(得分:1)
我们的意见如下:
mylist = ["hey how's it going?", "I'm fine thanks.", "Did you get that thing?", "Of course I got that thing, that's why I asked you: how's it going?"]
首先,我们定义一个预处理函数,以清理额外的空格和标点符号。这样可以更轻松地从句子中提取单词:
def preprocess(strings):
# used this answer to get rid of extra whitespaces: https://stackoverflow.com/a/1546251/6735980
return [" ".join(string.split()).replace(",", "").replace(".", "").replace("?", "") for string in strings]
我们还定义了一个辅助函数来查找 n -grams(句子中 n 单词的子串):
def find_n_grams(string, n):
words = string.split(" ")
n_grams = []
for i in range(len(words) - n + 1):
n_grams.append(" ".join([words[i + idx] for idx in range(n)]))
return n_grams
然后,我们可以使用以下函数来查找"模态子字符串"对于给定数量的单词,在问题中定义。我不确定这是否是计算它的最佳/最有效的方法,但它完成了这项工作:
def find_modal_substring(strings, num_words):
n_grams_per_string = [find_n_grams(string, num_words) for string in strings]
max_num_occurences = 0
modal_substring = None
for i in range(len(strings)):
n_grams = n_grams_per_string[i]
for n_gram in n_grams:
num_occurences = 1
for j in range(i + 1, len(strings)):
if n_gram in n_grams_per_string[j]:
num_occurences += 1
if num_occurences > max_num_occurences:
max_num_occurences = num_occurences
modal_substring = n_gram
elif num_occurences == max_num_occurences and len(modal_substring) < len(n_gram):
max_num_occurences = num_occurences
modal_substring = n_gram
return modal_substring
一些输入/输出对:
> print(find_modal_substring(preprocess(mylist), 3))
它是怎么回事
> print(find_modal_substring(preprocess(mylist), 2))
那件事
问题的这一部分最困难的是如何在给定alpha参数的情况下定义得分函数。我们知道:
Alpha接近1会选择一个非常长的模态子字符串(在单词中),但不一定会在列表中多次出现。接近0的Alpha会选择一个模式子字符串,该子字符串尽可能多地出现在列表中,并且不关心子字符串长度。
满足这一要求的一个分数函数如下,但我们也可以想到许多其他功能。通过在函数中隔离它,任何人都可以根据需要轻松修改它:
def compute_score(n_gram, occurences, alpha):
return alpha * len(n_gram.split(" ")) + (1.0 - alpha) * occurences
鉴于此,找到基于alpha的模态子字符串的函数相当长,因为它首先需要为所有 n 找到所有可能的 n -grams,但是这里我们走了:
def find_modal_substring_alpha(strings, alpha):
n_grams_per_n_per_string = []
n = 1
while True:
n_grams_per_string = [find_n_grams(string, n) for string in strings]
if all(n_grams == [] for n_grams in n_grams_per_string):
break
else:
n_grams_per_n_per_string.append(n_grams_per_string)
n += 1
best_modal_substring = None
best_score = 0.0
for n_grams_per_string in n_grams_per_n_per_string:
for i in range(len(strings)):
n_grams = n_grams_per_string[i]
for n_gram in n_grams:
num_occurences = 1
for j in range(i + 1, len(strings)):
if n_gram in n_grams_per_string[j]:
num_occurences += 1
score = compute_score(n_gram, num_occurences, alpha)
if score > best_score:
best_score = score
best_modal_substring = n_gram
elif score == best_score and len(best_modal_substring) < len(n_gram):
best_score = score
best_modal_substring = n_gram
return best_modal_substring
事实证明,alpha在这里有点难以调整。即使对于相对较低的alpha(0.3
),我们也会得到与1.0
完全相同的结果:
> print(find_modal_substring_alpha(preprocess(mylist), 0.3))
当然,我知道了为什么我问你:它是怎么回事
如果我们将alpha一直向下移动到0.01
,我们会得到:
> print(find_modal_substring_alpha(preprocess(mylist), 0.01))
它是怎么回事