разбить строку, но игнорировать разделители, окруженные заданными символами - PullRequest
1 голос
/ 25 сентября 2019

Я хотел бы разбить строку, но использовать разделитель, только если он не окружен заданными наборами символов

current:

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
}

Ответы [ 3 ]

2 голосов
/ 25 сентября 2019

Лучшей идеей будет использование рекурсии.В этом случае вы соберете все сгруппированные элементы вместе, а затем разделите их по разгруппированному разделителю:

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 голос
/ 25 сентября 2019

Вот реализация идеи @ 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.Решение для синтаксического анализа также может быть оптимизировано.

Решения Яна и Оньямбу гораздо более компактны и элегантны.Онямбу обрабатывает вложенность, кавычки и крайний регистр разделителей, заключенных в кавычки (хотя и не является частью вопроса), а Ян - нет.И они примерно так же быстры.

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 
1 голос
/ 25 сентября 2019

(*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)}"

См. демо на regex101.com .Это не будет работать для вложенных конструкций.

...