Посчитать пустые строки?

Предположим, в R у меня есть вектор типа:

vector<-c("Red", "   ", "", "5", "")

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

Есть ли какая-либо функция или метод, который это посчитает? Мне нужно было что-то, что можно было бы использовать для больших векторов, а не просто просматривать каждый элемент вектора.

«Я хочу подсчитать, сколько элементов этого вектора представляют собой просто пустые строки, состоящие либо из пробелов, либо вообще без пробелов. Для этого очень короткого вектора их всего три». На самом деле это 5. «Красный» и «5» вообще не имеют пробелов, поэтому они подходят. Может быть, вы имеете в виду «состоять либо только из пробелов, либо из пустых строк нулевой длины»?

TylerH 25.07.2024 18:14
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
6
1
342
7
Перейти к ответу Данный вопрос помечен как решенный

Ответы 7

Ответ принят как подходящий

Используйте 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 значительно увеличивает скорость
ThomasIsCoding 30.06.2024 22:59

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

Ben Bolker 30.06.2024 23:01

когда вы прочитаете исходный код trimws, вы увидите, что его ключевой код состоит из sub, НО, paste0 применяется для генерации шаблона регулярного выражения, что замедляет производительность

ThomasIsCoding 30.06.2024 23:04
sauce <- c(" Red", " ", " a", " 5", "", " fdsfd", " ff") ; vector <- sapply(1:100000, function (x) sauce[x %% length(sauce) + 1]) ## и болкер самый быстрый... Сильно зависит от набора данных
Sophie 01.07.2024 01:39

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

SamR 01.07.2024 10:18

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 Хорошая идея, отредактирую с вариацией.

Rui Barradas 30.06.2024 22:08

Я бы использовал 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 подход

У вас есть несколько отличных ответов по базе 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 подходы

Как показывает ответ С. Балдура, вы также можете использовать Rcpp для этого.

Это настолько быстрее, что я собираюсь включить его в отдельный раздел тестов ниже, чтобы было легче увидеть различия в подходах на чистом R.

Тесты чистого 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, которое работает на наносекунды быстрее, я бы оптимизировал читаемый код. .

Спасибо за ответ! Я не знал о стринги, поэтому спасибо, что описали это здесь!

James Rider 01.07.2024 00:43

этот бенчмаркинг впечатляет

ThomasIsCoding 01.07.2024 10:48

Вероятно, мы уже превзошли ожидания OP, но я добавил еще одно решение, если вам хочется обновить тест.

s_baldur 01.07.2024 13:10

@SamR Спасибо. Я все еще полный новичок в Rcpp. Было бы очень интересно посмотреть, сможет ли кто-нибудь еще оптимизировать код.

s_baldur 01.07.2024 13:24

@s_baldur Я уверен, что всем это безразлично, но я добавил подход Rcpp, который использует базовый SEXP, а не преобразование типов в std::string. Это немного быстрее, чем два других подхода. Интересно, что на этот раз ваш метод Rcpp работает немного быстрее, чем мой первый, поэтому в прошлый раз я, возможно, немного выиграл от стохастической природы этих тестов.

SamR 01.07.2024 15:19

Удивительный! Очень полезно. Спасибо. Теперь пришло время изучить распараллеливание (шутка).

s_baldur 01.07.2024 15:29

@s_baldur 😂 - хотя на самом деле делать это параллельно почти наверняка будет медленнее, поскольку накладные расходы на копирование данных будут слишком высокими

SamR 01.07.2024 15:48

Для игры в гольф, возможно, вас это заинтересует

> sum(!grepl("\\S", vector))
[1] 3

Интересно, что я вижу, что в более длинных векторах perl = TRUE работает быстрее.

LMc 02.07.2024 21:43

Вот несколько stringr решений:

library(stringr)

sum(word(vector) == "")
# [1] 3

sum(!str_count(vector, boundary("word")))
# [1] 3

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