Julia에서 String은 문자들이 순서대로 나열된 것이에요. 매뉴얼에서도 지적하듯이, "물론 진짜 어려움은 문자가 무엇인지 묻기 시작할 때 생겨요."
이런 복잡한 부분은 Chars 개념에서 더 자세히 다루지만, 간단히 말하면 다음과 같아요:
일반적으로 문자열은 큰따옴표 " "로 감싸요.
작은따옴표 ' '는 Chars에만 사용해요.
큰따옴표 세 개 """ """를 사용할 수도 있는데, 이렇게 하면 문자열 안에서 "를 이스케이프 없이 쓸 수 있어요.
더 중요한 점은, 여러 줄 문자열에서 불필요한 들여쓰기가 제거된다는 거예요.
julia> """
Multiline
"""
"Multiline\nString\n"
C 이후의 대부분 언어처럼, Julia는 이스케이프에 백슬래시 \를 사용해요:
\n처럼 화면에 출력되지 않는 문자를 입력할 때.\$(아래 참고).원칙적으로 문자열은 벡터와 똑같이 인덱싱할 수 있어요:
julia> s[1], s[5]
('J', 'a')
"Julia"처럼 ASCII 문자열에는 분명히 잘 동작하지만, 그건 세계 대부분의 언어를 무시하는 셈이에요.
약 15세기 전에 고대 영어로 쓰인 《베오울프》에 나오는 문자 하나를 생각해 봐요.
"Hrōðgār"는 분명히 7개의 문자지만, 그중 전부가 ASCII인 것은 아니에요.
julia> king = "Hrōðgār"
"Hrōðgār"
julia> length(king)
7
julia> isascii(king)
false
# collect() converts to a Char vector:
julia> collect(king)
7-element Vector{Char}:
'H': ASCII/Unicode U+0048 (category Lu: Letter, uppercase)
'r': ASCII/Unicode U+0072 (category Ll: Letter, lowercase)
'ō': Unicode U+014D (category Ll: Letter, lowercase)
'ð': Unicode U+00F0 (category Ll: Letter, lowercase)
'g': ASCII/Unicode U+0067 (category Ll: Letter, lowercase)
'ā': Unicode U+0101 (category Ll: Letter, lowercase)
'r': ASCII/Unicode U+0072 (category Ll: Letter, lowercase)
이런 문자열을 인덱싱하는 건 처음에는 잘 되지만, 어느 순간 무너져요:
julia> king[3]
'ō': Unicode U+014D (category Ll: Letter, lowercase)
julia> king[4]
ERROR: StringIndexError: invalid index [4], valid nearby indices [3]=>'ō', [5]=>'ð'
문제는 이런 인덱싱이 _바이트_를 세는데, ASCII가 아닌 문자열에서는 바이트와 문자가 1:1 관계가 아니라는 거예요.
유니코드/UTF-8 문자는 표현하는 데 최대 4바이트가 필요할 수 있으며, 이 부분은 Chars 개념에서 더 자세히 살펴봐요.
많은 언어가 +를 문자열 연결 연산자로 사용하지만, Julia는 아니에요.
적어도 오류 메시지는 친절해요.
julia> "s1 " + "s2"
ERROR: MethodError: no method matching +(::String, ::String)
The function `+` exists, but no method is defined for this combination of argument types.
String concatenation is performed with *
julia> "s1 " * "s2"
"s1 s2"
The string() 함수는 임의 개수의 문자열을 이어 붙여요.
julia> string("lots ", "of ", "bits")
"lots of bits"
join() 함수도 같은 일을 하고, 문자열 벡터를 받아 주어진 구분자로 이어 붙일 수도 있어요(기본값은 ""예요).
julia> join(["lots", "of", "bits"], "_")
"lots_of_bits"
문자열은 불변이고 이어 붙이기에는 복사가 따른다는 점을 기억해 둬요. 따라서 루프 안에서 이어 붙이기를 반복하는 것은 비효율적이고, 조각들을 벡터에 모아 두었다가 마지막에 한꺼번에 이어 붙이는 게 더 좋아요.
간단해요:
julia> repeat("Abc", 5)
"AbcAbcAbcAbcAbc"
많은 언어가 계산된 값을 문자열에 넣는 방법을 제공하지만, 어떤 문법이 최선인지에는 엄청난 의견 차이가 있어요.
Julia에서는 보간할 값 앞에 $를 붙여요.
julia> myvar = 42
42
julia> "myvar evaluates to $myvar"
"myvar evaluates to 42"
julia> r = 5.3
5.3
julia> "A circle of radius $r has area $(π * r^2)"
"A circle of radius 5.3 has area 88.24733763933729"
괄호를 생략하면 모호해질 때는 괄호가 필요하지만, 공백이 뒤따르는 단일 식별자에는 괄호가 없어도 돼요.
문자열에 $를 포함하려면 \$로 이스케이프해요.
현재 보간으로는 표시할 소수점 자릿수 같은 서식을 지정할 수 없다는 제약이 있다는 점을 기억해 둬요.
@sprintf()문자열 조립을 더 세밀하게 제어하려고, Julia는 C(그리고 이후의 여러 언어, 위키백과에는 약 30개가 나와요)에서 sprintf 함수를 가져왔어요.
Julia에서는 이것이 Printf 모듈 안의 macro로 구현되어 있어서 @ 접두사가 붙어요.
julia> using Printf
julia> r = 5.3
5.3
julia> @sprintf("A circle of radius %.1f has area %.2f", r, π * r^2)
"A circle of radius 5.3 has area 88.25"
간단히 하기 위해, 이 절에서는 새 문자열을 반환하고 입력은 그대로 두는 함수들에 집중해요.
많은 함수에는 입력을 제자리에서 수정하는, ! 접미사가 붙은 대응 함수가 있어요.
문자열은 얼마나 길까요? 때로는 무엇을 세느냐에 따라 달라져요.
간단한 경우는 모든 문자가 1바이트를 차지하는 ASCII 문자열이에요.
julia> string_asc = "Julia"
"Julia"
julia> length(string_asc)
5
julia> sizeof(string_asc)
5
length() 함수는 문자 수를 반환하고, sizeof() 함수는 바이트 수를 반환해요.
이 구분은 다른 문자 집합에서는 중요해져요:
julia> king = "Hrōðgār"
"Hrōðgār"
julia> length(king)
7
julia> sizeof(king)
10
프로그래밍에서 흔한 작업은 긴 문자열을 하나 이상의 문자로 된 구분자 문자열을 기준으로 조각조각 나누는 것이에요.
가장 일반적으로는 split() 함수를 사용해요.
구분자는 기본값이 (모든) 공백이지만, 다른 것을 지정할 수도 있어요.
julia> split("my little string")
3-element Vector{SubString{String}}:
"my"
"little"
"string"
julia> split("My_snake_case_string", "_")
4-element Vector{SubString{String}}:
"My"
"snake"
"case"
"string"
이 예제들은 문자열을 더 작은 _문자열_로 나눠요. 문자열을 _문자_로 나누는 것은 다른 작업이에요.
문자열에 지정한 부분 문자열이 들어 있을까요?
이상하게도, 이걸 확인하는 함수가 두 개 있어요. occursin()과 contains()는 인자의 순서만 다를 뿐이에요.
julia> needle = "Julia"
"Julia"
julia> haystack = "Python, Julia and R"
"Python, Julia and R"
julia> occursin(needle, haystack)
true
julia> contains(haystack, needle)
true
위치를 더 구체적으로 확인하려면:
julia> startswith(haystack, "Python")
true
julia> endswith(haystack, "Python")
false
부분 문자열의 시작:끝 인덱스를 구하는 함수는 여러 가지가 있어요:
findfirst()findlast()findprev()findnext()julia> findfirst(needle, haystack)
9:13
부분 문자열을 바꾸려면 x => y 문법으로 바꿀 내용을 나열해요.
julia> replace("abc", "a"=>"b", "b"=>"c", "c"=>"a")
"bca"
Julia 1.7부터 replace 함수는 임의 개수의 변경을 받을 수 있고, 바꾸는 동안 어떤 문자도 두 번 이상 바뀌지 않는다는 것이 보장돼요.
x => y 문법은 pair라고 불리는데, Julia에서 중요한 타입이며 Dicts and Pairs 개념에서 더 자세히 다뤄요.
문자열의 앞과 뒤의 공백을 제거해야 할 때가 자주 있어요.
이를 위한 함수는 lstrip()(왼쪽), rstrip()(오른쪽), strip()(양쪽)이에요.
julia> strip("\n\t useful_bit\n\n")
"useful_bit"
이 함수들의 이름은 대체로 이름만 봐도 알 수 있어요:
julia> s = repeat("aBcD ", 5)
"aBcD aBcD aBcD aBcD aBcD "
julia> uppercase(s)
"ABCD ABCD ABCD ABCD ABCD "
julia> lowercase(s)
"abcd abcd abcd abcd abcd "
julia> titlecase(s) # Initial letter of each word uppercase
"Abcd Abcd Abcd Abcd Abcd "
이번 연습 문제에서는 로그 줄을 처리해요.
각 로그 줄은 다음과 같은 형식의 문자열이에요: "[<LEVEL>]: <MESSAGE>".
로그 레벨은 세 가지가 있어요:
INFOWARNINGERROR세 가지 과제가 있고, 각 과제에서는 로그 줄 하나를 받아 무언가를 해봐요.
message 함수를 구현해서 로그 줄의 메시지를 반환하게 해요:
julia> message("[ERROR]: Invalid operation")
"Invalid operation"
앞뒤의 공백은 제거해야 해요:
julia> message("[WARNING]: Disk almost full\r\n")
"Disk almost full"
log_level 함수를 구현해서 로그 줄의 로그 레벨을 반환하게 해요. 로그 레벨은 소문자로 반환해야 해요:
julia> log_level("[ERROR]: Invalid operation")
"error"
reformat 함수를 구현해서 로그 줄의 형식을 다시 만들어요. 메시지를 앞에 두고, 그 뒤에 로그 레벨을 괄호 안에 넣어요:
julia> reformat("[INFO]: Operation completed")
"Operation completed (info)"
참고: 이 연습 문제의 모든 문자열은 영어이고 ASCII 문자 집합으로 제한돼요. 이후 콘셉트에서는 유니코드 문자를 다뤄 볼 기회가 있을 거예요.
Exercism에 가입하고 Julia 트랙을 개념 35개연습 문제 128개, 그리고 실제 사람의 멘토링과 함께 배우고 익혀 보세요. 모두 무료예요.