如何在字符串中计算特定字符的出现次数?

10
如果我有一串字符,例如"AABBABBBAAAABBAAAABBBAABBBBABABB"
有没有办法让R来计算连续的A的次数并说明每个长度有多少个?
所以我想知道有多少次出现了3个A,有多少次只有一个A,有多少次出现了2个A等。
4个回答

11
table(rle(strsplit("AABBABBBAAAABBAAAABBBAABBBBABABB","")[[1]]))

给予

       values
lengths A B
      1 3 1
      2 2 3
      3 0 2
      4 2 1

按照A列向下阅读,这意味着有3组长度为1的A连续出现,2组长度为2的A连续出现以及2组长度为4的A连续出现。


2
问题中没有提到速度是主要考虑因素,但是会有更快的方法。 - Glen_b
@akrun 是的,rle 通常很快,但 strsplit 可能较慢。 - Glen_b
是的,我可能会使用这个,因为它的输出更好(因为通过长度表示不同的字符串,并且我怀疑速度并不重要)。或者只需使用 table(nchar(strsplit(x, "[^A]+")[[1]])),因为 OP 只关心 "A"。 - Frank
结尾的[[1]]是在做什么? - user3754366
strsplit 返回一个列表(在这种情况下,由于参数只包含一个元素,它返回一个包含一个元素的列表,该元素包含从拆分结果得到的字符向量)。我想要的是字符向量而不是它所包含的列表;[[1]] 是实现这一点的一种方式。 - Glen_b

10

尝试

 v1 <- scan(text=gsub('[^A]+', ',', str1), sep=',', what='', quiet=TRUE)
 table(v1[nzchar(v1)])
 # A   AA AAAA 
 # 3    2    2 

或者

 library(stringi)
 table(stri_extract_all_regex(str1, '[A]+')[[1]])
 # A   AA AAAA 
 # 3    2    2 

基准测试

 set.seed(42)
 x1 <- stri_rand_strings(1,1e7, pattern='[A-G]')

 system.time(table(stri_split_regex(x1, "[^A]+", omit_empty = TRUE)))
 #   user  system elapsed 
 #  0.829   0.002   0.831 

 system.time(table(stri_extract_all_regex(x1, '[A]+')[[1]]))
 #   user  system elapsed 
 #   0.790   0.002   0.791 

 system.time(table(rle(strsplit(x1,"")[[1]])) )
 #   user  system elapsed 
 #  30.230   1.243  31.523 

 system.time(table(strsplit(x1, "[^A]+")))
 # user  system elapsed 
 # 4.253   0.006   4.258 


 system.time(table(attr(gregexpr("A+",x1)[[1]], 'match.length')))
 #  user  system elapsed 
 #  1.994   0.004   1.999 


 library(microbenchmark)
 microbenchmark(david=table(stri_split_regex(x1, "[^A]+", omit_empty = TRUE)),
    akrun=  table(stri_extract_all_regex(x1, '[A]+')[[1]]),
    david2 =  table(strsplit(x1, "[^A]+")),
    glen = table(rle(strsplit(x1,"")[[1]])),
    plannapus = table(attr(gregexpr("A+",x1)[[1]], 'match.length')),
         times=20L, unit='relative')

#Unit: relative
#     expr       min        lq      mean    median         uq       max    neval  cld
#   david  1.0000000  1.000000  1.000000  1.000000  1.0000000  1.000000    20       a  
#   akrun  0.7908313  1.023388  1.054670  1.336510  0.9903384  1.004711    20       a
#  david2  4.9325256  5.461389  5.613516  6.207990  5.6647301  5.374668    20       c 
#    glen 14.9064240 15.975846 16.672339 20.570874 15.8710402 15.465140    20       d
#plannapus 2.5077719  3.123360  2.836338  3.557242  2.5689176  2.452964    20       b 

数据

 str1 <- 'AABBABBBAAAABBAAAABBBAABBBBABABB'

时间安排很有趣。 - Glen_b
@akrun 我觉得我的程序在速度方面可能表现不佳 :) - plannapus
@plannapus 你说得对。我尝试了微基准测试,但是由于时间太长,我不得不停止它。我将使用system.time进行更新。 - akrun
@plannapus 我正在你的第一个函数上运行system.time(microbenchmark所花费的时间很长)。你能否使用基准测试中的数据检查第二个函数的system.time? - akrun
你可以在Frank的回答下面使用他的评论。 - David Arenburg
显示剩余9条评论

8
这是另一种使用strsplit的方法。
x <- "AABBABBBAAAABBAAAABBBAABBBBABABB"
table(strsplit(x, "[^A]+"))
# A   AA AAAA 
# 3    2    2 

或者类似地使用 stringi

library(stringi)
table(stri_split_regex(x, "[^A]+", omit_empty = TRUE))

3

为了完整起见,这里介绍另一种提取正则表达式的方法,使用regmatchesgregexpr组合:

x <- "AABBABBBAAAABBAAAABBBAABBBBABABB"
table(regmatches(x,gregexpr("A+",x))[[1]])
#   A   AA AAAA 
#   3    2    2

实际上,由于gregexpr将捕获的子字符串长度保留为属性,因此甚至可以直接进行以下操作:

table(attr(gregexpr("A+",x)[[1]],'match.length'))
# 1 2 4 
# 3 2 2 

我更新了基准测试。你的版本在基本R函数中更快。 - akrun
我尝试使用替换创建了一个100个随机样本,结果得到了一些"A"和"B"的混合。我认为这样做会带来更多问题,因为现在我似乎有了100个单独的字符串。这是个坏主意吗? - user3754366
如果你想创建一个有放回的100个A或B的随机样本,你可以使用@akrun的基于stringi的技术(参见他的答案)或者使用base的方式:paste(sample(c("A","B"),100,replace=TRUE),collapse="") - plannapus

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接