20

我有一个关于在 R 中查找最长公共子字符串的问题。在搜索 StackOverflow 上的一些帖子时,我了解了 qualV 包。但是,我看到这个包中的 LCS 函数实际上找到了 string1 中存在于 string2 中的所有字符,即使它们不连续。

解释一下,如果字符串是 string1 : " hell lo" string2 : " hel 12345lo" 我希望输出是hel,但是我得到的输出是 hello。我一定做错了什么。请在下面查看我的代码。

library(qualV)
a= "hello"
b="hel123l5678o" 
sapply(seq_along(a), function(i)
    paste(LCS(substring(a[i], seq(1, nchar(a[i])), seq(1, nchar(a[i]))),
              substring(b[i], seq(1, nchar(b[i])), seq(1, nchar(b[i]))))$LCS,
          collapse = ""))

我也尝试了 Rlibstree 方法,但我仍然得到不连续的子字符串。此外,子字符串的长度也超出了我的预期。请参见下文。

> a = "hello"
> b = "h1e2l3l4o5"

> ll <- list(a,b)
> lapply(data.frame(do.call(rbind, ll), stringsAsFactors=FALSE), function(x) getLongestCommonSubstring(x))
$do.call.rbind..ll.
[1] "h" "e" "l" "o"

> nchar(lapply(data.frame(do.call(rbind, ll), stringsAsFactors=FALSE), function(x) getLongestCommonSubstring(x)))
do.call.rbind..ll.
                21
4

5 回答 5

12

以下是三种可能的解决方案。

library(stringi)
library(stringdist)

a <- "hello"
b <- "hel123l5678o"

## get all forward substrings of 'b'
sb <- stri_sub(b, 1, 1:nchar(b))
## extract them from 'a' if they exist
sstr <- na.omit(stri_extract_all_coll(a, sb, simplify=TRUE))
## match the longest one
sstr[which.max(nchar(sstr))]
# [1] "hel"

在基础 R中也有adist()and ,并且该包有一些运行 LCS 方法的函数。下面来看看。它返回未配对字符的数量。agrep()stringdiststringsidt

stringdist(a, b, method="lcs")
# [1] 7

Filter("!", mapply(
    stringdist, 
    stri_sub(b, 1, 1:nchar(b)),
    stri_sub(a, 1, 1:nchar(b)),
    MoreArgs = list(method = "lcs")
))
#  h  he hel 
#  0   0   0 

现在我已经对此进行了更多探索,我认为adist()这可能是要走的路。如果我们设置counts=TRUE,我们会得到一系列匹配、插入等。因此,如果您将其提供给我们,stri_locate()我们可以使用该矩阵来获取从 a 到 b 的匹配。

ta <- drop(attr(adist(a, b, counts=TRUE), "trafos")))
# [1] "MMMIIIMIIIIM"

因此,这些M值直接表示匹配。我们可以去获取子字符串stri_sub()

stri_sub(b, stri_locate_all_regex(ta, "M+")[[1]])
# [1] "hel" "l"   "o" 

抱歉,我没有很好地解释这一点,因为我不精通字符串距离算法。

于 2015-02-01T13:55:11.200 回答
9

利用@RichardScrivenadist可以使用的洞察力(它计算“近似字符串距离”。我制作了一个更全面的函数。请注意"trafos"代表用于确定两个字符串之间“距离”的“转换”(底部示例)

编辑这个答案可能会产生错误/意外的结果;正如@wdkrnls 所指出的:

我对“apple”和“big apple bagels”运行了你的函数,它返回了“appl”。我会期待“苹果”。

有关错误结果,请参阅下面的说明。我们从一个获取longest_string列表的函数开始:

longest_string <- function(s){return(s[which.max(nchar(s))])}

然后我们可以使用@RichardSriven 的工作和stringi库:

library(stringi)
lcsbstr <- function(a,b) { 
  sbstr_locations<- stri_locate_all_regex(drop(attr(adist(a, b, counts=TRUE), "trafos")), "M+")[[1]]
  cmn_sbstr<-stri_sub(longest_string(c(a,b)), sbstr_locations)
  longest_cmn_sbstr <- longest_string(cmn_sbstr)
   return(longest_cmn_sbstr) 
}

或者我们可以重写我们的代码以避免使用任何外部库(仍然使用 R 的原生adist函数):

lcsbstr_no_lib <- function(a,b) { 
    matches <- gregexpr("M+", drop(attr(adist(a, b, counts=TRUE), "trafos")))[[1]];
    lengths<- attr(matches, 'match.length')
    which_longest <- which.max(lengths)
    index_longest <- matches[which_longest]
    length_longest <- lengths[which_longest]
    longest_cmn_sbstr  <- substring(longest_string(c(a,b)), index_longest , index_longest + length_longest - 1)
    return(longest_cmn_sbstr ) 
}

上述两个函数都只标识'hello '为最长的公共子字符串,而不是 ' hello r'(无论哪个参数是两者中较长的一个):

identical('hello',
    lcsbstr_no_lib('hello', 'hello there'), 
    lcsbstr(       'hello', 'hello there'),
    lcsbstr_no_lib('hello there', 'hello'), 
    lcsbstr(       'hello there', 'hello'))

最后编辑 请注意此结果的一些奇怪行为:

lcsbstr('hello world', 'hello')
#[1] 'hell'

我期待着'hello',但是由于转换实际上(通过删除)将 w o rld 中的“o”移动到 hell o中的“o” ——根据以下条件,只有hell部分被认为是匹配的M

drop(attr(adist('hello world', 'hello', counts=TRUE), "trafos"))
#[1] "MMMMDDDMDDD"
#[1]  vvvv   v
#[1] "hello world"

使用这个 Levenstein 工具可以观察到这种行为——它提供了两种可能的解决方案,相当于这两种转换

#[1] "MMMMDDDMDDD"
#[1] "MMMMMDDDDDD"

我不知道我们是否可以配置adist为首选一种解决方案而不是另一种解决方案?(变换具有相同的“权重”——相同数量的“M”和“D”——不知道如何选择具有更多连续 M数的变换)

最后,别忘了 adist 允许你传入ignore.case = TRUEFALSE默认)

  • "trafos"财产的钥匙adist;从一个字符串到另一个字符串的“转换”:

M转换序列作为返回值的“trafos”属性返回,作为带有元素、和的字符串I,表示匹配、插入、删除和替换DS

于 2015-10-28T07:08:07.473 回答
1

我不确定你做了什么来得到“你好”的输出。根据下面的试错实验,该LCS函数似乎 (a) 如果一个字符跟随在原本应该是 LCS 的字符后面,则该函数不会将字符串视为 LCS;(b) 找到多个等长的 LCS(不像 sub() 只找到第一个);(c) 字符串中元素的顺序无关紧要——下面没有说明;(b) LCS 调用中字符串的顺序无关紧要——也没有显示。

因此,您的 a 的“hello”在 b 中没有 LCS,因为 b 的“hel”后面跟着一个字符。嗯,这是我目前的假设。

以上A点:

a= c("hello", "hel", "abcd")
b= c("hello123l5678o", "abcd") 
print(LCS(a, b)[4]) # "abcd" - perhaps because it has nothing afterwards, unlike hello123...

a= c("hello", "hel", "abcd1") # added 1 to abcd
b= c("hello123l5678o", "abcd") 
print(LCS(a, b)[4]) # no LCS!, as if anything beyond an otherwise LCS invalidates it

a= c("hello", "hel", "abcd") 
b= c("hello1", "abcd") # added 1 to hello
print(LCS(a, b)[4]) # abcd only, since the b hello1 has a character

以上B点:

a= c("hello", "hel", "abcd") 
b= c("hello", "abcd") 
print(LCS(a, b)[4]) # found both, so not like sub vs gsub of finding first or all
于 2015-02-01T12:22:00.140 回答
1
df <- data.frame(A. = c("Australia", "Network"),
                 B. = c("Austria", "Netconnect"), stringsAsFactors = FALSE)

 auxFun <- function(x) {

   a <- strsplit(x[[1]], "")[[1]]
   b  <- strsplit(x[[2]], "")[[1]]
   lastchar <- suppressWarnings(which(!(a == b)))[1] - 1

   if(lastchar > 0){
     out <- paste0(a[1:lastchar], collapse = "")
   } else {
     out <- ""
   }

   return(out)
 }

 df$C. <- apply(df, 1, auxFun)

 df
 A.         B.    C.
 1 Australia    Austria Austr
 2   Network Netconnect   Net
于 2018-06-06T09:30:04.070 回答
0

使用生物字符串:

library(Biostrings)
a= "hello"
b="hel123l5678o"
astr= BString(a)
bstr=BString(b)

pmatchPattern(astr, bstr)

返回:

  Views on a 12-letter BString subject
Subject: hel123l5678o
views:
      start end width
  [1]     1   3     3 [hel]
  Views on a 5-letter BString pattern
Pattern: hello
views:
      start end width
  [1]     1   3     3 [hel]

所以我做了一个基准测试,虽然我的答案确实做到了这一点,并且实际上为您提供了更多信息,但它比 @Rich Scriven 慢了约 500 倍,哈哈。

system.time({
a= "hello"
b="123hell5678o"
rounds=100
for (i in 1:rounds) {
astr= BString(a)
bstr=BString(b)
pmatchPattern(astr, bstr)
}
})

system.time({
  c= "hello"
  d="123hell5678o"
  rounds=100
  for (i in 1:rounds) {
ta <- drop(attr(adist(c, d, counts=TRUE), "trafos"))
stri_sub(d, stri_locate_all_regex(ta, "M+")[[1]])
}
})

   user  system elapsed 
  2.476   0.027   2.510 

   user  system elapsed 
  0.006   0.000   0.005 
于 2021-05-31T00:29:39.567 回答