Remove duplicates within consecutive runs of characters

Viewed 390

I have strings containing lots of duplicates, like this:

tst <- c("C>C>C>B>B>B>B>C>C>*>*>*>*>*>C", "A>A>A", "*>B>B", 
     "A>A>A>A>A>*>A>A>A>*>*>*>*>A>A", "*>C>C", "A")

I'd like to remove all consecutive duplicated upper-case and "*" characters, so the expected result is this:

[1] "CBC*C" "A"     "*B"    "A*A*A" "*C"    "A"

I've successfully extracted the duplicated capitals:

library(stringr)
unlist(str_extract_all(gsub(">", "", tst), "(.)(?=\\1)"))
[1] "C" "C" "B" "B" "B" "C" "*" "*" "*" "*"

but am somewhat stuck here. My hunch is that the function which, which returns indices, might be of help but don't know how to implement it in this case.

Any ideas?

EDIT:

I wasn't that far from the solution myself - just using a negative lookahead (instead of the positive lookahead) does the trick:

str_extract_all(gsub(">", "", tst), "(.)(?!\\1)")
[[1]]
[1] "C" "B" "C" "*" "C"

[[2]]
[1] "A"

[[3]]
[1] "*" "B"

[[4]]
[1] "A" "*" "A" "*" "A"

[[5]]
[1] "*" "C"

[[6]]
[1] "A"
4 Answers

We can use gsub

gsub("([A-Z*]>)\\1+", "\\1", tst)
#[1] "C>B>C>*>C"

In order to get the second result, remove the >

gsub(">", "", gsub("([A-Z*]\\>)\\1+", "\\1", tst) ,fixed = TRUE)
#[1] "CBC*C"

Based on the OP's comments below, may be

gsub("(.)\\1+", "\\1", gsub(">", "", tst))
#[1] "CBC*C"
gsub("(.)\\1+", "\\1", gsub(">", "", "A>"))
#[1] "A"
gsub("(.)\\1+", "\\1", gsub(">", "", "A>A"))
#[1] "A"
gsub("(.)\\1+", "\\1", gsub(">", "", "A>A>A>A"))
#[1] "A"

Another way to get CBC*C could be using 2 groups and using group 2 in the replacement.

((.)>)\1+

Regex demo

Example

tst <- "C>C>C>B>B>B>B>C>C>*>*>*>*>*>C"
gsub("((.)>)\\1+", "\\2", tst)

Output

[1] "CBC*C"

For us allergic to regex:

paste(rle(strsplit(tst, ">")[[1]])$values, collapse = ">") # or collapse = ""
[1] "C>B>C>*>C"

...which of course fails for strings with runs of lowercase letters, like "A>A>a>a>A>A"

A somewhat universal base R approach without regexps.

The idea here is to melt down the string to groups and then remove the repeating patterns successively (which makes it distinct from unique):

tst <- "C>C>C>B>B>B>B>C>C>*>*>*>*>*>C"
st <- paste(unlist(strsplit(tst,">")),collapse="")
#[1] "CCCBBBBCC*****C"

paste( unlist( sapply( 1:nchar(st), function(x){
  if( substr(st,x,x) != substr(st,(x+1),(x+1)) ){ substr(st,x,x) } } ) ), collapse="" )
#[1] "CBC*C"

Oh, and if you want lowercase functionality (excluding lowercase letters from removal), use this instead:

paste( unlist( sapply( 1:nchar(st), function(x){
  a=substr(st,x,x); b=substr(st,(x+1),(x+1));
  if( a != b & toupper(a) == a ){ a } else if( toupper(a) != a ){ a }  } ) ), collapse="" )
Related