Р: Эффективный способ str_replace_all без рекурсивной замены конфликтующих замен?

Привет,

Проблема
Во-первых, позвольте мне попытаться проиллюстрировать проблему. Предположим, я хочу применить следующий шифр для кодирования строки: "abc".

library(tidyverse)

cipher <- tibble(
  byte = c(128:153, 160:185, 246:255) %>% as.hexmode() %>% str_to_upper(),
  char = c(LETTERS, letters, 0:9)
)

"abc" %>% str_replace_all(set_names(cipher$byte, cipher$char))
# [1] "AFFCAFFDAFFE"

Мне нужен результат "A0A1A2", а не "AFFCAFFDAFFE". Похоже, что 0 при первой замене A0 заменяется собственной заменой, то есть FF, и так далее. Именно это я имею в виду под рекурсивной заменой конфликтующих замен.

Сопутствующая информация
Я прочитала этот пост . Я тоже читала этот выпуск . Я также изучил аргумент vectorize_all функции stri_replace_all*.

Рабочее (но неэффективное) решение
Единственный способ, которым мне удалось успешно выполнить несколько замен строк с замещающими значениями, которые в противном случае были бы конфликтующими, — это разделить каждый строковый символ на символ, затем выполнить замены и, наконец, вставить все это обратно вместе. Вот так:

library(tidyverse)

c("abc", "123") %>%
  map_chr(\(string) {
    str_split_1(string, "") %>%
      map_chr(\(char) {
        str_replace_all(char, set_names(cipher$byte, paste0("^", cipher$char, "$")))
      }) %>% paste(collapse = "")
  })
# [1] "A0A1A2" "F7F8F9"

К сожалению, этот способ кодирования строк занимает много времени (по крайней мере, на моем Intel Macbook Pro 2020 года) для больших векторов. Я предпочитаю работать в tidyverse, но на данном этапе я бы рассмотрел и другие методы.

Стоит ли изучать 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 называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
3
0
94
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

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

Поскольку вы реализуете шифр, я бы избегал регулярных выражений в пользу базовой функции chartr() :

chartr переводит каждый символ x, указанный в old, в соответствующий символ, указанный в new. В спецификациях поддерживаются диапазоны, а вот классы символов и повторяющиеся символы — нет.

Небольшой недостаток в этом плане заключается в том, что chartr() выполняет только замену символов один на один. Вы хотите заменить "a" на "A0" и так далее. Однако все ваши две замены символов могут быть выражены в шестнадцатеричном виде как один символ Юникода.

Мы можем написать функцию для преобразования каждой вашей замены одного символа в один символ Юникода, чтобы мы могли использовать его в chartr(), а затем обратно. Например, "A0" становится intToUtf8("0xA0"), то есть пробелом, который затем передается в chartr() как односимвольная замена "a", а затем в конце преобразуется обратно в "A0":

do_cipher <- function(x, old = cipher$char, new = cipher$byte) {
    chartr(
        paste0(old, collapse = ""),
        intToUtf8(paste0("0x", new)),
        x
    ) |>
        vapply(
            \(x)
            x |>
                utf8ToInt() |>
                as.hexmode() |>
                paste0(collapse = "") |>
                toupper(),
            character(1)
        ) |>
        setNames(x)
}

Это возвращает желаемый результат:

c("abc", "123") |> do_cipher()
#      abc      123
# "A0A1A2" "F7F8F9"

Большое спасибо за этот ответ. Я раньше не слышал о chartr. Я поэкспериментирую с вашей функцией и альтернативой tidyverse, которую я обнаружил, как в обновленном посте.

rinkjames 21.07.2024 12:54

@rinkjames, ради интереса, кажется, он заметно отличается по скорости от твоего острота? Я могу придумать причины, по которым это может быть как быстрее, так и медленнее.

SamR 21.07.2024 13:17

Быстрый тест показал, что ваше базовое решение R (Mdn = 105 мкс) примерно в 2,4 раза быстрее, чем решение tidyverse (Mdn = 256 мкс). Хотя это незаметно. Оба решения заметно быстрее моего исходного решения (Mdn = 14 мс).

rinkjames 21.07.2024 13:31

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

Решение простое:

library(tidyverse)

cipher <- tibble(
  byte = c(128:153, 160:185, 246:255) %>% as.hexmode() %>% str_to_upper(),
  char = c(LETTERS, letters, 0:9)
)

c("abc", "123") %>% str_replace_all(".", ~ set_names(cipher$byte, cipher$char)[.x])
# [1] "A0A1A2" "F7F8F9"

Эта единственная строка кода работает более чем в 50 раз быстрее, чем первоначальный способ, которым я добивался этого. Базовое решение SamR R работает еще быстрее, более чем в 100 раз.

Кроме того, вот потенциально полезное расширение решения tidyverse, которое также включает замены многосимвольных строк.

library(tidyverse)

cipher <- tibble(
  byte = c(20:22, 128:153, 160:185, 246:255) %>% as.hexmode() %>% str_to_upper(),
  char = c("<multi>", "<char>", "<strings>", LETTERS, letters, 0:9)
)

c("<multi>", "<strings>") %>% str_replace_all("(<.+?>)|.", ~ set_names(cipher$byte, cipher$char)[.x])
# [1] "14" "16"

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