Привет,
Проблема
Во-первых, позвольте мне попытаться проиллюстрировать проблему. Предположим, я хочу применить следующий шифр для кодирования строки: "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
, но на данном этапе я бы рассмотрел и другие методы.
Поскольку вы реализуете шифр, я бы избегал регулярных выражений в пользу базовой функции 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"
@rinkjames, ради интереса, кажется, он заметно отличается по скорости от твоего острота? Я могу придумать причины, по которым это может быть как быстрее, так и медленнее.
Быстрый тест показал, что ваше базовое решение R (Mdn = 105 мкс) примерно в 2,4 раза быстрее, чем решение tidyverse (Mdn = 256 мкс). Хотя это незаметно. Оба решения заметно быстрее моего исходного решения (Mdn = 14 мс).
К моему смущению, вскоре после публикации я обнаружил довольно элегантное решение этой проблемы. Интересно, почему это не всплыло при первом поиске в 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"
Большое спасибо за этот ответ. Я раньше не слышал о
chartr
. Я поэкспериментирую с вашей функцией и альтернативойtidyverse
, которую я обнаружил, как в обновленном посте.