在字符串的特定位置插入一个字符

77

我想在一个字符串的特定位置插入一个额外的字符(或新的字符串)。例如,我想在 abcefg 的第四个位置插入 d 来得到 abcdefg

现在我正在使用以下代码:

old <- "abcefg"
n <- 4
paste(substr(old, 1, n-1), "d", substr(old, n, nchar(old)), sep = "")

我可以为这个任务编写一行简单的函数,但我只是好奇是否有现有的函数可以实现。

8个回答

77

您可以使用正则表达式和 gsub 实现此操作。

gsub('^([a-z]{3})([a-z]+)$', '\\1d\\2', old)
# [1] "abcdefg"

如果您想以动态方式实现此操作,可以使用paste创建表达式:

letter <- 'd'
lhs <- paste0('^([a-z]{', n-1, '})([a-z]+)$')
rhs <- paste0('\\1', letter, '\\2')
gsub(lhs, rhs, old)
# [1] "abcdefg"

根据DWin的评论,您可能希望这更加通用。
gsub('^(.{3})(.*)$', '\\1d\\2', old)

这样,任何三个字符都将匹配而不仅仅是小写字母。 DWin 还建议使用 sub 而不是 gsub。这样做可以减少对 ^ 的担忧,因为 sub 只会匹配第一个实例。但我喜欢在常规表达式中明确指定,并只有在理解它们并需要更多通用性时才转向更通用的表达式。


正如 Greg Snow 指出的那样,您可以使用另一种正则表达式形式来查找匹配项:

sub( '(?<=.{3})', 'd', old, perl=TRUE )

并且也可以使用 sprintf 而非 paste0 来构建我的动态 gsub

lhs <- sprintf('^([a-z]{%d})([a-z]+)$', n-1) 

或者对于他的 `sub` 正则表达式:
lhs <- sprintf('(?<=.{%d})',n-1)

3
我喜欢正则表达式的解决方案,但我会使用 sub() 函数,并建议使用更通用的模式:^(.{3})(.*$)。目前任何非小写字母都将破坏替换。 - IRTFM
我喜欢在正则表达式中尽可能具体,这样它们就会失败而不是意外成功。但你说得很好! - Justin
@Justin 感谢你指出 gsub 函数。我知道这些模式匹配函数非常强大,但实际上不知道如何使用它们。它们似乎非常复杂。我需要在谷歌上搜索更多相关的内容。 - JACKY88
1
你可以使用后顾匹配来获取正确的位置而不进行捕获:sub( '(?<=.{3})', 'd', old, perl=TRUE ) 或者 gsub( '(?<=^.{3})', 'd', old, perl=TRUE )。哪种更简单可能是个人口味问题。 - Greg Snow
如果超过255个字符,您需要使用 perl = TRUE。否则,您会得到以下错误:Error in gsub("^(.{256})(.*)$", "\\1\\2", test_list[[1]]) : invalid regular expression '^(.{256})(.*)$', reason 'Invalid contents of {}' - Fons MA
显示剩余2条评论

55

stringi 包再次拯救!在所有提出的解决方案中,这是最简单和最优雅的解决方案。

stri_sub 函数允许您提取字符串的部分并替换其中的部分,就像这样:

x <- "abcde"
stri_sub(x, 1, 3) # from first to third character
# [1] "abc"
stri_sub(x, 1, 3) <- 1 # substitute from first to third character
x
# [1] "1de"

但是如果你这样做:

x <- "abcde"
stri_sub(x, 3, 2) # from 3 to 2 so... zero ?
# [1] ""
stri_sub(x, 3, 2) <- 1 # substitute from 3 to 2 ... hmm
x
# [1] "ab1cde"

那么就不会删除任何字符,但是会插入新的字符。很酷,对吧?:)


2
您是否知道如何使用这种优雅的方法在多个位置插入? - Aurèle
1
这里我天真地尝试使用reduce(),但看起来很笨拙:https://stackoverflow.com/questions/47336114 - Aurèle
4
一个令我困扰已久的问题得到了出色的解决方法。 - Antonios
2
有趣的函数,但 stri_sub <- 不容易放入管道中,而 sub 函数则可以。(这只有在您想要使用管道时才很重要...) - Bastien
好主意@Bastien - 你可以创建一个问题,也许我们应该添加另一个函数 - stri_sub_pipe来解决这个问题? https://github.com/gagolews/stringi/issues/ - bartektartanus
7
@Bastien和@bartektartanus,你们可能已经意识到了:现在可以通过stringi::stri_sub_replace(x, 3,2, value = 1)进行管道操作。 - ChriiSchee

8
@Justin的回答是我真正采用的方法,因为它非常灵活,但是这个方法也可以很有趣。
你可以将字符串视为“固定宽度格式”,并指定要插入字符的位置:
paste(read.fwf(textConnection(old), 
               c(4, nchar(old)), as.is = TRUE), 
      collapse = "d")

特别好的是使用sapply时的输出,因为您可以看到原始字符串作为“名称”。
newold <- c("some", "random", "words", "strung", "together")
sapply(newold, function(x) paste(read.fwf(textConnection(x), 
                                          c(4, nchar(x)), as.is = TRUE), 
                                 collapse = "-WEE-"))
#            some          random           words          strung        together 
#   "some-WEE-NA"   "rand-WEE-om"    "word-WEE-s"   "stru-WEE-ng" "toge-WEE-ther" 

4

您原来的做法(即在索引处拆分字符串并插入插入的文本)可以制作成一个通用函数,如下所示:

split_str_by_index <- function(target, index) {
  index <- sort(index)
  substr(rep(target, length(index) + 1),
         start = c(1, index),
         stop = c(index -1, nchar(target)))
}

#Taken from https://stat.ethz.ch/pipermail/r-help/2006-March/101023.html
interleave <- function(v1,v2)
{
  ord1 <- 2*(1:length(v1))-1
  ord2 <- 2*(1:length(v2))
  c(v1,v2)[order(c(ord1,ord2))]
}

insert_str <- function(target, insert, index) {
  insert <- insert[order(index)]
  index <- sort(index)
  paste(interleave(split_str_by_index(target, index), insert), collapse="")
}

示例用法:

> insert_str("1234567890", c("a", "b", "c"), c(5, 9, 3))
[1] "12c34a5678b90"

这让你能够在由索引向量给出的位置处插入字符向量。 split_str_by_indexinterleave 函数本身也很有用。 编辑: 我修改了代码,使索引可以以任何顺序排列,之前必须按升序排列。

3

我花了一些时间来理解正则表达式,之后我用我手头的数字找到了自己的方法

最终结果是:

old <- "89580000"
gsub('^([0-9]{5})([0-9]+)$', '\\1-\\2', old)

2

我已经编写了一个名为substr1的自定义函数,用于处理字符串中的字符提取、替换和插入。在每个会话开始时运行这些代码。随时尝试并告诉我是否需要改进。

# extraction
substr1 <- function(x,y) {
  z <- sapply(strsplit(as.character(x),''),function(w) paste(na.omit(w[y]),collapse=''))
  dim(z) <- dim(x)
  return(z) }

# substitution + insertion
`substr1<-` <- function(x,y,value) {
  names(y) <- c(value,rep('',length(y)-length(value)))
  z <- sapply(strsplit(as.character(x),''),function(w) {
    v <- seq(w)
    names(v) <- w
    paste(names(sort(c(y,v[setdiff(v,y)]))),collapse='') })
  dim(z) <- dim(x)
  return(z) }

# demonstration
abc <- 'abc'
substr1(abc,1)
# "a"
substr1(abc,c(1,3))
# "ac"
substr1(abc,-1)
# "bc"
substr1(abc,1) <- 'A'
# "Abc"
substr1(abc,1.5) <- 'A'
# "aAbc"
substr1(abc,c(0.5,2,3)) <- c('A','B')
# "AaB"

1

与你的类似!

首先确保加载 tidyverse 包,然后同时使用 paste0 和 gsub。

以下是精确代码:

paste0(substr(old, 1,3), "d", substr(old,4,6))

1
在编程中,你可以使用regmatches函数在字符串的特定位置插入字符。
old <- "abcefg"
n <- 4
regmatches(old, `attr<-`(n, "match.length", 0)) <- "d"

old
#[1] "abcdefg"

这也可以与正则表达式一起使用,以查找要插入的位置。

s <- "abcefg"
regmatches(s, regexpr("(?<=c)", s, perl=TRUE)) <- "d"

s
#[1] "abcdefg"

并且也适用于多个匹配,每个匹配都有不同的替换。

s <- "abcefg abcefg"
regmatches(s, gregexpr("(?<=c)", s, perl=TRUE)) <- list(1:2)

s
#[1] "abc1efg abc2efg"

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接