расширить gsub и grepl, чтобы игнорировать подстроки между заданными разделителями - PullRequest
2 голосов
/ 09 ноября 2019

Я хочу иметь возможность использовать grepl() и gsub() только вне заданных наборов разделителей, например, я хочу иметь возможность игнорировать текст между кавычками.

Вот мой желаемый вывод:

grepl2("banana", "'banana' banana \"banana\"", escaped =c('""', "''"))
#> [1] TRUE
grepl2("banana", "'banana' apple \"banana\"", escaped =c('""', "''"))
#> [1] FALSE
grepl2("banana", "{banana} banana {banana}", escaped = "{}")
#> [1] TRUE
grepl2("banana", "{banana} apple {banana}", escaped = "{}")
#> [1] FALSE

gsub2("banana", "potatoe", "'banana' banana \"banana\"")
#> [1] "'banana' potatoe \"banana\""
gsub2("banana", "potatoe", "'banana' apple \"banana\"")
#> [1] "'banana' apple \"banana\""
gsub2("banana", "potatoe", "{banana} banana {banana}", escaped = "{}")
#> [1] "{banana} potatoe {banana}"
gsub2("banana", "potatoe", "{banana} apple {banana}", escaped = "{}")
#> [1] "{banana} apple {banana}"

В реальных случаях подстроки могут указываться в разных количествах и порядке.

Я написал следующие функции, которые работают в этих случаях, но они неуклюжи и gsub2() совсем не устойчивы, так как временно заменяют контент с разделителями заполнителями, и последующие операции могут повлиять на эти заполнители. .

regex_escape <-
function(string,n = 1) {
  for(i in seq_len(n)){
    string <- gsub("([][{}().+*^$|\\?])", "\\\\\\1", string)
  }
  string
}

grepl2 <- 
  function(pattern, x, ignore.case = FALSE, perl = FALSE, fixed = FALSE, 
           useBytes = FALSE, escaped =c('""', "''")){
    escaped <- strsplit(escaped,"")
    # TODO check that "escaped" delimiters are balanced and don't cross each other
    for(i in 1:length(escaped)){
      close <- regex_escape(escaped[[i]][[2]])
      open <- regex_escape(escaped[[i]][[1]])
      pattern_i <- sprintf("%s.*?%s", open, close)
      x <- gsub(pattern_i,"",x)
    }
    grepl(pattern, x, ignore.case, perl, fixed, useBytes)
  }

gsub2 <- function(pattern, replacement, x, ignore.case = FALSE, perl = FALSE, 
                   fixed = FALSE, useBytes = FALSE, escaped =c('""', "''")){
  escaped <- strsplit(escaped,"")
  # TODO check that "escaped" delimiters are balanced and don't cross each other
  matches <- character()
  for(i in 1:length(escaped)){
    close <- regex_escape(escaped[[i]][[2]])
    open <- regex_escape(escaped[[i]][[1]])
    pattern_i <- sprintf("%s.*?%s", open, close)
    ind <- gregexpr(pattern_i,x)
    matches_i <- regmatches(x, ind)[[1]]
    regmatches(x, ind)[[1]] <- paste0("((",length(matches) + seq_along(matches_i),"))")
    matches <- c(matches, matches_i)
  }
  x <- gsub(pattern, replacement, x, ignore.case, perl, fixed, useBytes)
  for(i in seq_along(matches)){
    pattern <- sprintf("\\(\\(%s\\)\\)", i)
    x <- gsub(pattern, matches[[i]], x)
  }
  x
}

Существует ли решение с использованием регулярных выражений и без заполнителя? Обратите внимание, что моя текущая функция поддерживает несколько пар разделителей, но я буду удовлетворен решением, которое поддерживает только одну пару, и не будет пытаться сопоставить подстроки, например, между простыми кавычками.

Также допустимонакладывать разные разделители, например { и }, а не 2 " или 2 ', если это помогает.

Мне также хорошо с наложением perl = TRUE

Ответы [ 4 ]

3 голосов
/ 09 ноября 2019

Вы можете использовать аргументы start/end_escape для предоставления LHS и RHS сопоставленных разделителей, таких как { и }, не сопоставляя их в неправильном месте (} в качестве разделителя LHS)

perl = TRUE позволяет проверять утверждения. Они оценивают достоверность утверждений внутри них , не фиксируя их в шаблоне . Этот пост довольно хорошо их охватывает.

Вы получите ошибку в perl = FALSE, потому что TRE , механизм регулярных выражений по умолчанию для R, не поддерживает их.

  gsub3 <- function(pattern, replacement, x, escape = NULL, start_escape = NULL, end_escape = NULL) {
      if (!is.null(escape) || !is.null(start_escape)) 
      left_escape <- paste0("(?<![", paste0(escape, paste0(start_escape, collapse = ""), collapse = ""), "])")
      if (!is.null(escape) || !is.null(end_escape))
      right_escape <- paste0("(?![", paste0(escape, paste0(end_escape, collapse = ""), collapse = ""), "])")
      patt <- paste0(left_escape, "(", pattern, ")", right_escape)
      gsub(patt, replacement, x, perl = TRUE)
    }
    gsub3("banana", "potatoe", "'banana' banana \"banana\"", escape = "'\"")
    #> [1] "'banana' potatoe \"banana\""
    gsub3("banana", "potatoe", "'banana' apple \"banana\"", escape = '"\'')
    #> [1] "'banana' apple \"banana\""
    gsub3("banana", "potatoe", "{banana} banana {banana}", escape = "{}")
    #> [1] "{banana} potatoe {banana}"
    gsub3("banana", "potatoe", "{banana} apple {banana}", escape = "{}")
    #> [1] "{banana} apple {banana}"

Ниже grepl3 - обратите внимание, что для этого не нужно perl = TRUE, так как нам все равно, что захватывает образец, только то, что он соответствует.

grepl3 <- function(pattern, x, escape = "'", start_escape = NULL, end_escape = NULL) {
  if (!is.null(escape) || !is.null(start_escape)) 
  start_escape <- paste0("[^", paste0(escape, paste0(start_escape, collapse = ""), collapse = ""), "]")
  if (!is.null(escape) || !is.null(end_escape))
  end_escape <- paste0("[^", paste0(escape, paste0(end_escape, collapse = ""), collapse = ""), "]")
  patt <- paste0(start_escape, pattern, end_escape)
  grepl(patt, x)
}

grepl3("banana", "'banana' banana \"banana\"", escape =c('"', "'"))
#> [1] TRUE
grepl3("banana", "'banana' apple \"banana\"", escape =c('""', "''"))
#> [1] FALSE
grepl3("banana", "{banana} banana {banana}", escape = "{}")
#> [1] TRUE
grepl3("banana", "{banana} apple {banana}", escape = "{}")
#> [1] FALSE

Редактировать:

Это должно решить gsub без проблем, упомянутых Эндрю, если у вас все в порядке с одним набором парных операторов. Я думаю, что вы можете изменить его, чтобы разрешить несколько разделителей. Спасибо за увлекательную проблему, нашла новый драгоценный камень в regmatches!

gsub4 <-
  function(pattern,
           replacement,
           x,
           left_escape = "{",
           right_escape = "}") {
    # `regmatches()` takes a character vector and
    # output of `gregexpr` and friends and returns
    # the matching (or unmatching, as here) substrings
    string_pieces <-
      regmatches(x,
                 gregexpr(
                   paste0(
                     "\\Q",  # Begin quote, regex will treat everything after as fixed.
                     left_escape,
                     "\\E(?>[^", # \\ ends quotes.
                     left_escape,
                     right_escape,
                     "]|(?R))*", # Recurses, allowing nested escape characters
                     "\\Q",
                     right_escape,
                     "\\E",
                     collapse = ""
                   ),
                   x,
                   perl = TRUE
                 ), invert =NA) # even indices match pattern (so are escaped),
                                # odd indices we want to perform replacement on.
for (k in seq_along(string_pieces)) {
    n_pieces <- length(string_pieces[[k]])
  # Due to the structure of regmatches(invert = NA), we know that it will always
  # return unmatched strings at odd values, padding with "" as needed.
  to_replace <- seq(from = 1, to = n_pieces, by = 2)
  string_pieces[[k]][to_replace] <- gsub(pattern, replacement, string_pieces[[k]][to_replace])
}
    sapply(string_pieces, paste0, collapse = "")
  }
gsub4('banana', 'apples', "{banana's} potatoes {banana} banana", left_escape = "{", right_escape = "}")
#> [1] "{banana's} potatoes {banana} apples"
gsub4('banana', 'apples', "{banana's} potatoes {banana} banana", left_escape = "{", right_escape = "}")
#> [1] "{banana's} potatoes {banana} apples"
gsub4('banana', 'apples',  "banana's potatoes", left_escape = "{", right_escape = "}")
#> [1] "apples's potatoes"
gsub4('banana', 'apples', "{banana's} potatoes", left_escape = "{", right_escape = "}")
#> [1] "{banana's} potatoes"
3 голосов
/ 09 ноября 2019

Я попробовал свои силы в grepl2, но еще не было трещины (или мысли о чистом решении) до gsub2. В любом случае, это просто удаляет любые символы (кроме новых строк) между самыми короткими парами из предоставленных escaped символов. Это должно масштабироваться довольно хорошо, тоже. Если вы используете это решение, вы можете встроить проверку, чтобы убедиться, что есть пары из escaped символов без пробелов (или иным образом адаптироваться к использованию substr(). Надеюсь, что этопомогает!

grepl3 <- 
  function(pattern, x, ignore.case = FALSE, perl = FALSE, fixed = FALSE, 
           useBytes = FALSE, escaped =c('""', "''")){

    new_esc1 <- gsub("([][{}().+*^$|\\?])", "\\\\\\1", substr(escaped, 1, 1))
    new_esc2 <- gsub("([][{}().+*^$|\\?])", "\\\\\\1", substr(escaped, 2, 2))
    rm_pat <- paste0(new_esc1, ".*?", new_esc2, collapse = "|")
    new_arg <- gsub(rm_pat, "", arg)
    grepl(pattern, new_arg)

  }

grepl3(pattern = "banana", x = "'banana' apple \"banana\" {banana}", escaped =c("''", '""', "{}"))
[1] FALSE
1 голос
/ 09 ноября 2019

Мое мнение таково, что вам может понадобиться разделить открывающую и закрывающую скобки, чтобы код работал правильно. Здесь я использую функцию regex lookaround. Это может не работать универсально (особенно просмотр? <Оператор сопоставления) вне R. </p>

grepl2 = function(pattern, x, escapes = c(open="\"'{", close="\"'}")){
     grepl(paste0("(?<![", escapes[[1]], "])",
                  pattern, 
                  "(?![", escapes[[2]], "])"), 
           x, perl=T)
}
grepl2("banana", "'banana' banana \"banana\"")
#> [1] TRUE
grepl2("banana", "'banana' apple \"banana\"")
#> [1] FALSE
grepl2("banana", "{banana} banana {banana}")
#> [1] TRUE
grepl2("banana", "{banana} apple {banana}")
#> [1] FALSE
1 голос
/ 09 ноября 2019

Вот простое решение регулярного выражения с использованием оператора отрицания в классе символов. Это только удовлетворяет вашему простому случаю. Я не смог заставить его удовлетворить запрос парного множественного разделителя:

grepl2 <- function(patt, escape="'", arg=NULL) {
             grepl( patt=paste0("[^",escape,"]", 
                                patt,
                                "[^",escape,"]"), arg) }

grepl2("banana", "'banana' apple \"banana\"", escape =c( "'"))
#[1] TRUE

grepl2("banana", "'banana' apple ", escape =c( "'"))
[#1] FALSE
...