Предположим, в R у меня есть вектор типа:
vector<-c("Red", " ", "", "5", "")
Я хочу подсчитать, сколько элементов этого вектора представляют собой просто пустые строки, состоящие либо из пробелов, либо вообще без пробелов. Для этого очень короткого вектора их всего три. Второй, третий и пятый элементы — это просто пробелы или вообще не пробелы. В них нет никаких символов, таких как буквы, цифры, символы и т. д.
Есть ли какая-либо функция или метод, который это посчитает? Мне нужно было что-то, что можно было бы использовать для больших векторов, а не просто просматривать каждый элемент вектора.
Используйте sum(grepl())
плюс подходящее регулярное выражение:
vector<-c("Red", " ", "", "5", "")
sum(grepl("^ *$", vector))
^
: начало строки *
: ноль или более пробелов$
: конец строкиЕсли вы хотите искать «пробелы» в более общем смысле (например, разрешить табуляцию), используйте "^[[:space:]]*$"
, хотя, как указано в ?grep
, определение пробелов зависит от локали...
length(grep(...))
тоже подойдет, или stringr::str_count(vector, "^ *$")
.
Чего это стоит:
microbenchmark::microbenchmark(
bolker = sum(grepl("^ *$", vector)),
rudolph = sum(! nzchar(trimws(vector))),
baldur = sum(gsub(" ", "", vector, fixed = TRUE) == ""),
baldur2 = sum(! nzchar(gsub(" ", "", vector, fixed = TRUE))))
Unit: microseconds
expr min lq mean median uq max neval cld
bolker 10.499 10.8900 12.31869 11.8020 12.7990 40.976 100 a
rudolph 19.306 20.0125 22.01722 20.7990 22.9480 66.815 100 b
baldur 2.294 2.5700 2.76420 2.7455 2.8950 3.567 100 c
baldur2 2.294 2.4740 2.66267 2.6450 2.7755 5.130 100 c
(@RuiBarradas не включен, поскольку он похож на @KonradRudolph). Я удивлен, что ответ @s_baldur такой быстрый... но также, вероятно, стоит иметь в виду, что эта операция будет достаточно быстрой, чтобы не беспокоиться об эффективности, если только она не составляет большую часть вашего общего рабочего процесса...
fixed = TRUE
значительно увеличивает скорость
Я согласен, но я просто удивлен, что gsub()
по-прежнему быстрее, чем trimws()
, или что изменение строки происходит быстрее, чем сканирование ее на наличие регулярных выражений...
когда вы прочитаете исходный код trimws
, вы увидите, что его ключевой код состоит из sub
, НО, paste0
применяется для генерации шаблона регулярного выражения, что замедляет производительность
sauce <- c(" Red", " ", " a", " 5", "", " fdsfd", " ff") ; vector <- sapply(1:100000, function (x) sauce[x %% length(sauce) + 1])
## и болкер самый быстрый... Сильно зависит от набора данных
К вашему сведению, я добавил еще несколько тестов с векторами различной длины и всеми ответами. Подход, который является самым быстрым с коротким вектором, не является самым быстрым с более длинными (хотя все же относительно быстрым). Ваш ответ использует меньше всего памяти.
trimws
удаляет все пробелы. Тогда nchar
получит количество символов. Сравнивайте с нулем и считайте только те. Но это примерно в 3 раза медленнее, чем ответ Бена Болкера.
v <- c("Red", " ", "", "5", "")
sum(nchar(trimws(v)) == 0)
#> [1] 3
Created on 2024-06-30 with reprex v2.1.0
Судя по комментарию, который сейчас удален,
sum(!nzchar(trimws(v)))
#> [1] 3
Created on 2024-06-30 with reprex v2.1.0
@BenBolker Хорошая идея, отредактирую с вариацией.
Я бы использовал nzchar() в сочетании с trimws()
(хотя двойное отрицание !nzchar()
делает это немного неудобным для чтения):
sum(! nzchar(trimws(vector)))
# [1] 3
Еще один вариант:
sum(gsub(" ", "", vector, fixed = TRUE) == "")
И краткая вариация предыдущего ответа:
sum(trimws(vector) == "")
Наконец... раз уж мы развлекаемся с тестами. Есть возможности для улучшения по сравнению с базой R, как показал SamR с использованием stringi. Вот еще один пример использования Rcpp, где мы избегаем изменения вектора и проверяем посимвольно до первого символа, не являющегося пробелом.
cppFunction("int count_empty_cpp(CharacterVector x) {
int count = 0, j, n;
std::string v;
for (int i = 0; i < x.size(); i++) {
v = Rcpp::as<std::string>(x[i]);
n = v.length();
j = 0;
while (j < n && v[j] == ' ') j++;
if (j == n) count++;
}
return count;
}")
У вас есть несколько отличных ответов по базе R. Я заметил, что вы отметили stringr
. Я не думаю, что здесь есть какое-то преимущество в использовании stringr
. Однако можно использовать пакет stringi THE R для быстрой, переносимой, правильной, последовательной и удобной обработки строк/текста в любой локали или кодировке символов.
stringi
имеет тенденцию быть очень быстрым. stringr
зависит от stringi
(и на самом деле многие stringr
функции представляют собой тонкие оболочки для stringi
функций), поэтому если у вас установлена stringr
, то у вас также есть stringi
.
В отличие от stringr
, stringi
имеет функцию проверки пустых строк (эквивалент !base::nzchar()
), которая, вероятно, быстрее, чем сравнение строк, и почти наверняка быстрее, чем подсчет символов всех строк (включая непустые).
library(stringi)
sum(stri_isempty(stri_trim_both(vector)))
# [1] 3
Как показывает ответ С. Балдура, вы также можете использовать Rcpp
для этого.
Это настолько быстрее, что я собираюсь включить его в отдельный раздел тестов ниже, чтобы было легче увидеть различия в подходах на чистом R.
Просто ради интереса я провел несколько тестов с векторами длиной до 1 метра. Второй подход С. Балдура является самым быстрым для векторов длиной 10 и 100. Для векторов длиной 1000 и выше подход stringi
является самым быстрым.
Если оперативная память является важным фактором, ответ Бена Болкера постоянно использует наименьшее количество памяти. Вот данные в табличной форме (обратите внимание, что тайминги являются относительными, и самый быстрый/самый низкий подход к памяти всегда равен 1
).
expression vec_length min median `itr/sec` mem_alloc `gc/sec` n_itr n_gc total_time
<bch:expr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <int> <dbl> <bch:tm>
1 bolker 10 1.95 1.65 4.91 NaN Inf 9999 1 161ms
2 rudolph 10 5.64 7.11 1 NaN Inf 6099 2 483ms
3 baldur 10 1.09 1 7.13 NaN Inf 9999 1 111ms
4 baldur2 10 1.05 1 6.91 NaN NaN 10000 0 115ms
5 thomas 10 1.55 1.92 4.11 NaN Inf 9999 1 193ms
6 rui 10 5.82 7.38 1.18 NaN Inf 7046 2 472ms
7 rui2 10 5.55 6.95 1.23 NaN Inf 7373 3 475ms
8 samr 10 1 1.27 6.72 NaN NaN 10000 0 118ms
9 bolker 100 1.56 1.57 4.30 1 Inf 9999 1 320ms
10 rudolph 100 6.83 6.53 1.10 5.79 Inf 3892 1 487ms
11 baldur 100 1.03 1.04 6.64 2.89 Inf 9999 1 207ms
12 baldur2 100 1 1 6.87 3.89 Inf 9999 1 200ms
13 thomas 100 1.64 1.58 4.19 2 NaN 10000 0 328ms
14 rui 100 7 7.07 1 5.79 Inf 3546 1 488ms
15 rui2 100 6.69 6.5 1.12 5.79 Inf 3904 2 482ms
16 samr 100 1.19 1.05 6.38 2.89 NaN 10000 0 216ms
17 bolker 1000 1.31 1.64 4.09 1 Inf 3025 1 487ms
18 rudolph 1000 5.43 5.97 1.10 5.98 NaN 830 0 499ms
19 baldur 1000 1.06 1.23 5.13 2.99 Inf 3109 1 399ms
20 baldur2 1000 1 1.18 5.57 3.99 NaN 4186 0 495ms
21 thomas 1000 1.17 1.41 4.89 2 NaN 3685 0 496ms
22 rui 1000 7.32 6.58 1 5.98 NaN 758 0 499ms
23 rui2 1000 7.17 5.85 1.12 5.98 NaN 853 0 500ms
24 samr 1000 1.05 1 6.01 2.99 Inf 4439 1 486ms
25 bolker 10000 1.77 1.61 4.39 1 NaN 355 0 501ms
26 rudolph 10000 8.56 6.18 1.13 6.00 NaN 92 0 502ms
27 baldur 10000 1.06 1.26 5.56 3.00 Inf 443 1 492ms
28 baldur2 10000 1 1.21 5.69 4.00 NaN 460 0 500ms
29 thomas 10000 1.57 1.44 4.81 2 Inf 388 1 499ms
30 rui 10000 8.28 6.89 1 6.00 NaN 81 0 501ms
31 rui2 10000 8.69 6.19 1.13 6.00 NaN 92 0 505ms
32 samr 10000 1.23 1 6.69 3.00 Inf 533 1 493ms
33 bolker 100000 1.92 1.58 4.21 1 NaN 36 0 510ms
34 rudolph 100000 7.83 6.31 1.07 6.00 NaN 9 0 504ms
35 baldur 100000 1.37 1.31 5.08 3.00 Inf 42 1 493ms
36 baldur2 100000 1.45 1.37 4.96 4.00 Inf 41 1 493ms
37 thomas 100000 1.52 1.37 4.89 2 NaN 41 0 500ms
38 rui 100000 7.46 6.60 1 6.00 NaN 9 0 537ms
39 rui2 100000 6.93 6.05 1.11 6.00 Inf 9 1 483ms
40 samr 100000 1 1 6.56 3.00 NaN 55 0 500ms
41 bolker 1000000 1.81 1.79 4.39 1 NaN 4 0 551ms
42 rudolph 1000000 7.76 7.13 1.09 6.00 NaN 1 0 553ms
43 baldur 1000000 1.48 1.44 5.42 3.00 Inf 4 1 447ms
44 baldur2 1000000 1.52 1.44 5.44 4.00 Inf 4 1 445ms
45 thomas 1000000 1.64 1.53 5.04 2 Inf 4 1 480ms
46 rui 1000000 8.50 7.80 1 6.00 NaN 1 0 605ms
47 rui2 1000000 7.59 6.97 1.12 6.00 NaN 1 0 541ms
48 samr 1000000 1 1 7.88 3.00 Inf 6 1 460ms
results <- bench::press(
vec_length = 10^(1:6),
{
vals <- c("Red", "", " ", " ", " ", " ", "5", letters[1:3])
v <- sample(vals, vec_length, replace = TRUE)
bench::mark(
relative = TRUE,
bolker = sum(grepl("^ *$", v)),
rudolph = sum(!nzchar(trimws(v))),
baldur = sum(gsub(" ", "", v, fixed = TRUE) == ""),
baldur2 = sum(!nzchar(gsub(" ", "", v, fixed = TRUE))),
thomas = sum(!grepl("\\S", v)),
rui = sum(nchar(trimws(v)) == 0),
rui2 = sum(!nzchar(trimws(v))),
samr = sum(stri_isempty(stri_trim_both(v)))
)
}
)
Rcpp
тестыОтдельно включаю тест функции С. Балдура count_empty_cpp()
. Это намного быстрее, чем мой подход stringi
, поэтому я добавил еще одну функцию Rcpp
, используя стандартную библиотеку C++, во многом основываясь на ответе на вопрос C++: Эффективный способ проверить, содержит ли std::string только пробелы.
Rcpp::cppFunction("int count_empty_cpp2(CharacterVector x) {
int count = 0, j, n;
std::string str;
for (int i = 0; i < x.size(); i++) {
str = Rcpp::as<std::string>(x[i]);
if (str.find_first_not_of(' ') == std::string::npos)
{
count++;
}
}
return count;
}")
Я также добавил третью Rcpp
функцию, которая смотрит на основное S-выражение каждого элемента вектора символов. Это означает, что мы можем избежать приведения типов в тех случаях, когда строка пуста. Кроме того, когда нам нужно просмотреть содержимое строки, я использую CHAR()
для приведения SEXP
к указателю в стиле C на строку с нулевым завершением (const char*
), а не к C++ std::string
. Это означает, что мы копируем ссылку (вероятно, 8 байт на строку), а не данные.
Rcpp::cppFunction("int count_empty_cpp3(CharacterVector x) {
int count = 0;
for (int i = 0; i < x.size(); i++) {
SEXP elem = x[i];
R_xlen_t len = Rf_length(elem);
if (len == 0) {
count++;
} else {
const char* str = CHAR(elem);
bool is_empty = true;
for (R_xlen_t j = 0; j < len; j++) {
if (str[j] != ' ') {
is_empty = false;
break;
}
}
if (is_empty) count++;
}
}
return count;
}")
Я сравнил их с двумя самыми быстрыми ответами R. Все подходы Rcpp
работают намного быстрее, чем самые быстрые подходы R, если длина векторов равна >1e4
.
Вот таблица результатов. Между первыми двумя Rcpp
подходами очень мало различий. Подход с избеганием std::string
немного быстрее, чем два других:
expression vec_length min median `itr/sec` mem_alloc `gc/sec` n_itr n_gc total_time
<bch:expr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <int> <dbl> <bch:tm>
1 baldur2_baser 10 1.56 1.55 1.61 NaN Inf 9999 1 84.46ms
2 samr_stringi 10 2.44 2.18 1 NaN NaN 10000 0 135.71ms
3 baldercpp 10 1.11 1.05 2.22 Inf NaN 10000 0 61.11ms
4 samrcpp 10 1.11 1.09 1.95 Inf Inf 9999 1 69.62ms
5 samrcpp2 10 1 1 2.10 Inf Inf 9999 1 64.57ms
6 baldur2_baser 100 2.64 2.57 1 1.35 NaN 10000 0 200.12ms
7 samr_stringi 100 3 2.57 1.01 1 Inf 9999 1 198.57ms
8 baldercpp 100 1.14 1.03 2.33 1.97 NaN 10000 0 85.74ms
9 samrcpp 100 1.14 1.1 1.90 1.97 Inf 9999 1 105.58ms
10 samrcpp2 100 1 1 2.23 1.97 Inf 9999 1 89.85ms
11 baldur2_baser 1000 3.67 4.51 1 6.33 Inf 4408 2 481.84ms
12 samr_stringi 1000 3.64 3.93 1.03 4.74 Inf 4591 1 488.59ms
13 baldercpp 1000 1.45 1.41 2.77 1 Inf 9999 1 395.14ms
14 samrcpp 1000 1.55 1.49 2.58 1 Inf 9999 1 423.62ms
15 samrcpp2 1000 1 1 3.92 1 NaN 10000 0 278.97ms
16 baldur2_baser 10000 4.06 5.29 1 62.8 Inf 465 3 480.15ms
17 samr_stringi 10000 3.73 4.26 1.16 47.1 Inf 555 1 494.04ms
18 baldercpp 10000 1.52 1.56 2.99 1 NaN 1444 0 498.64ms
19 samrcpp 10000 1.58 1.64 3.05 1 Inf 1457 1 492.9ms
20 samrcpp2 10000 1 1 4.79 1 NaN 2305 0 496.85ms
21 baldur2_baser 100000 5.35 5.16 1 627. Inf 48 2 529.89ms
22 samr_stringi 100000 4.61 4.08 1.23 470. Inf 54 2 484.1ms
23 baldercpp 100000 1.51 1.53 3.34 1 NaN 152 0 501.8ms
24 samrcpp 100000 1.57 1.57 3.31 1 NaN 150 0 500.94ms
25 samrcpp2 100000 1 1 4.89 1 NaN 222 0 501.5ms
26 baldur2_baser 1000000 4.46 5.04 1 6270. Inf 27 23 2.89s
27 samr_stringi 1000000 3.94 3.89 1.27 4702. Inf 37 13 3.11s
28 baldercpp 1000000 1.30 1.41 3.50 1 NaN 50 0 1.53s
29 samrcpp 1000000 1.45 1.53 3.15 1 NaN 50 0 1.69s
30 samrcpp2 1000000 1 1 4.65 1 NaN 50 0 1.15s
Это относительно короткие строки. Я не собираюсь проводить больше тестов, но подозреваю, что если бы строки были длиннее, мы увидели бы больше относительных преимуществ от копирования указателя, а не данных.
Эти тесты в основном предназначены для развлечения. Различия между всеми ответами относительно невелики, поэтому, если вы не повторяете это много раз с огромными векторами, не имеете чрезвычайно длинные строки или очень ограниченные ресурсы памяти, вместо того, чтобы развертывать собственное решение Rcpp, которое работает на наносекунды быстрее, я бы оптимизировал читаемый код. .
Спасибо за ответ! Я не знал о стринги, поэтому спасибо, что описали это здесь!
этот бенчмаркинг впечатляет
Вероятно, мы уже превзошли ожидания OP, но я добавил еще одно решение, если вам хочется обновить тест.
@SamR Спасибо. Я все еще полный новичок в Rcpp. Было бы очень интересно посмотреть, сможет ли кто-нибудь еще оптимизировать код.
@s_baldur Я уверен, что всем это безразлично, но я добавил подход Rcpp, который использует базовый SEXP
, а не преобразование типов в std::string
. Это немного быстрее, чем два других подхода. Интересно, что на этот раз ваш метод Rcpp работает немного быстрее, чем мой первый, поэтому в прошлый раз я, возможно, немного выиграл от стохастической природы этих тестов.
Удивительный! Очень полезно. Спасибо. Теперь пришло время изучить распараллеливание (шутка).
@s_baldur 😂 - хотя на самом деле делать это параллельно почти наверняка будет медленнее, поскольку накладные расходы на копирование данных будут слишком высокими
Для игры в гольф, возможно, вас это заинтересует
> sum(!grepl("\\S", vector))
[1] 3
Интересно, что я вижу, что в более длинных векторах perl = TRUE
работает быстрее.
Вот несколько stringr
решений:
library(stringr)
sum(word(vector) == "")
# [1] 3
sum(!str_count(vector, boundary("word")))
# [1] 3
«Я хочу подсчитать, сколько элементов этого вектора представляют собой просто пустые строки, состоящие либо из пробелов, либо вообще без пробелов. Для этого очень короткого вектора их всего три». На самом деле это 5. «Красный» и «5» вообще не имеют пробелов, поэтому они подходят. Может быть, вы имеете в виду «состоять либо только из пробелов, либо из пустых строк нулевой длины»?