Insert a character at a specific location in a string

Solution 1:

You can do this with regular expressions and gsub.

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

If you want to do this dynamically, you can create the expressions using paste:

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

as per DWin's comment,you may want this to be more general.

gsub('^(.{3})(.*)$', '\\1d\\2', old)

This way any three characters will match rather than only lower case. DWin also suggests using sub instead of gsub. This way you don't have to worry about the ^ as much since sub will only match the first instance. But I like to be explicit in regular expressions and only move to more general ones as I understand them and find a need for more generality.


as Greg Snow noted, you can use another form of regular expression that looks behind matches:

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

and could also build my dynamic gsub above using sprintf rather than paste0:

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

or for his sub regular expression:

lhs <- sprintf('(?<=.{%d})',n-1)

Solution 2:

stringi package for the rescue once again! The most simple and elegant solution among presented ones.

stri_sub function allows you to extract parts of the string and substitute parts of it like this:

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"

But if you do this:

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"

then no characters are removed but new one are inserted. Isn't that cool? :)

Solution 3:

@Justin's answer is the way I'd actually approach this because of its flexibility, but this could also be a fun approach.

You can treat the string as "fixed width format" and specify where you want to insert your character:

paste(read.fwf(textConnection(old), 
               c(4, nchar(old)), as.is = TRUE), 
      collapse = "d")

Particularly nice is the output when using sapply, since you get to see the original string as the "name".

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" 

Solution 4:

Your original way of doing this (i.e. splitting the string at an index and pasting in the inserted text) could be made into a generic function like so:

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="")
}

Example usage:

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

This allows you to insert a vector of characters at the locations given by a vector of indexes. The split_str_by_index and interleave functions are also useful on their own.

Edit:

I revised the code to allow for indexes in any order. Before, indexes needed to be in ascending order.

Solution 5:

I've made a custom function called substr1 to deal with extracting, replacing and inserting chars in a string. Run these codes at the start of every session. Feel free to try it out and let me know if it needs to be improved.

# 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"