str_replace_all 不是一次全部迭代地替换命名的向量元素

str_replace_all replacing named vector elements iteratively not all at once

假设我有一个长字符串:pneumonoultramicroscopicsilicovolcanoconiosis。我想使用 stringr::str_replace_all 将某些字母替换为其他字母。根据文档,str_replace_all 可以采用命名向量并将名称替换为值。这对于 1 次替换工作正常,但对于多次替换它似乎是迭代进行的,所以结果是替换了 prelast 迭代。我不确定这是预期的行为。

library(tidyverse)
text_string = "developer"
text_string %>% 
  str_replace_all(c(e ="X")) #this works fine
[1] "dXvXlopXr"
text_string %>% 
  str_replace_all(c(e ="p", p = "e")) #not intended behaviour
[1] "develoeer"

想要的结果:

[1] "dpvploepr"

这是我通过引入一个新角色得到的:

text_string %>% 
  str_replace_all(c(e ="X", p = "e", X = "p"))

这是一个可用的解决方法,但很难推广。这是一个错误还是我的期望错误?

我还希望能够同时用 n 其他字母替换 n 字母,最好使用两个向量(例如"old" 和 "new") 或命名向量作为输入。

reprex 已编辑以便于人类阅读

函数的作用可能是有顺序的,所以在用s替换所有c之后,你用c替换所有s,只剩下c.. 试试这个:

long_string %>% str_replace_all(c(c ="X", s = "U"))  %>% str_replace_all(c(X ="s", U = "c"))

我的解决方法是利用 str_replace_all 可以将函数作为替换输入的事实。

library(stringr)
text_string = "developer"
pattern <- "p|e"
fun <- function(query) {
    if(query == "e") y <- "p"
    if(query == "p") y <- "e"
    return(y)
}

str_replace_all(text_string, pattern, fun)

当然,如果你需要放大,我建议使用更复杂的功能。

迭代行为是有意的。也就是说,我们可以使用自己的解决方法。我将使用 character subsetting 进行替换。

在命名向量中,我们可以按名称查找事物并为每个名称获取替换值。这就像同时进行所有替换。

rules <- c(a = "X", b = "Y", X = "a")
chars <- c("a", "a", "b", "X", "X")
rules[chars]
#>   a   a   b   X   X 
#> "X" "X" "Y" "a" "a"

所以在这里,在 rules 向量中查找 "a" 得到我们 "X",有效地将 "a" 替换为 "X"。其他角色也是如此。

一个问题是没有匹配的名称会产生 NA

rules <- c(a = "X", b = "Y", X = "a")
chars <- c("a", "Y", "Z")
rules[chars]
#>    a <NA> <NA> 
#>  "X"   NA   NA

为了防止出现 NA,我们可以扩展规则以包含任何新字符,这样一个字符就会被自身替换。

rules <- c(a = "X", b = "Y", X = "a")
chars <- c("a", "Y", "Z")
no_rule <- chars[! chars %in% names(rules)]
rules2 <- c(rules, setNames(no_rule, no_rule))
rules2[chars]
#>   a   Y   Z 
#> "X" "Y" "Z"

这就是以下函数背后的逻辑。

  • 将字符串分解为字符
  • 创建替换规则的完整列表
  • 查找替换值
  • 将字符串粘在一起
library(stringr)

str_replace_chars <- function(string, rules) {
  # Expand rules to replace characters with themselves 
  # if those characters do not have a replacement rule
  chars <- unique(unlist(strsplit(string, "")))
  complete_rules <- setNames(chars, chars)
  complete_rules[names(rules)] <- rules

  # Split each string into characters, replace and unsplit
  for (string_i in seq_along(string)) {
    chars_i <- unlist(strsplit(string[string_i], ""))
    string[string_i] <- paste0(complete_rules[chars_i], collapse = "")
  }
  string
}

rules <- c(a = "X", p = "e", e = "p")
string <- c("application", "developer")
str_replace_chars(string, rules)
#> [1] "XeelicXtion" "dpvploepr"

我正在开发一个包来处理这类问题。这比 qdap::mgsub 函数更安全,因为它不依赖占位符。它完全支持正则表达式作为匹配和替换。您提供一个命名列表,其中名称是要匹配的字符串,它们的值是替换。

devtools::install_github("bmewing/mgsub")
library(mgsub)
mgsub("developer",list("e" ="p", "p" = "e"))
#> [1] "dpvploepr"

qdap::mgsub(c("e","p"),c("p","e"),"developer")
#> [1] "dpvploppr"