我想分割一个字符串,但仅在没有给定字符集包围的情况下才使用分隔符
当前:
strsplit("1 ? 2 ? (3 ? 4) ? {5 ? (6 ? 7)}","\\?")
#> [[1]]
#> [1] "1 " " 2 " " (3 " " 4) " " {5 " " (6 " " 7)}"
预期:
strsplit2 <- function(x, split, fixed = FALSE, perl = FALSE, useBytes = FALSE,
escape = c("()","{}","[]","''",'""',"%%")){
# ...
}
strsplit2("1 ? 2 ? (3 ? 4) ? {5 ? (6 ? 7)}","\\?")
#> [[1]]
#> [1] "1 " " 2 " " (3 ? 4) " " {5 ? (6 ? 7)}"
我通过一些复杂的解析解决了这个问题,但我担心性能,想知道正则表达式是否可以更快。
仅供参考:
我当前的解决方案(与问题无关)是:
parse_qm_args <- function(x){
x <- str2lang(x)
# if single symbol
if(is.symbol(x)) return(x)
i <- numeric(0)
out <- character(0)
while(identical(x[[c(i,1)]], quote(`?`)) &&
(!length(i) || length(x[[i]]) == 3)){
out <- c(x[[c(i,3)]],out)
i <- c(2, i)
}
# if no `?` was found
if(!length(out)) return(x)
if(length(x[[i]]) == 2) {
# if we have a unary `?` fetch its arg
out <- c(x[[c(i,2)]],out)
} else {
# if we have a binary `?` fetch the its first arg
out <- c(x[[c(i)]], out)
}
out
}
答案 0 :(得分:2)
最好的方法是使用递归。在这种情况下,您将所有分组的元素捕获在一起,然后在未分组的分隔符上拆分:
pattern = "([({'](?:[^(){}']*|(?1))*[')}])(*SKIP)(*FAIL)|\\?"
x1 <- "1 ? 2 ? (3 ? 4) ? {5 ? (6 ? 7)}"
x2 <- "1 ? 2 ? '3 ? 4' ? {5 ? (6 ? 7)}"
x3 <- "1 ? 2 ? '3 {(? 4' ? {5 ? (6 ? 7)}"
x4 <- "1 ? 2 ? '(3 ? 4) ? {5 ? (6 ? 7)}'"
strsplit(c(x1,x2,x3, x4),pattern,perl=TRUE)
[[1]]
[1] "1 " " 2 " " (3 ? 4) " " {5 ? (6 ? 7)}"
[[2]]
[1] "1 " " 2 " " '3 ? 4' " " {5 ? (6 ? 7)}"
[[3]]
[1] "1 " " 2 " " '3 {(? 4' " " {5 ? (6 ? 7)}"
[[4]]
[1] "1 " " 2 " " '(3 ? 4) ? {5 ? (6 ? 7)}'"
答案 1 :(得分:1)
(*SKIP)(*FAIL)
和perl = T
是您的朋友在这里:
some_string <- c("1 ? 2 ? (3 ? 4) ? {5 ? (6 ? 7)}")
pattern <- c("(?:\\{[^{}]*\\}|\\([^()]*\\))(*SKIP)(*FAIL)|\\?")
some_parts <- strsplit(some_string, pattern, perl = T)
some_parts
这产生
[[1]]
[1] "1 " " 2 " " (3 ? 4) " " {5 ? (6 ? 7)}"
请参见a demo on regex101.com。这不适用于嵌套构造。
答案 2 :(得分:1)
这是@CodeManiac想法的实现,它进行了一些优化并处理了一些极端情况。
splitter <- function(x) {
str <- strsplit(x,"")[[1]]
final <- character(0)
strTemp <- ""
count <- 0
# define escape sets
parensStart <- c("{","(")
parensClosing <- c("}",")")
parensBoth <- c("'",'"', "%")
quotes_on <- FALSE
for(i in 1:nchar(x)){
if(str[i] %in% parensBoth){
# handle quotes
strTemp <- c(strTemp,str[i])
if(!quotes_on) {
quotes_on <- TRUE
count <- 1 # no need to count here, just make it non zero
} else {
quotes_on <- FALSE
count <- 0
}
i <- i + 1
next
}
if(str[i] == "?" && count == 0){
# if found `?` reinitialise strTemp and count and append final
final <- c(final, paste(strTemp, collapse=""))
strTemp <- ""
count <- 0
i <- i + 1
next
}
strTemp <- c(strTemp,str[i])
if(str[i] %in% parensStart){
# increment count entering set
count <- count+1
} else if(str[i] %in% parensClosing){
# decrement if exiting set
count <- count-1
}
i <- i + 1
}
# append what's left
final <- c(final, paste(strTemp, collapse=""))
final
}
结果:
x1 <- "1 ? 2 ? (3 ? 4) ? {5 ? (6 ? 7)}"
splitter(x1)
#> [1] "1 " " 2 " " (3 ? 4) " " {5 ? (6 ? 7)}"
x2 <- "1 ? 2 ? '3 ? 4' ? {5 ? (6 ? 7)}"
splitter(x2)
#> [1] "1 " " 2 " " '3 ? 4' " " {5 ? (6 ? 7)}"
在写问题时我没有想到的一个极端情况,引号之间的字符不是分隔符的候选对象
x3 <- "1 ? 2 ? '3 {(? 4' ? {5 ? (6 ? 7)}"
splitter(x3)
#> [1] "1 " " 2 " " '3 {(? 4' " " {5 ? (6 ? 7)}"
基准
到目前为止,解析可以快10倍,尽管可以使用Rcpp进一步优化上述解决方案。解析解决方案也可能会进一步优化。
Jan和Onyambu的解决方案更加紧凑和优雅。 Onyambu的句柄处理嵌套,引号以及用引号引起的分隔符的边缘情况(尽管不是问题的一部分),而Jan则不行。而且它们的运行速度同样如此。
regex_split_jan <- function(x){
pattern <- c("(?:\\{[^{}]*\\}|\\([^()]*\\))(*SKIP)(*FAIL)|\\?")
out <- strsplit(x, pattern, perl = T)[[1]]
out
}
regex_split_onyambu <- function(x){
pattern <- c("([({'](?:[^(){}']*|(?1))*[')}])(*SKIP)(*FAIL)|\\?")
out <- strsplit(x, pattern, perl = T)[[1]]
out
}
microbenchmark::microbenchmark(
regex_jan = as.list(parse(text=regex_split_jan(x))),
regex_onyambu = as.list(parse(text=regex_split_onyambu(x))),
loop = as.list(parse(text=splitter(x))),
parse = parse_qm_args(x)
)
#> Unit: microseconds
#> expr min lq mean median uq max neval cld
#> regex_jan 89.1 92.15 112.114 92.95 94.45 1893.5 100 b
#> regex_onyambu 91.0 93.50 116.850 94.95 96.45 2056.1 100 b
#> loop 122.0 125.95 130.289 128.30 131.20 169.8 100 b
#> parse 10.7 13.55 14.642 14.80 15.65 25.3 100 a