【问题标题】:How to know the operations made to calculate the Levenshtein distance between strings?如何知道计算字符串之间的 Levenshtein 距离的操作?
【发布时间】:2019-11-11 15:27:25
【问题描述】:

使用函数stringdist,我可以计算字符串之间的Levenshtein 距离:它计算将一个字符串转换为另一个字符串所需的删除、插入和替换的次数。例如,stringdist("abc abc","abcd abc") = 1 因为在第二个字符串中插入了“d”。

是否有可能知道获得两个字符串之间的 Levenshtein 距离的操作?或者要知道两个字符串之间不同的字符(在这个例子中,只有“d”)? 谢谢。

library(stringdist)
stringdist("abc abc","abcde acc") = 3

我想知道:

  • “d”已插入

  • “e”已插入

  • “b”被替换为“c”

或者更简单地说,我想要列表 ("d","e","c")。

【问题讨论】:

  • Levenshtein 编辑路径不一定是唯一的:通常会有多个相同长度且最小的编辑序列从一个字符串指向另一个字符串。

标签: r string levenshtein-distance stringdist


【解决方案1】:

使用adist(),您可以检索操作:

drop(attr(adist("abc abc","abcde acc", count = TRUE), "counts"))

ins del sub 
  2   0   1 

来自?adist

如果 counts 为 TRUE,则转换计数作为 这个矩阵的“计数”属性,作为一个 3 维数组 对应于 x 的元素、y 的元素和 转换的类型(插入、删除和替换), 分别。

【讨论】:

  • 感谢它对我帮助很大!不知道有没有直接知道这些操作对应的字符的功能?否则,我可以尝试使用attr(adist("abda cc","abc abc", count = TRUE),"trafos") #= "MMSDMSIM" 创建一个函数,其中M=match, S=substitute, D=delete, I=insert
  • 不知道有什么方便的功能可以做到这一点。但是,我认为在trafos 周围玩会引导您获得想要的结果。
【解决方案2】:

这被称为Needleman–Wunsch algorithm。它计算两个字符串之间的距离以及所谓的 traceback,它允许您重建对齐方式。

由于这个问题主要出现在生物学中比较生物序列时,这个算法(和相关算法)在 R 包{Biostrings} 中实现,它是Bioconductor 的一部分。

由于这个包实现比简单的 Levenshtein 距离更通用的解决方案,不幸的是用法更复杂,usage vignette 也相应长。但您的基本用途如下:

library(Biostrings)

dist_mat = diag(27L)
colnames(dist_mat) = rownames(dist_mat) = c(letters, ' ')

result = pairwiseAlignment(
    "abc abc", "abcde acc",
    substitutionMatrix = dist_mat,
    gapOpening = 1, gapExtension = 1
)

不过,这不会简单地为您提供c('b', 'c', 'c') 列表,因为该列表并不能完全代表此处实际发生的情况。相反,它将返回两个字符串之间的 alignment。这可以表示为具有替换和间隙的序列:

score(result)
# [1] 3
aligned(result)
as.matrix(aligned(result))
#      [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8] [,9]
# [1,] "a"  "b"  "c"  "-"  "-"  " "  "a"  "b"  "c"
aligned(result)

——对于第二个字符串中的每个字符,它提供原始字符串中的相应字符,将插入的字符替换为-。基本上,这是将第一个字符串转换为第二个字符串的“秘诀”。请注意,它将仅包含插入和替换,而不包含删除。要获得这些,您需要以相反的方式执行对齐(即交换字符串参数)。

【讨论】:

  • 不幸的是,上面的代码要求您手动指定dist_mat,以便它包含您的字符串可能包含的每个字符的一行和一列。因此,此答案中显示的代码只允许使用小写字母和空格,没有别的。
【解决方案3】:

这里是提取每种类型的变化次数的代码,然后是每种类型的操作对应的字符:

source_string="12234"
target_string="02345"
lev=adist(source_string,target_string,count=T)

#number of operations of each kind
attributes(lev)$counts[,,"ins"] 
attributes(lev)$counts[,,"del"]
attributes(lev)$counts[,,"sub"]

substitution_bank=deletion_bank=insertion_bank=match_bank=NULL

changes<-strsplit(attributes(lev)$trafos, "")[[1]]

counter_source=counter_target=1
for(j in changes){
 if(j=="S") {
   substitution_bank=rbind(substitution_bank,
           cbind(strsplit(source_string,"")[[1]][counter_source], strsplit(target_string,"")[[1]][counter_target]))
   counter_source=counter_source+1
   counter_target=counter_target+1
 }
 if(j=="I") {
   insertion_bank=rbind(insertion_bank,
                           strsplit(target_string,"")[[1]][counter_target])
   counter_target=counter_target+1
 }
 if(j=="D") {
   deletion_bank=rbind(deletion_bank,
                        strsplit(source_string,"")[[1]][counter_source])
   counter_source=counter_source+1
 }
 if(j=="M") {
   match_bank=rbind(match_bank,
                           strsplit(source_string,"")[[1]][counter_source])
   counter_source=counter_source+1
   counter_target=counter_target+1
 }
 

}

substitution_bank
deletion_bank
insertion_bank
match_bank

老实说,我为代码感到羞耻——一次只输入一个字符似乎很浪费。但是在插入和删除的情况下,我无法弄清楚如何提取正确的字符......所以欢迎更优雅的答案!

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-04-20
    • 2020-05-17
    • 2018-08-11
    • 2016-05-25
    • 2013-04-23
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多