JuliaのStringは、文字を順番に並べたものです。マニュアルにも書かれているとおりです。「もちろん、本当に厄介なのは『文字とは何か』と問い始めたときです」。
この複雑さの多くはCharsのコンセプト演習でより詳しく扱いますが、手短に言うと次のとおりです:
一般に、文字列は二重引用符" "で囲みます。
一重引用符' 'はCharsにだけ使います。
三重の二重引用符""" """を使うこともできます。これを使うと、文字列の中の"をエスケープせずに使えます。
さらに重要なのは、複数行の文字列から余分なインデントが取り除かれることです。
julia> """
Multiline
"""
"Multiline\nString\n"
C以降のほとんどの言語と同じく、Juliaはエスケープにバックスラッシュ\を使います:
\nのような、表示されない文字を入力するためです。\$(後述)のように、そうしなければ特別な意味を持ってしまう文字を守るためです。原則として、文字列はベクトルとまったく同じようにインデックスできます:
julia> s[1], s[5]
('J', 'a')
「Julia」のようなASCII文字列なら問題なく動きますが、それでは世界のほとんどの言語を無視することになります。
約15世紀前に古英語で書かれた『ベオウルフ』の文字を1つ考えてみましょう。
「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の関係にはありません。
Unicode/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"
string()関数は、任意の数の文字列を連結します。
julia> string("lots ", "of ", "bits")
"lots of bits"
join()関数も同じことができ、さらに文字列のベクトルを受け取って、指定した区切り文字(デフォルトは"")で連結します。
julia> join(["lots", "of", "bits"], "_")
"lots_of_bits"
文字列はイミュータブルで、連結にはコピーが伴うことに注意してください。
そのため、ループの中で連結を繰り返すのは非効率で、断片をベクトルに集めておいて最後にまとめてjoinするほうがよいです。
簡単です:
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からsprintf関数を取り入れています(後発の言語の多くも同様で、Wikipediaには約30言語が挙げられています)。
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"
話を簡単にするため、このセクションでは新しい文字列を返し、入力を変更しない関数に絞ります。
多くには、!を付けた名前の、入力をその場で変更する同等の関数があります。
文字列の長さはどれくらいでしょうか? 数え方によって変わることがあります。
単純なのはASCII文字列で、どの文字も1バイトを占めます。
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
プログラミングでよくある操作は、長い文字列を、1文字以上の区切り文字列をもとに分割することです。
最も一般的には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"
これらの例は文字列をより小さな_文字列_に分割しています。 文字列を_文字_に分けるのは別の操作です。
文字列に指定した部分文字列が含まれているかどうかを調べるにはどうすればよいでしょうか?
奇妙なことに、これを調べる関数は2つあります。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>"。
ログレベルは3種類あります:
INFOWARNINGERRORタスクは3つあり、どれもログ行を受け取って何らかの処理をするものです。
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文字セットに限られています。Unicode文字を扱う機会は、あとの概念で出てきます。