Аналог setdiff() с использованием регулярных выражений

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

value <- c("apple pie", "cat", "dog", "dogmatic", "no apples")
re_setdiff(value, c("^apple", "^dog"))
## desired results:
value[c(2,5)]
[1] "cat"       "no apples"

Я знаю, как я могу закодировать это с помощью грубой силы (см. мой ответ), но мне интересно, есть ли более эффективный/более идиоматический способ сделать это (может быть, что-то в stringi/stringr?) или что-то, что уже есть (широко используется) упаковка?

Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
5
0
88
4
Перейти к ответу Данный вопрос помечен как решенный

Ответы 4

Вот решение методом грубой силы:

re_setdiff <- function(x, y, ...) {
   for (yy in y) {
      x <- grep(yy, x, invert = TRUE, value = TRUE, ...)
   }
   return(x)
}

Я оставил там ... на случай, если кто-то захочет уточнить, например. perl=TRUE. (Думаю, это можно было бы сделать более компактно/незаметно с помощью Reduce() ... ?)

Я думаю, что ваше динамическое обновление for-цикла достаточно хорошее (+1), см. мой тест. Но если вас волнует скорость, необходимы дополнительные исследования, поскольку она действительно зависит от мощности наборов value и exclude.

ThomasIsCoding 30.07.2024 10:18

Я провел еще один тест на более крупном векторе. Кажется, что объединение регулярных выражений с помощью | (как это сделал JoFrhwld) — отличный выбор для этого случая.

Darren Tsai 31.07.2024 04:36

С помощью stringr вы можете использовать str_subset().

value <- c("apple pie", "cat", "dog", "dogmatic", "no apples")

exclude <- c("^apple", "^dog")

str_subset(
  value,
  # concat into 1 regex
  pattern = str_c(exclude, collapse = "|"),
  negate = TRUE
)

#> [1] "cat"       "no apples"

Интересный вопрос, в чем разница в эффективности/масштабировании между последовательной обработкой и объединением регулярного выражения с помощью |...

Ben Bolker 29.07.2024 23:44

Вариант последовательного исключения — использовать sapply с grepl.

value[!rowSums(sapply(exclude, grepl, value))]

# [1] "cat"       "no apples"

Другой вариант — использовать рекурсию:

recur_diff <- function(x, pattern) {
  if (length(pattern) == 0) return(x)
  recur_diff(grep(pattern[1], x, invert = TRUE, value = TRUE), pattern[-1])
}

recur_diff(value, exclude)
# [1] "cat"       "no apples"

Контрольный показатель

Я провел тест с более крупным вектором и большим количеством шаблонов.

N <- 5000
M <- 50

value <- replicate(N, paste(sample(LETTERS, 10), collapse = ''))
exclude <- replicate(M, paste(sample(LETTERS, 3), collapse = ''))
microbenchmark::microbenchmark(
  jofrhwld = jofrhwld(value, exclude),
  tic = tic(value, exclude),
  darren_sapply = darrentsai(value, exclude),
  darren_recur = recur_diff(value, exclude),
  benbolker = benbolker(value, exclude),
  unit = "relative",
  check = "equal"
)

Unit: relative
          expr      min       lq     mean   median       uq      max neval cld
      jofrhwld 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000   100  a 
           tic 3.342135 3.321231 3.339698 3.320279 3.309806 3.481526   100   b
 darren_sapply 3.356324 3.343180 3.356167 3.345018 3.328611 3.592255   100   b
  darren_recur 3.362487 3.343806 3.361479 3.346198 3.336437 3.462555   100   b
     benbolker 3.357269 3.336147 3.358892 3.340248 3.322553 3.506824   100   b

Спасибо за интересный бенчмаркинг! Да, скорость действительно зависит от размеров двух комплектов.

ThomasIsCoding 31.07.2024 08:27
Ответ принят как подходящий

Вы правы, это можно сделать Reduce

> value <- c("apple pie", "cat", "dog", "dogmatic", "no apples")

> exclude <- c("^apple", "^dog")

> Reduce(\(x, y) grep(y, x, value = TRUE, invert = TRUE), exclude, value)
[1] "cat"       "no apples"

Бенчмаркинг

jofrhwld <- \(value, exclude) {
    str_subset(
        value,
        # concat into 1 regex
        pattern = str_c(exclude, collapse = "|"),
        negate = TRUE
    )
}

tic <- \(value, exclude) {
    Reduce(\(x, y) grep(y, x, value = TRUE, invert = TRUE), exclude, value)
}

darrentsai <- \(value, exclude) {
    value[!rowSums(sapply(exclude, grepl, value))]
}

benbolker <- function(x, y, ...) {
    for (yy in y) {
        x <- grep(yy, x, invert = TRUE, value = TRUE, ...)
    }
    return(x)
}

microbenchmark(
    jofrhwld = jofrhwld(value, exclude),
    tic = tic(value, exclude),
    darrentsai = darrentsai(value, exclude),
    benbolker = benbolker(value, exclude),
    unit = "relative",
    check = "equal"
)

шоу

Unit: relative
       expr      min       lq     mean   median       uq      max neval
   jofrhwld 3.298173 3.365282 4.302031 3.267003 4.027422 11.35689   100
        tic 1.326923 1.334713 3.154308 1.366346 1.429391 14.58037   100
 darrentsai 2.548269 2.761838 3.840466 2.606786 2.723790 17.21892   100
  benbolker 1.000000 1.000000 1.000000 1.000000 1.000000  1.00000   100

Спасибо, раньше не знала о unit = "relative"...

Ben Bolker 30.07.2024 15:33

Привет, вдохновленный вашим тестом, я провел еще один тест на более крупном векторе. Кажется, что объединение регулярных выражений через | (как это сделал JoFrhwld) — лучший подход для этого случая.

Darren Tsai 31.07.2024 04:30

Другие вопросы по теме