문자열은 따옴표 사이에 들어 있는 텍스트 조각이에요. 현재 지원되는 모든 R 버전은 문자열을 UTF-8로 인코딩하므로, 세상 대부분의 언어를 지원할 수 있어요.
mystring <- "some text"
mystring
#> [1] "some text"
typeof(mystring)
#> [1] "character"
아쉽게도, 다른 언어에 익숙한 프로그래머에게는 이 용어가 헷갈릴 수 있어요.
type은 character이지만, R은 한 글자(다른 여러 언어에서는 char라고 부르지만 R에서는 아니에요)와 긴 문자열을 구분하지 않아요.
둘 다 하나의 항목이고, 작은따옴표 ' '와 큰따옴표 " "를 아무렇게나 바꿔 써도 돼요.
스타일 가이드에서는 큰따옴표를 쓰기를 권장해요. 단, 문자열 안에 큰따옴표를 이스케이프하지 않고 쓰고 싶을 때는 예외예요.
str2 <- 'String with "quotes"' # convenience format
str2
#> [1] "String with \"quotes\"" # standard format
따라서 character vector는 문자열의 벡터이고, 각 문자열이 하나의 원소예요.
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
위 예시에서 length(s[1])은 length("some")과 같아요. 즉 _문자열 길이_가 아니라 _벡터 원소의 개수_예요.
개별 문자열을 조작하는 것도 당연히 가능해요. 위 예시의 nchar()처럼요. 하지만 아래에서 다룰 특별한 함수들이 필요해요.
base R에서 못 하는 일은 거의 없지만, 많은 프로그래머는 문자열 조작 지원이 다소 제한적이고 헷갈린다고 느껴요.
다행히 훨씬 직관적이고 유연하며 스스로 일관성도 잘 지키는 tidyverse 패키지들이 있어요.
예전 방식의 문자열 함수를 쓰는 레거시 코드가 여전히 많아요. Exercism의 커뮤니티 풀이 대부분도 여기에 해당해요.
게다가 작은 작업에서는 큰 라이브러리를 불러오는 대신 내장 함수를 쓰는 게 유용할 때가 있어요. R 트랙 관리자들은 연습 문제를 검증할 모범 풀이를 작성할 때 이 방식을 주로 써요.
그래서 예전 방식에 대한 지식도 어느 정도 알아 둘 가치가 있어요.
nchar문자열의 문자 개수를 반환해요.
nchar("string")
#> [1] 6
nchar("Hrōðgār") # a person in Beowulf, written in Old English
#> [1] 7
"문자"가 무엇을 뜻하는지는 (복잡해요!) 이 글의 범위를 벗어나요. 위 예시에서 몇 가지만 짚어볼까요:
\0로 끝나지 않아요(그랬다면 길이에 1이 더해졌겠죠).paste와 paste0
임의의 개수만큼의 문자열을 하나로 합쳐요.
paste에는 선택적으로 구분자를 지정할 수 있어요. 기본값은 공백이에요.
paste0(v)는 paste(v, sep = "")와 같아요.
paste("bits", "of", "a", "string")
#> [1] "bits of a string"
paste0("bits", "of", "a", "string")
#> [1] "bitsofastring"
sprintf문자열 조립을 더 세밀하게 제어하기 위해, R은 C에서 sprintf 함수를 그대로 가져왔어요(이후의 여러 언어들도 마찬가지고요. 위키백과에는 약 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"
trimws앞쪽이나 뒤쪽의 공백은 trimws()로 제거할 수 있어요:
s <- " messy string "
trimws(s)
#> [1] "messy string"
양쪽 끝을 모두 다듬지 않으려면 which = "left"(또는 "right") 매개변수를 써요.
substr하나의 문자열에 작용해서 두 지점(양 끝 포함) 사이의 부분 문자열을 반환해요.
substr("Exercism", 5, 7)
#> [1] "cis"
strsplit구분자 문자열을 기준으로 문자열을 R 리스트로 쪼개요. 구분자는 필수예요(기본값이 없어요). 하지만 빈 문자열도 될 수 있어요.
strsplit("R, Python, Julia", ", ")
[[1]]
#> [1] "R" "Python" "Julia"
strsplit("Exercism", "")
[[1]]
#> [1] "E" "x" "e" "r" "c" "i" "s" "m"
리스트는 별도의 개념에서 다뤄요.
아직 교육 과정에서 그 부분에 이르지 않았다면, 지금은 unlist() 함수가 (이 경우에는) 리스트를 벡터로 바꿔 준다는 점만 알아두면 돼요.
unlist(strsplit("Exercism", ""))
#> [1] "E" "x" "e" "r" "c" "i" "s" "m"
좋게 말하자면, 예전 방식의 문자열 함수가 R이 줄 수 있는 최선은 아니에요(더 박하게 보는 시각도 있어요).
이런 상황을 개선하기 위해 stringr 라이브러리는 2009년부터 개발되어 왔고, 지금은 Tidyverse 모음의 핵심 부분이에요.
library(stringr)나 library(tidyverse)로 네임스페이스에 불러올 수 있어요.
주요 특징은 다음과 같아요:
str_ 접두사가 붙어 있고, 이름만 봐도 목적이 분명하도록 하려고 해요.모든 Tidyverse 패키지처럼 stringr도 문서가 잘 갖춰져 있고, 보통 패키지 작성자가 직접 써요.
함수를 연결하는 파이프 |>의 사용법은 교육 과정의 다른 부분에서 더 자세히 다뤄요.
지금은 다음 정도만 알아두면 유용해요:
아래 예시는 이 문법을 보여줘요. 개별 함수는 나중에 다뤄요.
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는 코드 포인트를 세요(nchars와 비슷해요).
str_trim()과 str_squish()를 비롯해 공백을 다루는 함수도 여러 가지가 있어요.
이는 데이터 과학 프로젝트를 시작할 때 꼭 필요한 정리 작업인 경우가 많아요.
# 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"
str_sub(string, start, end) 함수는 start부터 end까지(양 끝 포함)의 부분 문자열을 반환하고, str_sub_all()은 여러 문자열에 한꺼번에 작용할 수 있어요.
유연성이 아주 커요.
start와 end는 각각 첫 번째 문자와 마지막 문자를 기본값으로 해요.
인덱스 벡터가 될 수도 있어요.
반가운 점은, 벡터 인덱싱과 달리 음수 값은 문자열 끝에서부터 거꾸로 세요. Python 방식이죠.
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_flatten은 character 벡터를 하나의 문자열로 바꿔요.
collapse 인자는 각 원소 사이에 들어가고, 기본값은 ""예요.
c("R", "Julia", "Python") |> str_flatten()
#> [1] "RJuliaPython"
c("R", "Julia", "Python") |> str_flatten(collapse = " - ")
#> [1] "R - Julia - Python"
str_c는 여러 character 벡터를 하나의 character 벡터로 합쳐요. 이때 필요하면 재활용도 해요.
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는 간단한 문자열 보간을 R에 가져와요.
중괄호 { } 안의 표현식이 해석되고, 그 텍스트 표현이 대신 들어가요.
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
R을 쓸 때 아주 흔한 작업은 지저분한 데이터를 분석하기 좋게 일관된 형식으로 다듬는 거예요. 이를 돕기 위해 텍스트를 부분 문자열로 나누는 여러 방법이 있어요. 대부분은 지금 다룰 범위를 벗어나요.
str_split_1이 가장 간단한데, 하나의 문자열을 지정한 패턴을 기준으로 나눠 조각들의 벡터로 바꿔요(기본 패턴은 없어요).
s <- "R Julia Python"
str_split_1(s, " ")
#> [1] "R" "Julia" "Python"
일부 언어(주로 유럽 언어)는 대문자와 소문자를 구분해요.
이런 문자를 로케일에 맞게 서로 바꿔 주는 stringr 함수들이 있어요:
str_to_lower()str_to_upper()str_to_title() : 각 단어의 첫 글자를 대문자로str_to_sentence() : 문장의 첫 글자를 대문자로str_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"
패턴 매칭을 이용한 부분 문자열 교체에는 str_replace()가 패턴이 처음 나타난 곳을 바꾸고, str_replace_all()은 나타나는 모든 곳을 바꿔요.
문자 인덱스를 이용한 부분 문자열 교체에는 str_sub()의 할당 형식이 있어요.
s <- "abcde"
str_sub(s, 3, 4) <- "yz"
s
#> [1] "abyze"
이 연습 문제에서는 로그 줄을 처리해요.
각 로그 줄은 다음과 같은 형식의 문자열이에요: "[<LEVEL>]: <MESSAGE>".
로그 레벨은 세 가지가 있어요:
INFOWARNINGERROR과제는 세 가지이고, 각 과제는 로그 줄 하나를 받아 그걸로 무언가를 해 보라는 내용이에요.
base R의 레거시 함수를 사용해도 되고 stringr 라이브러리를 사용해도 돼요. 둘 다 짧으면서도 쓸 만한 해법을 만들어 줘요.
로그 줄의 메시지를 반환하도록 message 함수를 구현해요:
message("[ERROR]: Invalid operation")
#> "Invalid operation"
앞이나 뒤에 있는 공백은 제거해야 해요:
message("[WARNING]: Disk almost full\r\n")
#> "Disk almost full"
로그 줄의 로그 레벨을 소문자로 반환하도록 log_level 함수를 구현해요:
log_level("[ERROR]: Invalid operation")
#> "error"
메시지를 앞에 두고 그 뒤에 로그 레벨을 괄호 안에 넣도록 로그 줄 형식을 바꾸는 reformat 함수를 구현해요:
reformat("[INFO]: Operation completed")
#> "Operation completed (info)"
Exercism에 가입하고 R 트랙을 개념 21개연습 문제 111개, 그리고 실제 사람의 멘토링과 함께 배우고 익혀 보세요. 모두 무료예요.