A string is a piece of text between quotes.
mystring <- "some text"
mystring
#> [1] "some text"
typeof(mystring)
#> [1] "character"
Unfortunately, the terminology may be confusing to programmers familiar with other languages.
Although the type is character, R makes no distinction between a single letter (called char in several other languages, but not in R) and a long string.
Both are a single item, and can be in single ' ' or double " " quotes interchangeably.
The style guide recommends using double quotes, except when you want to use double quotes within the string without escaping them.
str2 <- 'String with "quotes"' # convenience format
str2
#> [1] "String with \"quotes\"" # standard format
A character vector is thus a vector of strings, with each string as a single element.
s <- c("some", "strings")
s
#> [1] "some" "strings"
length(s)
#> [1] 2
length(s[1]) # the number of vector elements, not the string length
#> [1] 1
nchar(s[1]) # the number of characters in the string "some"
#> [1] 4
In the above example, length(s[1]) is equivalent to length("some"): the number of vector elements, not the string length.
Manipulating an individual string is obviously possible, as with nchar() in the above example, but needs particular functions that will be discussed below.
As we saw above, some punctuation characters need to be "escaped" by being preceeded with backslash \.
The list will be no surprise to most experienced programmers, as this topic is fairly well standardized across languages.
As well as quotes (as shown above), the list includes newlines \n, tabs \t, backslash \\, and Unicode symbols beginning with \U or \u.
To avoid using lots of escape characters, it is possible to define raw strings within r"( )":
r"(raw "string")"
#> [1] "raw \"string\""
For raw strings containing parentheses ( ), there are many other syntax options.
Any currently-supported version of R encodes strings in UTF-8, so most world languages can be supported.
"आधुनिक मानक हिन्दी"
#> [1] "आधुनिक मानक हिन्दी"
Additionally, many R functions are aware of locale, and try to follow local conventions for string sorting, conversion to upper/lower case, dates and times, etc.
This syllabus will show examples mainly for US English: both because this is a common default, and because the syllabus authors happen to be US-resident.
Locales are a complex topic, so please search for documentation on your own situation.
Most things are possible in base R, but many programmers find support for string manipulation rather limited and confusing.
Fortunately, there are tidyverse packages that are much more intuitive, flexible and self-consistent.
There is still a lot of legacy code using old-style string functions. This includes most community solutions on Exercism.
Additionally, for small tasks it can be useful to use built-in functions instead of importing a large library. The R track maintainers tend to take this approach when writing exemplar solutions to validate the exercises on our GitHub repo.
Therefore, some knowledge of the old approaches is still worth learning.
The functions below are presented in their simplest form, but most also have optional arguments. See the documentation for details.
ncharReturns the number of characters in a string.
nchar("string")
#> [1] 6
nchar("Hrōðgār") # a person in Beowulf, written in Old English
#> [1] 7
The (complicated!) topic of what "character" means is beyond our scope. Just note a couple of points in the examples above:
\0-terminated (which would add 1 to the length).Be careful what you pass to nchar.
Results can still be unpredictable for non-character values, though this has improved in recent R versions.
grepSearches for a pattern in a vector of strings, returning indices with a match.
grep("Jav", c("Java", "Python", "Javascript"))
#> [1] 1 3
paste and paste0
Combines an arbitrary number of strings into a single string.
Optionally, a separator can be specified for paste: the default is a space.
paste0(v) is equivalent to paste(v, sep = "").
paste("bits", "of", "a", "string")
#> [1] "bits of a string"
paste0("bits", "of", "a", "string")
#> [1] "bitsofastring"
sprintfFor finer control over string assembly, R copies the sprintf function from C (and several later languages: Wikipedia lists about 30).
r <- 5.3
pi <- 3.14159
sprintf("A circle of radius %.1f has area %.2f", r, pi * r^2)
#> [1] "A circle of radius 5.3 has area 88.25"
trimwsLeading and/or trailing whitespace can be removed with trimws():
s <- " messy string "
trimws(s)
#> [1] "messy string"
There is a which = "left" (or "right") parameter to avoid trimming both ends.
substrOperates on a single string and returns a substring between two (inclusive) limits.
substr("Exercism", 5, 7)
#> [1] "cis"
strsplitSplits a string into an R list, based on a separator string. The separator is required (with no default), but can be an empty string.
strsplit("R, Python, Julia", ", ")
[[1]]
#> [1] "R" "Python" "Julia"
strsplit("Exercism", "")
[[1]]
#> [1] "E" "x" "e" "r" "c" "i" "s" "m"
Lists will be covered in a separate Concept.
If you have not already reached that part of the syllabus, just know for now that unlist() function will (in this case) convert a list to a vector.
unlist(strsplit("Exercism", ""))
#> [1] "E" "x" "e" "r" "c" "i" "s" "m"
regexpr and regexpr
Short for "regular expression", these functions search for occurrences of a pattern in a string, returning a list of (potentially) useful data. They are mentioned only for completeness: there are better options!
regexpr returns only the first occurrence, gregexpr returns all occurrences ("g" for "global" search).
As the names imply, the pattern can be a regex, which will be covered in a later Concept.
Why do these old string functions look like Unix shell commands?
Though R was created in 1993, it was designed to be largely backwards-compatible with the S language.
S was designed at Bell Labs, first appearing in 1976. Bell Labs were also responsible for Unix (from 1969) and the C language (1972).
Quite a team from that much earlier era! We can only assume that they talked to one another and exchanged ideas.
Being charitable, we could say that the old-style string functions are not the best that R has to offer (less charitable views are available).
To improve the situation, the stringr library has been in development since 2009, and is now a key part of the Tidyverse collection.
Import it into your namespace with library(stringr) or library(tidyverse).
Some key features include:
str_ prefix and try to make the purpose clear.Like all the Tidyverse packages, stringr has good documentation, typically written by the package author(s).
There are dozens of functions available in stringr, and they often have a variety of optional arguments.
This can be a bit overwhelming at first, so the documentation is your friend.
Think about what you want to put in (single string? vector? something more complex?), and what you want to get out.
There is probably an easy way to do whatever you want, if you can find the correct function and the correct arguments.
The use of pipes |> to connect functions will be discussed in more detail in other parts of the syllabus.
For now, it is useful to know that:
The example below illustrates the syntax. Individual functions will be discussed later.
library(stringr)
"Monday-25-February" |>
str_split("-") |>
unlist() |> # [1] "Monday" "25" "February"
str_sub(1, 3) |> # [1] "Mon" "25" "Feb"
str_to_upper() |> # [1] "MON" "25" "FEB"
str_flatten(collapse = ", ")
#> [1] "MON, 25, FEB"
str_length counts code points (similar to nchars).
Various other functions manipulate whitespace, including str_trim() and str_squish().
This is often a vital cleaning task at the start of any data science project.
# The first 3 kernels in JuPyteR notebooks:
str_length(c("Julia", "Python", "R")) #> [1] 1 6 5
#> [1] 5 6 1
str_pad(c("Julia", "Python", "R"), 8, "right") # add spaces if necessary
#> [1] "Julia " "Python " "R "
str_trim("Julia Python R ") # remove leading & trailing whitespace
#> [1] "Julia Python R"
str_squish("Julia Python R ") # trim, collapse multiple spaces
#> [1] "Julia Python R"
The function str_sub(string, start, end) will return the substring from start to end (inclusive), while str_sub_all() can operate on multiple strings.
There is a lot of flexibility.
start and end default to the first and last character, respectively.
They can be vectors of indices.
In welcome contrast to vector indexing, negative values count back from the string end, Python-style.
s <- "abcdefgh"
str_sub(s, 2, 4)
#> [1] "bcd"
str_sub(s, 5)
#> [1] "efgh"
str_sub(s, -3, -2)
#> [1] "fg"
str_sub(s, c(1, 4), c(2, 5))
#> [1] "ab" "de"
str_sub(s, c(1, 4, 6), c(2, 5, -1))
#> [1] "ab" "de" "fgh"
s |> str_sub(2, 4) # these functions are designed for pipes
#> [1] "bcd"
# str_sub_all takes vector input, gives list output
str_sub_all(c(s, "ijklmnop"), c(1, 4, 6), c(2, 5, -1))
[[1]]
#> [1] "ab" "de" "fgh"
[[2]]
#> [1] "ij" "lm" "nop"
str_flatten converts a character vector to a single string.
The collapse argument is inserted between each element and defaults to "".
c("R", "Julia", "Python") |> str_flatten()
#> [1] "RJuliaPython"
c("R", "Julia", "Python") |> str_flatten(collapse = " - ")
#> [1] "R - Julia - Python"
str_c converts multiple character vectors to a single character vector, with recyling as necessary.
str_c(LETTERS[1:8], 1:8, sep = ":")
#> [1] "A:1" "B:2" "C:3" "D:4" "E:5" "F:6" "G:7" "H:8"
str_glue brings simple string interpolation to R.
Expressions inside braces { } are interpreted and the text repesentation substituted.
If multiple expressions strings are supplied, they will also be joined as in str_flatten.
Strings can also be optionally trimmed of leading/trailing whitespace.
r <- 5.3
pi <- 3.14159
str_glue("A circle of radius {r} has area {pi * r^2}")
A circle of radius 5.3 has area 88.2472631
Though str_glue is flexible (and quite complicated), an even wider range of possibilities is available in the underlying glue library.
A very common use of R is to massage messy data into a consistent format for analysis. To help this, there are several ways to split text into substrings.
str_split_1 is the simplest, converting a single string into a vector of pieces, split at some specified pattern (there is no default pattern).
s <- "R Julia Python"
str_split_1(s, " ")
#> [1] "R" "Julia" "Python"
str_split_i takes a character vector and an index n, returning a vector of the nth substring of each input string.
s2 <- c(s, "C Kotlin, F#") #> [1] "R Julia Python" "C Kotlin, F#"
str_split_i(s2, " ", 2) # 2nd element of each
#> [1] "Julia" "Kotlin,"
str_split takes a character vector, returning a list of vectors.
There is a Lists Concept in our syllabus, which explains the output.
str_split(s2, " ")
[[1]]
#> [1] "R" "Julia" "Python"
[[2]]
#> [1] "C" "Kotlin," "F#"
There is also str_split_fixed, which returns a matrix.
Matrices will be covered in a later Concept.
To match the whole string, str_equal() can use various definitions of "equality".
Most simply, the default is case sensitive but can be changed.
Locales and Unicode create many more possibilities.
str_equal("Odin", "odin")
#> [1] FALSE
str_equal("Odin", "odin", ignore_case = TRUE)
#> [1] TRUE
There are many other functions, which will be discussed in more detail in the Regular Expressions Concept.
Some languages (mainly European) distinguish between UPPERCASE and lowercase letters.
There are a set of stringr functions to interconvert these, in locale-appropriate ways:
str_to_lower()str_to_upper()str_to_title() : first letter of each word to upperstr_to_sentence() : first letter of sentence to upperstr_to_upper("elixir") # defaults to locale = "en"
#> [1] "ELIXIR"
str_to_upper("ελληνικά") # infers Greek
#> [1] "ΕΛΛΗΝΙΚΆ"
str_to_title("the cat sat on the mat")
#> [1] "The Cat Sat On The Mat"
str_to_sentence("the cat sat on the mat")
#> [1] "The cat sat on the mat"
For substring replacement based on pattern matching, str_replace() changes the first occurrence of the pattern, str_replace_all() changes all occurrences.
For substring replacement based on character indexing, there is an assignment form of str_sub().
s <- "abcde"
str_sub(s, 3, 4) <- "yz"
s
#> [1] "abyze"
str_dup creates duplicates and str_unique removes duplicates.
By default str_dup has no separator, but one can be specified.
str_dup("xyz", 4, sep = ", ")
#> [1] "xyz, xyz, xyz, xyz"
str_unique is an extension of unique().
Both remove duplicate entries from a vector, but the stringr function can also handle case sensitivity and various locale-specific matches.
inp <- c("some", "strings", "Some", "Strings")
str_unique(inp)
#> [1] "some" "strings" "Some" "Strings"
str_unique(inp, ignore_case = TRUE)
#> [1] "some" "strings"
The stringr library is based on the stringi library of lower-level functions.
If you have specific needs not already discussed above, then: