JuliaのStringは、順序づけられた文字の並びです。マニュアルにもあるとおり、「もちろん、本当の難しさは、文字とは何かと問い始めたときに現れます」。
この複雑さの多くは、Charsコンセプトでより詳しく扱いますが、手短にまとめると次のとおりです。
一般に、文字列は二重引用符" "で囲みます。
単一引用符' 'はCharsにのみ使います。
三重の二重引用符""" """も使えます。これを使うと、文字列の中の"をエスケープせずに書けます。
さらに重要なのは、複数行の文字列から余分なインデントが取り除かれることです。
julia> """
Multiline
String
"""
"Multiline\nString\n"
C以降のほとんどの言語と同じく、Juliaではエスケープにバックスラッシュ\を使います。
\nのように、表示されない文字を入力するためです。\$のように、そのままだと特別な意味を持つ文字を保護するためです(後述)。文字列は繰り返し可能なコレクションなので、文字のベクトルに変換しなくてもfor ... inループがそのまま動きます。
julia> s = "Julia"
"Julia"
# admittedly, this is a silly example
julia> for c in s; print(c, "; "); end
J; u; l; i; a;
このように繰り返す利点は、ASCII以外の文字列でも、Juliaが文字の境界を正しく扱ってくれることです。 このことの重要性は、次の節でよりはっきりします。
原理的には、文字列にもベクトルとまったく同じようにインデックスでアクセスできます。
julia> s[1], s[5]
('J', 'a')
"Julia"のようなASCII文字列なら、これは問題なく動きます。しかし、それでは世界のほとんどの言語を無視することになります。
約1500年前に古英語で書かれたBeowulfに出てくる文字を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"
文字列はイミュータブルで、連結にはコピーが伴うことに注意してください。 そのため、ループの中で何度も連結するのは効率が悪く、断片をベクトルに集めておいて最後にまとめて連結するほうがよいです。
簡単です。
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"
括弧(())は、省略すると曖昧になる場合には必要ですが、空白が続く単一の識別子なら省略できます。
文字列に$を含めたいときは、\$とエスケープします。
現時点では、補間で表示する小数の桁数などの書式指定ができないという制限があることに注意してください。
回避策としては、次のようなものがあります。
round()関数で包む。@sprintf()マクロについては後述します。@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"
これらの例では、文字列をより小さな_文字列_に分割しています。 文字列を_文字_に分割するのは別の操作です。
julia> collect("input string")
12-element Vector{Char}:
'i': ASCII/Unicode U+0069 (category Ll: Letter, lowercase)
'n': ASCII/Unicode U+006E (category Ll: Letter, lowercase)
'p': ASCII/Unicode U+0070 (category Ll: Letter, lowercase)
'u': ASCII/Unicode U+0075 (category Ll: Letter, lowercase)
't': ASCII/Unicode U+0074 (category Ll: Letter, lowercase)
' ': ASCII/Unicode U+0020 (category Zs: Separator, space)
's': ASCII/Unicode U+0073 (category Ll: Letter, lowercase)
't': ASCII/Unicode U+0074 (category Ll: Letter, lowercase)
'r': ASCII/Unicode U+0072 (category Ll: Letter, lowercase)
'i': ASCII/Unicode U+0069 (category Ll: Letter, lowercase)
'n': ASCII/Unicode U+006E (category Ll: Letter, lowercase)
'g': ASCII/Unicode U+0067 (category Ll: Letter, lowercase)
ある文字列に、指定した部分文字列が含まれているかどうかを調べたいとします。
奇妙なことに、これを調べる関数が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
部分文字列の開始:終了のインデックスを取得するには、いくつかの関数があります。
julia> findfirst(needle, haystack)
9:13
部分文字列を置換するには、x => y構文で変更を列挙します。
julia> replace("abc", "a"=>"b", "b"=>"c", "c"=>"a")
"bca"
Julia 1.7以降では、replace関数に任意の数の変更を渡せます。置換の間に同じ文字が2回以上変更されないことも保証されています。
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 "
数値とその文字列表現を相互に変換できると便利なことがよくあります。
数値から文字列への変換は簡単です。ただし、デフォルトの10以外の基数を使うには追加の引数が必要です。
julia> string(42)
"42"
julia> string(42, base=16)
"2a"
julia> string(42, base=2)
"101010"
文字列をパースして数値を得るのはもう少し複雑で、エラーになる可能性も高くなります。 変換先の数値型は必ず指定します。 基数の指定は任意で、デフォルトは10です。
julia> parse(Int, "42")
42
julia> parse(Float64, "42")
42.0
julia> parse(Int, "42?")
ERROR: ArgumentError: invalid base 10 digit '?' in "42?"
julia> parse(Int, "42 ") # whitespace may be tolerated
42
文字列内での通常の補間やエスケープを無効にできると便利なことがあります。
そのためには、開き引用符の前にrawを付けます。
julia> x = 10
10
julia> "x = $x if we use \$ for interpolation"
"x = 10 if we use \$ for interpolation"
julia> raw"x = $x if we use \$ for interpolation"
"x = \$x if we use \\\$ for interpolation"
正規表現には独自の複雑な構文があり、Juliaは便利な文字列型を2つ用意しています。正規表現にはr" "、キャプチャした断片を扱うにはs" "です。
その他のさまざまな特殊文字列は、Exercismのようなプログラミングではあまり見かけませんが、ライブラリを作るときには広く使われます。
v"x.y.z"は、ソフトウェアリリースのmajor.minor.patchの番号付けを扱います。b" "は、Unicodeの文字境界を経由せず、バイトレベルで動作します。