Предположим, я хочу исключить значения, соответствующие серии регулярных выражений, из вектора символов точно так же, как я бы использовал 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?) или что-то, что уже есть (широко используется) упаковка?





Вот решение методом грубой силы:
re_setdiff <- function(x, y, ...) {
for (yy in y) {
x <- grep(yy, x, invert = TRUE, value = TRUE, ...)
}
return(x)
}
Я оставил там ... на случай, если кто-то захочет уточнить, например. perl=TRUE. (Думаю, это можно было бы сделать более компактно/незаметно с помощью Reduce() ... ?)
Я провел еще один тест на более крупном векторе. Кажется, что объединение регулярных выражений с помощью | (как это сделал JoFrhwld) — отличный выбор для этого случая.
С помощью 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"
Интересный вопрос, в чем разница в эффективности/масштабировании между последовательной обработкой и объединением регулярного выражения с помощью |...
Вариант последовательного исключения — использовать 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
Спасибо за интересный бенчмаркинг! Да, скорость действительно зависит от размеров двух комплектов.
Вы правы, это можно сделать 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"...
Привет, вдохновленный вашим тестом, я провел еще один тест на более крупном векторе. Кажется, что объединение регулярных выражений через | (как это сделал JoFrhwld) — лучший подход для этого случая.
Я думаю, что ваше динамическое обновление
for-цикла достаточно хорошее (+1), см. мой тест. Но если вас волнует скорость, необходимы дополнительные исследования, поскольку она действительно зависит от мощности наборовvalueиexclude.