R代码:快速找到模式&它在字符串

时间:2015-12-30 06:57:04

标签: r algorithm

我有一个数字向量,我想将它们分析为字符。对于每组数字(即" 3412123401234"),我想知道什么是模式,优先考虑最常出现的模式长度大于1的模式。在&#34的情况下; 3412123401234",我希望它能够认识到" 34"作为模式和出现为3。

我的算法非常低效,需要永久循环一个包含100000多个数字集的列表。需要一种更有效的方式以矢量方式处理它。

repeats = function(string){
  out = c()
  check.till = floor(nchar(string)/2)
  for (start in 1:nchar(string)){
    for (end in 1:check.till){
      pat = substr(string,start,start+end)
      repeats = length(gregexpr(pat,string)[[1]])
      length = nchar(pat)
      if (length >= 2) out = rbind(out, c(pat, repeats, length))
    }
  }
  out = as.data.frame(out, stringsAsFactors = F)
  colnames(out) = c("Pattern","Repeats","Pat.Length")
  out$Repeats = as.integer(out$Repeats)
  out$Pat.Length = as.integer(out$Pat.Length)
  out = out[out$Repeats > 1,]
  out = out[order(out[,2],out[,3],decreasing = T),]
  out = out[1,]
  out[is.na(out)] = 0
  return(out)
}

3 个答案:

答案 0 :(得分:0)

我不完全确定这是否是一个解决方案,但我尝试的是将矢量拆分为所有独特的组合。

我的想法是所有组合都包含在“对角矩阵”的上半部分中:

d <- c("3412123401234")
# create function
make.matrix <- function(x,y) {
 vl <- nchar(d)
 OUT <- seq(x,nchar(y),1)
 if(length(OUT) < vl) OUT <- c(OUT,rep(NA,vl-length(OUT)))
 OUT
}
mm <- sapply(1:nchar(d), make.matrix, d)  
mm

    [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8] [,9] [,10] [,11] [,12] [,13]
tmp    1    2    3    4    5    6    7    8    9    10    11    12    13
tmp    2    3    4    5    6    7    8    9   10    11    12    13    NA
tmp    3    4    5    6    7    8    9   10   11    12    13    NA    NA
tmp    4    5    6    7    8    9   10   11   12    13    NA    NA    NA
tmp    5    6    7    8    9   10   11   12   13    NA    NA    NA    NA
tmp    6    7    8    9   10   11   12   13   NA    NA    NA    NA    NA
tmp    7    8    9   10   11   12   13   NA   NA    NA    NA    NA    NA
tmp    8    9   10   11   12   13   NA   NA   NA    NA    NA    NA    NA
tmp    9   10   11   12   13   NA   NA   NA   NA    NA    NA    NA    NA
tmp   10   11   12   13   NA   NA   NA   NA   NA    NA    NA    NA    NA
tmp   11   12   13   NA   NA   NA   NA   NA   NA    NA    NA    NA    NA
tmp   12   13   NA   NA   NA   NA   NA   NA   NA    NA    NA    NA    NA
tmp   13   NA   NA   NA   NA   NA   NA   NA   NA    NA    NA    NA    NA

现在我使用str_sub包的stringr函数根据第一行的起始值与矩阵mm

的每一行划分向量
library(stringr)
mm2 <- sapply(2:nrow(mm), function(x,y) str_sub(string= y, start= mm[1,], end= mm[x,]), d) 

最后计算独特的模式:

counts <- sort(table(mm2),decreasing = T)

您想要的输出:

res <- data.frame("nlength"=sapply(names(counts),nchar),"counts"=counts)
head(res)
     nlength counts
12         2     3
34         2     3
123        3     2
1234       4     2
23         2     2
234        3     2

答案 1 :(得分:0)

基础R中的另一种方法(与实际算法相差不大,主要使用正则表达式来获得长度为2的可能匹配):

str<-c("3412123401234") # example entries

replength<-function(pat,s) {
  length( gregexpr(pat,s)[[1]] )
}

repeats<-function(s) {
  r<-sapply(
    unique(
      regmatches( s, gregexpr("(\\d{2,})(?=.*\\1)", s, perl=T) )[[1]]
    ),
    replength,s=s )
  pat<-names(r[r==max(r)][1])
  data.frame("Pattern"=pat,"Repeats"=unname(r[r==max(r)][1]),"Pat.length"=nchar(pat),stringsAsFactors=FALSE)
}

这给出了输出:

> repeats(str)
  Pattern Repeats Pat.length
1      34       3          2

在第一个示例中,它将跳过一些中间匹配123,具体取决于它们在字符串中的显示方式。但是,如果它们不匹配,那么它们应该被另一个最长或等效的匹配“取代”(我没有找到证明它错误的测试用例)。

答案的基准(所有函数的代码都很长,所以我创建了gist

> microbenchmark(original(str),jimbou(str),tensibai(str),times=3)
Unit: microseconds
          expr      min        lq     mean   median       uq      max neval
 original(str) 1439.982 1722.5050 1849.559 2005.028 2054.347 2103.667     3
   jimbou(str) 1866.474 1994.3365 2227.586 2122.199 2408.142 2694.086     3
 tensibai(str)  468.971  494.9145  558.109  520.858  602.678  684.498     3

答案 2 :(得分:-1)

正如其他人所指出的那样,2克图案总是至少与最常见的图案相关联,所以它(几乎)只需要考虑2克。*

只是为了好玩,请考虑这个python 3解决方案:

# Solution
from collections import Counter
def most_common_2gram(row):
    pairs = [tuple(row[j:j+2]) for j in range(len(row)-1)]
    return Counter(pairs).most_common(1) # or most_common(k) as desired

# Testing Data
from random import randint
nrow = int(1e6)
ncol = 10
x = [[randint(0,9) for j in range(ncol)] for i in range(nrow)]

# Performance Test
%timeit [most_common_2gram(row) for row in x]

# Time result: 21 seconds (from %paste into IPython)

使用20列运行示例:

x[0:5]
Output: 
 [[0, 8, 1, 7, 9, 7, 7, 3, 8, 5, 1, 5, 4, 7, 8, 5, 4, 2, 3, 5],
 [6, 5, 4, 0, 9, 0, 0, 4, 4, 3, 7, 1, 7, 0, 7, 5, 1, 9, 0, 5],
 [2, 4, 8, 5, 8, 9, 5, 9, 0, 8, 8, 2, 4, 8, 1, 6, 3, 7, 8, 1],
 [5, 6, 6, 7, 1, 7, 9, 3, 3, 0, 7, 9, 5, 7, 6, 3, 8, 5, 3, 3],
 [4, 6, 3, 9, 1, 5, 5, 4, 9, 0, 2, 1, 5, 3, 4, 2, 4, 1, 4, 5]]

out[0:5]
Output: [[((5, 4), 2)], [((9, 0), 2)], [((4, 8), 2)], [((3, 3), 2)], [((1, 5), 2)]]

如果您想要所有模式,而不仅仅是最常见的模式,您可以将Counter对象转换为列表并按计数排序。例如:

counts = Counter({(0,2): 2, (3,4): 3, (1,6): 1})
sorted(counts.items(),key=lambda pc: pc[1],reverse=True)
Output: [((3, 4), 3), ((0, 2), 2), ((1, 6), 1)]

*唯一可能的例外是如果2克的频率等于3克(或更多),你希望3克+是首选。可以通过更复杂的代码检测和处理这种情况。