文字列

文字列とは有限の文字列を意味します.当然ながら,ここで問題になるのは「文字とは何か」ということです.英語圏の人がよく知っている文字は,アルファベットの「A」「B」「C」などのほか,数字や一般的な句読点などであり,これらの文字はASCII 規格による 0 ~ 127 の整数値への写像に合わせて規格化されています.確かに,ASCII 文字にアクセントなどの修飾を加えたものやキリル文字やギリシャ文字など英語に関連する script,アラビア語,中国語,ヘブライ語,ヒンディー語,日本語,韓国語などの ASCII や英語とは全く関係のない script など,英語以外の言語で使われている文字は他にもたくさんあります.Unicode 規格は'文字とは何か'という複雑な問題に取り組んでおり,この問題を扱う決定的な規格として一般に受け入れられています.必要に応じて,これらの複雑さを完全に無視して ASCII 文字だけが存在すると考えることもできますし,非 ASCII テキストを扱う際に遭遇する可能性のある文字やエンコーディングを処理できるコードを書くこともできます.Julia ではプレーンな ASCII テキストをシンプルかつ効率的に扱うことができ,また Unicode の取り扱いも可能な限りシンプルかつ効率的です.特に,C スタイルの文字列コードを書いて ASCII 文字列を処理すると性能面でもセマンティクス面でも期待通りに動作します.そのようなコードは,非 ASCII テキストに遭遇した場合,誤った結果を黙って渡されるのではなく,明確なエラーメッセージを表示して潔く失敗するようになっています.このような場合には,非 ASCII データを扱うようにコードを修正することが容易にできます.

Julia の文字列には,注目すべきハイレベルな特徴がいくつかあります:

  • Julia で文字列(および文字列リテラル)に使われる組み込みの具体的な型は,Stringです.これは,UTF-8 エンコーディングによるUnicode 文字の全範囲をサポートしています(他の Unicode エンコーディングとの間で変換するためのtranscode関数が提供されています).
  • すべての文字列型は抽象型である AbstractString のサブタイプであり,外部パッケージではさらに AbstractString サブタイプが定義されています (他のエンコーディング用など).関数で文字列の引数を取る場合,任意の文字列型を受け付けるためにその型を AbstractString と宣言する必要があります.
  • C 言語や Java のように,多くの動的型付け言語とは違い,Julia は単一の文字を表すAbstractCharというファーストクラスの型があります.AbstractCharの組み込みのサブタイプである Char は任意の Unicode 文字を表すことのできる 32-bit のプリミティブな型です(UTF-8 エンコーディングに基づいています).
  • Java のように文字列はイミュータブルです. AbstractString 型のオブジェクトは変更不可能です.異なる文字列の値を生成するには他の文字列から新たに生成します.
  • 概念的に言えば,文字列はインデックスから文字への 部分写像 です.即ちインデックスの値によっては,文字の値が返されず,例外が発生してしまいます.このためエンコード表現のバイトインデックスも利用でき,効率的な文字列のインデックス呼び出しが可能になります. 文字によるインデックスだと,ユニコード文字列はエンコーディングの変数の幅が変わるので,効率的かつ単純とはならないのです.

文字

Charは1つの文字を表します.これは,特別なリテラル表現と適切な算術動作を持つ 32 ビットのプリミティブ型であり,Unicode code point を表す数値に変換することができます(Julia のパッケージでは他の テキストエンコーディング に対する操作を最適化するためにAbstractCharなどの他のサブタイプを定義することができます). 以下は,Char の値がどのようなものかを示しています.

入力と表示:

julia> 'x'
'x': ASCII/Unicode U+0078 (category Ll: Letter, lowercase)

julia> typeof(ans)
Char

Charは整数値に容易に変換することができます:

julia> Int('x')
120

julia> typeof(ans)
Int64

32-bit アーキテクチャではtypeof(ans)Int32になります.整数値をCharに戻すことも容易です:

julia> Char(120)
'x': ASCII/Unicode U+0078 (category Ll: Letter, lowercase)

パフォーマンスのために,任意の整数値が Unicode の符号位置というわけではありませんが,Char変換では文字の値が有効であるかはチェックしません.変換された値が有効な符号位置であるかチェックしたい場合はisvalid関数を使用します:

julia> Char(0x110000)
'\U110000': Unicode U+110000 (category In: Invalid, too high)

julia> isvalid(Char, 0x110000)
false

この記事を書いている時点で,有効な Unicode 符号位置は,U+0000からU+D7FF およびU+E000からU+10FFFFです.これらの符号位置全てに明瞭な意味が与えられたわけではなく,またそれらをアプリケーションが必ずしも解釈できるわけでもありません.しかし,これらの値は全て有効な Unicode 文字であると考えられます.

任意の Unicode 文字を一重引用符で囲んで入力するには,\uに続けて 4 桁までの 16 進数を入力するか,\Uに続けて 8 桁までの 16 進数を入力します(最長の有効値は 6 桁まで):

julia> '\u0'
'\0': ASCII/Unicode U+0000 (category Cc: Other, control)

julia> '\u78'
'x': ASCII/Unicode U+0078 (category Ll: Letter, lowercase)

julia> '\u2200'
'∀': Unicode U+2200 (category Sm: Symbol, math)

julia> '\U10ffff'
'\U10ffff': Unicode U+10FFFF (category Cn: Other, not assigned)

Julia はシステムのロケールと言語設定を用いてどの文字がそのまま表示可能で,どの文字が一般的なエスケープされた\u\Uを用いた入力形式を用いなければならないかを決定します. それに加え,全てのC の従来のエスケープされた入力フォームも使用することができます.:

julia> Int('\0')
0

julia> Int('\t')
9

julia> Int('\n')
10

julia> Int('\e')
27

julia> Int('\x7f')
127

julia> Int('\177')
127

Charの値で比較や限られた範囲の算術演算ができます:

julia> 'A' < 'a'
true

julia> 'A' <= 'a' <= 'Z'
false

julia> 'A' <= 'X' <= 'Z'
true

julia> 'x' - 'a'
23

julia> 'A' + 1
'B': ASCII/Unicode U+0042 (category Lu: Letter, uppercase)

文字列の基本

文字列リテラルはタブルクォートやトリプルクォートで区切られます:

julia> str = "Hello, world.\n"
"Hello, world.\n"

julia> """Contains "quote" characters"""
"Contains \"quote\" characters"

文字列から 1 文字を取り出したい場合はインデックスで取り出せます.

julia> str[begin]
'H': ASCII/Unicode U+0048 (category Lu: Letter, uppercase)

julia> str[1]
'H': ASCII/Unicode U+0048 (category Lu: Letter, uppercase)

julia> str[6]
',': ASCII/Unicode U+002C (category Po: Punctuation, other)

julia> str[end]
'\n': ASCII/Unicode U+000A (category Cc: Other, control)

文字列を含む多くの Julia のオブジェクトは整数でインデックスをつけることができます.最初の要素(文字列の最初の文字)のインデックスはfirstindex(str)で最後の要素(文字)のインデックスはlastindex(str)で返されます.キーワード beginendは,インデックス操作の中で,与えられた次元に沿ったそれぞれの最初と最後のインデックスを表す略語として使用できます.文字列インデックスのような Julia におけるほとんどのインデックスは 1 から始まり,firstindexはどのAbscractStringに対しても常に1を返します.しかしながら,後述するように,一般的にはlastindex(str)は文字列のlength(str)とは違うものです.なぜなら,Unicode 文字は複数の「符号」を占めることがあるからです.

endでは通常の値と同じように算術演算やその他の操作を行うことができます:

julia> str[end-1]
'.': ASCII/Unicode U+002E (category Po: Punctuation, other)

julia> str[end÷2]
' ': ASCII/Unicode U+0020 (category Zs: Separator, space)

インデックスが begin (1) より小さいか,end より大きいと,エラーになります:

julia> str[begin-1]
ERROR: BoundsError: attempt to access String
  at index [0]
[...]

julia> str[end+1]
ERROR: BoundsError: attempt to access String
  at index [15]
[...]

レンジインデックスを用いて部分文字列を取り出すことができます:

julia> str[4:9]
"lo, wo"

str[k]str[k:k] は同じ結果にならないことに注意してください:

julia> str[6]
',': ASCII/Unicode U+002C (category Po: Punctuation, other)

julia> str[6:6]
","

前者は Char 型の 1 文字の値で,後者は 1 文字しか含まない文字列の値です. 後者は,たまたま 1 文字しか含まれていない文字列値です.Julia ではこれらは全く異なるものです.

範囲指定では下の文字列の選択された部分をコピーします.また,SubString型を使って文字列へのビューを作成することもできます.

例:

julia> str = "long string"
"long string"

julia> substr = SubString(str, 1, 4)
"long"

julia> typeof(substr)
SubString{String}

chopchompstripのようないくつかの標準的な関数は,SubStringを返します.

Unicode と UTF-8

Julia は Unicode 文字とその文字列に完全に対応しています.上述のように,文字リテラルでは,Unicode の符号位置は,Unicode の\u\Uのエスケープシーケンスや,C 標準のエスケープシーケンスを使って表現することができます.これらは,文字列リテラルを記述する際にも同様に使用できます:

julia> s = "\u2200 x \u2203 y"
"∀ x ∃ y"

これらの Unicode 文字がエスケープされて表示されるか,特殊文字として表示されるかは,ターミナルのロケール設定と Unicode の対応状況に依存します.文字列リテラルのエンコードには UTF-8 エンコーディングを使用してエンコードされます.UTF-8 は可変幅のエンコーディングなので,つまりすべての文字が同じバイト数(「符号」)でエンコードされるわけではありません.UTF-8 では,ASCII 文字,つまり符号位置が 0x80(128)未満の文字は,ASCII と同じように 1 バイトでエンコードされますが,符号位置が 0x80 以上の文字は 1 文字あたり最大 4 バイトまでの複数バイトでエンコードされます.

Julia の文字列インデックスは,任意の文字(符号位置)をエンコードするための固定幅の構成要素である符号(= UTF-8 ではバイト)を指します.つまり,Stringへのすべてのインデックスが,必ずしも文字に対して有効なインデックスではないということです.このような無効なバイトインデックスで文字列を入力した場合,エラーが発生します:

julia> s[1]
'∀': Unicode U+2200 (category Sm: Symbol, math)

julia> s[2]
ERROR: StringIndexError("∀ x ∃ y", 2)
[...]

julia> s[3]
ERROR: StringIndexError("∀ x ∃ y", 3)
Stacktrace:
[...]

julia> s[4]
' ': ASCII/Unicode U+0020 (category Zs: Separator, space)

この場合,文字は 3 バイト文字なので,インデックス 2 と 3 は無効で,次の文字のインデックスは 4 となります.この次の有効なインデックスはnextind(s,1)で計算でき,その次のインデックスはnextind(s,4)となります.

endは常にコレクションの最後の有効なインデックスなので,最後から 2 番目の文字がマルチバイトの場合,end-1は無効なバイトインデックスを参照します.

julia> s[end-1]
' ': ASCII/Unicode U+0020 (category Zs: Separator, space)

julia> s[end-2]
ERROR: StringIndexError("∀ x ∃ y", 9)
Stacktrace:
[...]

julia> s[prevind(s, end, 2)]
'∃': Unicode U+2203 (category Sm: Symbol, math)

最初のケースは,最後の文字 y とスペースが 1 バイト文字であるので動作するのに対し,インデックスend-2 のマルチバイト表現の中央にインデックスを置くので,動作しません. この場合の正しい方法は,prevind(s, lastindex(s), 2)を使うか,sへのインデックスにその値を使うのであればs[prevind(s, end, 2)]と書き,endlastindex(s)に展開されます.

レンジインデックスを使用した部分文字列の抽出でも有効なバイトインデックスは必要であり,そうでない場合はエラーが発生します:

julia> s[1:1]
"∀"

julia> s[1:2]
ERROR: StringIndexError("∀ x ∃ y", 2)
Stacktrace:
[...]

julia> s[1:4]
"∀ "

可変長エンコーディングのため,(length(s)で与えられる)文字列の文字数は,最後のインデックスと同じとは限りません. 1 からlastindex(s)までのインデックスを反復してsの文字を参照すると,エラーが発生しなかったときに返される文字列は文字列sを構成する文字列となります.このように文字列の各文字にはそれぞれインデックスが必要なので,length(s) <= lastindex(s)という恒等式が成り立ちます.以下は,sの文字を反復する非効率的で冗長な方法です.

julia> for i = firstindex(s):lastindex(s)
           try
               println(s[i])
           catch
               # ignore the index error
           end
       end
∀

x

∃

y

空白行は実際にはスペースが入っています.幸いなことに,文字列内の文字を反復処理する際には,上記のような厄介なイディオムは必要ありません.文字列を反復可能なオブジェクトとして使用するだけで,例外処理は必要ありません:

julia> for c in s
           println(c)
       end
∀

x

∃

y

文字列の有効なインデックスを取得する必要がある場合には,前述のように,nextind および prevind 関数を使って,有効な次/前のインデックスにインクリメント/デクリメントすることができます.また,eachindex関数を使って,有効な文字列インデックスを繰り返し処理することもできます:

julia> collect(eachindex(s))
7-element Array{Int64,1}:
  1
  4
  5
  6
  7
 10
 11

エンコーディングの未加工の符号(UTF-8 の場合はバイト)にアクセスするには,codeunit(s,i)関数を使います.ここで,インデックスi1からncodeunits(s)まで連続しています.codeunits(s)関数はAbstractVector{UInt8}というラッパーを返すので,これらの未加工の符号(バイト)を配列として利用することができます.

Julia の文字列には,無効な UTF-8 符号列が含まれることがあります.この規約により,任意のバイト列を String として扱うことができます.このような状況では,符号列を左から右に解析する際に,文字は以下のビットパターン(各 x0 または 1)のいずれかの開始に一致する,最長の 8 ビットの符号列によって形成されるというルールがあります.

  • 0xxxxxxx;
  • 110xxxxx 10xxxxxx;
  • 1110xxxx 10xxxxxx 10xxxxxx;
  • 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx;
  • 10xxxxxx;
  • 11111xxx.

特に,冗長すぎたり値が大きすぎたりする符号列とその接頭辞は,複数の無効な文字ではなく,単一の無効な文字として扱われます.このルールは,例を挙げて説明するのが一番わかりやすいでしょう.

julia> s = "\xc0\xa0\xe2\x88\xe2|"
"\xc0\xa0\xe2\x88\xe2|"

julia> foreach(display, s)
'\xc0\xa0': [overlong] ASCII/Unicode U+0020 (category Zs: Separator, space)
'\xe2\x88': Malformed UTF-8 (category Ma: Malformed, bad data)
'\xe2': Malformed UTF-8 (category Ma: Malformed, bad data)
'|': ASCII/Unicode U+007C (category Sm: Symbol, math)

julia> isvalid.(collect(s))
4-element BitArray{1}:
 0
 0
 0
 1

julia> s2 = "\xf7\xbf\xbf\xbf"
"\U1fffff"

julia> foreach(display, s2)
'\U1fffff': Unicode U+1FFFFF (category In: Invalid, too high)

文字列 s の最初の 2 つの符号が,空白文字の冗長なエンコーディングを形成していることがわかります.これは無効ですが,文字列では 1 つの文字として受け入れられます.次の 2 つの符号は,3 バイトの UTF-8 の列の有効な開始を形成します.しかし,5 番目の符号\xe2は有効な値ではありません.したがって,3 番目と 4 番目の符号もこの文字列では不正な文字として解釈されます.同様に,5 番目の符号はが有効な継続部分ではないため,不正な文字を形成します.最後に,文字列 s2 には 大きすぎる符号位置が 1 つ含まれています.

Julia はデフォルトでは UTF-8 エンコーディングを使用しますが,新しいエンコーディングのサポートはパッケージによって追加することができます.例えば,LegacyStrings.jlパッケージでは,UTF16String型とUTF32String型を実装しています.他のエンコーディングやそのサポートの実装方法についての詳しい説明はこのドキュメントの範囲外となります.また UTF-8 エンコーディングの問題については,以下のbyte array literalsの節を参照してください.様々な UTF-xx エンコーディングの間でデータを変換するために,transcode 関数が提供されています.これは主に外部のデータやライブラリを扱うためのものです.

連結

最も一般的で便利な文字列操作の一つが連結です:

julia> greet = "Hello"
"Hello"

julia> whom = "world"
"world"

julia> string(greet, ", ", whom, ".\n")
"Hello, world.\n"

無効な UTF-8 文字列の連結など,潜在的に危険な状況に注意することが重要です.結果として得られる文字列には,入力文字列とは異なる文字が含まれている可能性があり,その文字数は,連結された文字列の文字数の合計よりも少ない可能性があります.

例:

julia> a, b = "\xe2\x88", "\x80"
("\xe2\x88", "\x80")

julia> c = a*b
"∀"

julia> collect.([a, b, c])
3-element Array{Array{Char,1},1}:
 ['\xe2\x88']
 ['\x80']
 ['∀']

julia> length.([a, b, c])
3-element Array{Int64,1}:
 1
 1
 1

この状況は,無効な UTF-8 文字列に対してのみ発生します.有効な UTF-8 文字列の場合,連結は文字列内のすべての文字と文字列長の加法性を保持します.

また,Julia には文字列連結のための*が用意されています:

julia> greet * ", " * whom * ".\n"
"Hello, world.\n"

一方,文字列の連結に + を提供している言語のユーザーにとっては,* は意外な選択に思えるかもしれませんが,* の使用は,数学,特に抽象代数では前例があります.数学では,+は通常,被演算子の順序が問題にならない 可換 の演算を表します.この例として行列の加算を考えると,同じ形の行列 AB に対してA + B == B + A となります.対照的に,* は一般的に 非可換 演算を表し,演算子の順序が重要になります.この例として行列の乗算を考えると,一般的には A * B != B * A となります.行列の乗算と同様に文字列の連結も 非可換 です.例えば,greet * whom != whom * greetとなります.このように, 中置記法の文字列連結演算子としては * がより自然な選択であり,一般的な数学的使用と一致しています.

より正確には,すべての有限長の文字列 S と文字列連結演算子*の集合は,自由モノイド (S, *)を形成します.この集合の恒等要素は空文字列 "" です.自由モノイドが可換でない場合,その演算は通常 + ではなくcdot, *, または同様の記号で表されます.

文字列補間

連結で文字列を構築するのは少々面倒な作業です.そこで,stringのくどい呼び出しや繰り返しの乗算を減らすために,Julia では Perl のように,$を用いて文字列リテラルに補間することができます:

julia> "$greet, $whom.\n"
"Hello, world.\n"

こちらはより読みやすく便利で,上記の文字列連結と同値です.システムは,この見かけ上の単一の文字列リテラルを呼び出しstring(greet, ", ", whom, ".\n")に書き換えます.

$の後の最も短い完全な式が,文字列に値を補うべき式とみなされます.このように,括弧を使えば,どんな式でも文字列に補間することができます:

julia> "1 + 2 = $(1 + 2)"
"1 + 2 = 3"

連結や文字列補間では,オブジェクトを文字列に変換するために string を呼び出します.しかし,stringは実際には print の出力を返すだけなので,新しい型ではstringの代わりに printshow のメソッドを追加する必要があります.

多くのAbstractStringではないオブジェクトは,リテラル式として入力された様式に近い形で文字列に変換されます.

julia> v = [1,2,3]
3-element Array{Int64,1}:
 1
 2
 3

julia> "v: $v"
"v: [1, 2, 3]"

stringAbstractStringAbstractCharの値と恒等であり,これらは引用符やエスケープされずにそのまま文字列に補間されます.

julia> c = 'x'
'x': ASCII/Unicode U+0078 (category Ll: Letter, lowercase)

julia> "hi, $c"
"hi, x"

リテラル $ を文字列リテラルに含めるには,バックスラッシュでエスケープします:

julia> print("I have \$100 in my account.\n")
I have $100 in my account.

トリプルクォーテーション付きの文字列リテラル

トリプルクォーテーション("""...""")を使った文字列には,長いテキストブロックを作成するのに便利で特別な動作があります.

まず,トリプルクォーテーションで囲まれた文字列はインデントが少ない行のレベルを基準として,インデントが除去(ディデンテーション)されます.これは,コードの中でインデントを含んだ文字列を定義するのに便利です.例えば,以下のようになります:

julia> str = """
           Hello,
           world.
         """
"  Hello,\n  world.\n"

この場合は閉じる側の"""の直前の(空)行がインデントレベルとして設定されます.

ディデンテーションレベルは,冒頭の """ に続く行と,スペースまたはタブだけを含む行を除いた,すべての行のうち,最も長い先頭のスペースまたはタブ数によって決められます(最後の """ を含む行は常に含まれます).続いて,冒頭の """ に続く行を除いたすべての行について,各行先頭の空白やタブが削除されます(スペースとタブだけを含む行を含む).

例:

julia> """    This
         is
           a test"""
"    This\nis\n  a test"

次に,冒頭の """ の後に改行がある場合は結果の文字列から改行が取り除かれます.

"""hello"""

これは以下と等価です.

"""
hello"""

しかし

"""

hello"""

は先頭に改行リテラルを含みます.

改行の除去は,ディテンデーションの後に行われます.例えば以下のようになります:

julia> """
         Hello,
         world."""
"Hello,\nworld."

末尾のホワイトスペースはそのまま残されます.

トリプルクォーテーションで囲まれた文字列リテラルには,エスケープせずに " 文字を含めることができます.

リテラル文字列の改行はリテラルがシングルクオーテーション,トリプルクオーテーションどちらで囲まれていても返り値の改行部分は改行(LF)文字\nが入ります.これはエディタが CR 文字や CRLF の組み合わせで行を終わらせている場合でも同様です.文字列に CR 文字を含めるには,明示的なエスケープを使用します,例えば/リテラル文字列 "a CRLF line ending\r\n" を入力します.

よくある操作

標準的な比較演算子を使って,文字列を辞書的に比較することができます.

julia> "abracadabra" < "xylophone"
true

julia> "abracadabra" == "xylophone"
false

julia> "Hello, world." != "Goodbye, world."
true

julia> "1 + 2 = 3" == "1 + 2 = $(1 + 2)"
true

findfirstおよびfindlast関数を使って,特定の文字のインデックスを検索することができます.

julia> findfirst(isequal('o'), "xylophone")
4

julia> findlast(isequal('o'), "xylophone")
7

julia> findfirst(isequal('z'), "xylophone")

関数findnextfindprevを使えば,指定したオフセットで文字の検索を開始することができます.

julia> findnext(isequal('o'), "xylophone", 1)
4

julia> findnext(isequal('o'), "xylophone", 5)
7

julia> findprev(isequal('o'), "xylophone", 5)
4

julia> findnext(isequal('o'), "xylophone", 8)

文字列の中に部分文字列があるかどうかを調べるにはoccursin関数を使います.

julia> occursin("world", "Hello, world.")
true

julia> occursin("o", "Xylophon")
true

julia> occursin("a", "Xylophon")
false

julia> occursin('o', "Xylophon")
true

最後の例では,occursinが文字リテラルを探すこともできることを示しています.

他にも便利な文字列関数としてrepeatjoinがあります.

julia> repeat(".:Z:.", 10)
".:Z:..:Z:..:Z:..:Z:..:Z:..:Z:..:Z:..:Z:..:Z:..:Z:."

julia> join(["apples", "bananas", "pineapples"], ", ", " and ")
"apples, bananas and pineapples"

他にも便利な関数があります:

  • firstindex(str)str へのインデックスに使用できる最小の(バイト)インデックスを返します(文字列の場合は常に 1 ですが,他のコンテナの場合は必ずしもそうではありません).
  • lastindex(str)は,str のインデックスに使用できる最大の(バイト)インデックスを返します.
  • length(str)strの文字数です.
  • length(str, i, j)str の中の i から j までの有効な文字インデックスの数です.
  • ncodeunits(str) は文字列中のcode unitsの数です.
  • codeunit(str, i) は文字列 str のインデックス i にコードユニットの値を返します.
  • thisind(str, i) は文字列に任意のインデックスを与えると,そのインデックスが指し示す文字の最初のインデックスを返します.
  • nextind(str, i, n=1) は添字 i から始まる n 番目の文字の先頭を見つけます.
  • prevind(str, i, n=1) は添字 i より前の n 番目の文字の始まりを見つけます.

非標準文字列リテラル

文字列を作成したり文字列セマンティクスを使用したりしたいが,標準的な文字列構築の動作が必要とされるものとは全く異なる場合があります.このような状況のために,Julia は非標準文字列リテラルを提供しています. 非標準文字列リテラルは通常のダブルクオーテーションで囲まれた文字列リテラルのように見えますが,識別子として接頭辞がつけられ,通常の文字列リテラルのようには動作しません.具体的には後述の正規表現,バイト配列リテラル,バージョン番号リテラルなどがなどが挙げられます.その他の例 はメタプログラミングの項を参照してください.

正規表現

Julia は,PCREライブラリが提供する Perl 互換の正規表現(regexes)を備えています(構文の説明はこちらにあります).正規表現は文字列と 2 つの点で関連しています.まず明らかな関連性として,正規表現が文字列の正規パターンを見つけるために使用される点が挙げられます.もう一つの関連性は,正規表現はそれ自体が文字列として入力され,文字列内のパターンを効率的に検索するために使用される状態機械(ステートマシン)に解析されるということです.Julia では,正規表現は rで始まる様々な識別子を前置した非標準の文字列リテラルを使って入力されます.最も基本的な正規表現リテラルは,オプションを何もつけずにr"..."を使うだけです:

julia> r"^\s*(?:#|$)"
r"^\s*(?:#|$)"

julia> typeof(ans)
Regex

正規表現が文字列にマッチするかどうかを調べるには,occursinを使います:

julia> occursin(r"^\s*(?:#|$)", "not a comment")
false

julia> occursin(r"^\s*(?:#|$)", "# a comment")
true

ここで見られるように,occursinは単純に true または false を返し,与えられた正規表現にマッチするものが文字列の中に存在するかどうかを示します.しかし,一般的には文字列がマッチしたかどうかだけでなく,どのようにマッチしたかを知りたい場合があります.このようなマッチに関する情報を取得するには,代わりに match 関数を使用します:

julia> match(r"^\s*(?:#|$)", "not a comment")

julia> match(r"^\s*(?:#|$)", "# a comment")
RegexMatch("#")

正規表現が与えられた文字列にマッチしなかった場合,matchnothingを返します.これは,対話型プロンプトに何も表示しない特別な値です.何も表示されないだけであり,正常な値であるのでプログラムは正常に動作します.

m = match(r"^\s*(?:#|$)", line)
if m === nothing
    println("not a comment")
else
    println("blank or comment")
end

正規表現にマッチした場合,matchRegexMatch オブジェクトを返します.これらのオブジェクトはパターンがマッチした部分文字列やキャプチャされた部分文字列に対して正規表現がどのようにマッチしたかを記録したものです.この例では,マッチした部分文字列のみをキャプチャしていますが,コメントの後の空白でないテキストもキャプチャしたい場合があります. その場合は次のようにします:

julia> m = match(r"^\s*(?:#\s*(.*?)\s*$|$)", "# a comment ")
RegexMatch("# a comment ", 1="a comment")

matchを呼び出す際には検索を開始するインデックスを指定できるオプションがあります.

例:

julia> m = match(r"[0-9]","aaaa1aaaa2aaaa3",1)
RegexMatch("1")

julia> m = match(r"[0-9]","aaaa1aaaa2aaaa3",6)
RegexMatch("2")

julia> m = match(r"[0-9]","aaaa1aaaa2aaaa3",11)
RegexMatch("3")

RegexMatchオブジェクトからは以下の情報を抽出することができます:

  • マッチした部分文字列全体: m.match
  • キャプチャされた部分文字列が文字列の配列になったもの: m.captures
  • マッチした文字列の開始位置(オフセット): m.offset
  • キャプチャされた部分文字列のオフセットをベクトルで表したもの: m.offsets

キャプチャがマッチしない場合,m.capturesにはその位置にnothingが含まれ,m.offsetsにはゼロのオフセットが含まれます(Julia のインデックスは 1 ベースなので,文字列へのゼロの 文字列へのゼロオフセットは無効です).以下では,やや作為的な例を 2 つ示します:

julia> m = match(r"(a|b)(c)?(d)", "acd")
RegexMatch("acd", 1="a", 2="c", 3="d")

julia> m.match
"acd"

julia> m.captures
3-element Array{Union{Nothing, SubString{String}},1}:
 "a"
 "c"
 "d"

julia> m.offset
1

julia> m.offsets
3-element Array{Int64,1}:
 1
 2
 3

julia> m = match(r"(a|b)(c)?(d)", "ad")
RegexMatch("ad", 1="a", 2=nothing, 3="d")

julia> m.match
"ad"

julia> m.captures
3-element Array{Union{Nothing, SubString{String}},1}:
 "a"
 nothing
 "d"

julia> m.offset
1

julia> m.offsets
3-element Array{Int64,1}:
 1
 0
 2

キャプチャを配列として返すと分割代入を使ってローカル変数にバインドすることができます:

julia> first, second, third = m.captures; first
"a"

キャプチャはRegexMatchのインデックス番号か,名前付きキャプチャグループをインデックスすることでアクセスできます:

julia> m=match(r"(?<hour>\d+):(?<minute>\d+)","12:45")
RegexMatch("12:45", hour="12", minute="45")

julia> m[:minute]
"45"

julia> m[2]
"45"

replaceを使った置換文字列でキャプチャーを参照するには,\nを使って n 番目のキャプチャグループを参照し,置換文字列の前にsを付けます.キャプチャグループ 0 はマッチオブジェクト全体を指します.名前付きキャプチャグループは,置換文字列の中で \g<groupname> を使って参照できます.たとえば,以下のようになります:

julia> replace("first second", r"(\w+) (?<agroup>\w+)" => s"\g<agroup> \1")
"second first"

番号の付いたキャプチャーグループは,曖昧さ回避のためにg<n>として参照することもでき,以下のようになります:

julia> replace("a", r"." => s"\g<0>1")
"a1"

ダブルクォーテーションマークの後のフラグimsxの組み合わせによって,正規表現の動作を変更することができます.これらのフラグは,perlre manpageからの抜粋で説明されているように,Perl におけるフラグと同じ意味を持っています.

i   大文字小文字を区別しないパターンマッチング

    ロケール照合規則が有効な場合,符号位置が255未満の場合は現在のロケールから,
    それ以上の場合はUnicode規則から,ケースマップを取得します.ただし,Unicode規則と
    非Unicode規則の境界(コード番号255/256)を超えるようなマッチは (ords 255/256)
    を超えるマッチは成功しません.

m   文字列を複数行として扱います.つまり,"^"と"$"は文字列の開始または終了にマッチするのではなく
    文字列内の任意の行の開始または終了に一致するように変更します.

s   文字列を一行として扱います.つまり,"."を任意の文字や通常はマッチしない
    "改行"にもマッチするように変更されます.

    r""msのようにmとsを一緒に使うと". “は任意の文字にマッチする一方で,"^" and “$"は
    それぞれ任意の行の開始と終了文字にマッチします.

x   正規表現パーサーは,バックスラッシュでも文字クラスにも含まれないほとんどの空白を無視します.
    これを使うと,正規表現を(少し)読みやすいパーツに分割することができます.
    また,'#'文字は,通常のコードと同様に,コメントを表すメタ文字として扱われます.

例えば,次の正規表現は 3 つのフラグがすべてオンになっています:

julia> r"a+.*b+.*?d$"ism
r"a+.*b+.*?d$"ims

julia> match(r"a+.*b+.*?d$"ism, "Goodbye,\nOh, angry,\nBad world\n")
RegexMatch("angry,\nBad world")

r"..." リテラルは,補間やエスケープを行わずに構築されます(ただし,引用符 " はエスケープが必要です).以下は標準的な文字列リテラルとの違いを示した例です:

julia> x = 10
10

julia> r"$x"
r"$x"

julia> "$x"
"10"

julia> r"\x"
r"\x"

julia> "\x"
ERROR: syntax: invalid escape sequence

r"""..."""のようにトリプルクォーテーションで囲まれた正規表現の文字列もサポートされています(クオーテーションや改行文字を含む正規表現には便利です).

Regex()コンストラクタを使うと,プログラムで有効な正規表現の文字列を作成することができます.これにより,正規表現文字列を作成する際に文字列変数の内容や他の文字列操作を使用することができます.上記の正規表現コードのいずれも,Regex()の文字列引数の中で使用することができます.以下にその例を示します:

julia> using Dates

julia> d = Date(1962,7,10)
1962-07-10

julia> regex_d = Regex("Day " * string(day(d)))
r"Day 10"

julia> match(regex_d, "It happened on Day 10")
RegexMatch("Day 10")

julia> name = "Jon"
"Jon"

julia> regex_name = Regex("[\"( ]$name[\") ]")  # interpolate value of name
r"[\"( ]Jon[\") ]"

julia> match(regex_name," Jon ")
RegexMatch(" Jon ")

julia> match(regex_name,"[Jon]") === nothing
true

バイト列リテラル

もう一つの便利な非標準の文字列リテラルは,バイト配列の文字列リテラルであるb"..."です.この形式では文字列記法を用いて,読み取り専用のバイト配列リテラルを表現することができます.すなわち,UInt8値の配列です.これらのオブジェクトのタイプは,CodeUnits{UInt8, String}です.バイト配列リテラルのルールは以下の通りです:

  • ASCII 文字と ASCII エスケープは 1 バイトを生成します.
  • \xと 8 進数のエスケープシーケンスでは,エスケープ値に対応する byte が生成されます.
  • Unicode のエスケープシーケンスは,その符号位置を UTF-8 でエンコードしたバイト列を生成します.

\xと 0x80(128)以下の 8 進数エスケープの動作は,最初の 2 つのルールの両方でカバーされているため,これらのルールには重複がありますが,ここではこれらのルールは一致しています.これらのルールを合わせるとその規則により,ASCII 文字,任意のバイト値,UTF-8 シーケンスを使ってバイト配列を簡単に作成することができます.この 3 つを使った例を示します:

julia> b"DATA\xff\u2200"
8-element Base.CodeUnits{UInt8,String}:
 0x44
 0x41
 0x54
 0x41
 0xff
 0xe2
 0x88
 0x80

ASCII 文字列 "DATA "は,バイト 68,65,84,65 に対応します.\xffはシングルバイト 255 になります.Unicode のエスケープである\u2200は,UTF-8 では 226, 136, 128 の 3 バイトになります.なお,結果として得られるバイト配列は,有効な UTF-8 文字列には対応していないことに注意してください.

julia> isvalid("DATA\xff\u2200")
false

前述したように,CodeUnits{UInt8,String}型はUInt8の読み取り専用の配列のように動作します.標準的なベクトルが必要な場合は,Vector{UInt8}を使って変換できます:

julia> x = b"123"
3-element Base.CodeUnits{UInt8,String}:
 0x31
 0x32
 0x33

julia> x[1]
0x31

julia> x[1] = 0x32
ERROR: setindex! not defined for Base.CodeUnits{UInt8,String}
[...]

julia> Vector{UInt8}(x)
3-element Array{UInt8,1}:
 0x31
 0x32
 0x33

また,xffuffの大きな違いにも注目してください.前者のエスケープシーケンスは byte 255 をエンコードしているのに対し,後者のエスケープシーケンスは 符号位置 255 を表しており,UTF-8 では 2 バイトでエンコードされています.

julia> b"\xff"
1-element Base.CodeUnits{UInt8,String}:
 0xff

julia> b"\uff"
2-element Base.CodeUnits{UInt8,String}:
 0xc3
 0xbf

文字リテラルも同様の動作をします.

\u80未満の符号位置では,各符号位置の UTF-8 エンコーディングは\xに対応したエスケープからが生成されるシングルバイトであるため,この区別は安全に無視できます.しかしながら\x80\xffエスケープと\u80\uffのエスケープでは大きな違いがあります.前者のエスケープは,すべてシングルバイトをエンコードしており,特定の継続バイトが続かない限り,有効な UTF-8 データを形成しませんが,後者はすべて 2 バイトエンコードの Unicode の符号位置を表しています.

これだけではよくわからないという方は,"The Absolute Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and Character Sets”を読んでみてください. これは Unicode と UTF-8 に関する優れた入門書で,この問題に役立つかもしれません.

バージョン番号リテラル

バージョン番号は,v"..."のような非標準の文字列リテラルで簡単に表現できます.バージョン番号リテラルはsemantic versioningの仕様に従った VersionNumber オブジェクトを生成します.これは,メジャー・マイナー・パッチの数値から構成され,プレリリースやビルドの英数字による注釈が続きます.例えば,v"0.2.1-rc1+win64"はメジャーバージョン0,マイナーバージョン2,パッチバージョン1,プレリリースrc1,ビルドwin64に分けられます.

バージョンリテラルを入力する際,メジャーバージョン番号以外はすべて省略可能です.そのため,例えば v"0.2"v"0.2.0" (pre-release/build のアノテーションは空),v"2"v"2.0.0" などとなります.

VersionNumber オブジェクトは,主に 2 つの(あるいはそれ以上の)バージョンを簡単かつ正確に比較するのに役立ちます.例えば,定数 VERSION は Julia のバージョン番号を VersionNumber オブジェクトとして保持しています.そのため,以下のような簡単な記述でバージョン固有の動作を定義することができます:

if v"0.2" <= VERSION < v"0.3-"
    # do something specific to 0.2 release series
end

上記では非標準のバージョン番号 v"0.3-" が使われており,最後に - が付いていることに注意してください.この表記法は標準の Julia 拡張で,プレリリースを含めたあらゆる0.3のリリースよりも下位のバージョンを示しています.したがって上記のコードは安定した 0.2 バージョンでのみ動作し, v"0.3.0-rc1" などのバージョンは除外されます. 不安定な(つまりプレリリースの)0.2バージョンも許容するための下限チェックは,次のようにできます: v"0.2-" <= VERSION

もうひとつの非標準的なバージョン指定の拡張機能として,末尾を+ とすることでビルドバージョンの上限を表すことができます. 例えば,VERSION > v"0.2-rc1+"とすると0.2-rc1よりも上のバージョンを有効なビルドバージョンとして示しており,v"0.2-rc1+win64"ではfalsev"0.2-rc2"ではtrueを返します.

特別なバージョンを上のような比較で用いることは良いプラクティスです(特に,末尾の-は正当な理由がない限りは常に使用されるべきです)が,それらは実際のセマンティックバージョニングスキームではないので,実際のバージョン番号として使用するべきではありません.

さらにVERSIONは定数として使われている他,Pkgモジュールではパッケージのバージョンとその依存関係を指定するためにVersionNumberオブジェクトが広く使われています.

Raw 文字列リテラル

補間やアンエスケープされていない raw 文字列はraw"..."という形式の非標準文字列で表現できます.また,raw 文字列リテラルは 通常の String オブジェクトを作成します.このオブジェクトは補間やエスケープを行わずに,入力された通りの内容を含みます.これは,コードや$\を特殊文字として使用する他のマークアップ言語などを含む文字列の場合に便利です.

例外として,引用符は依然としてエスケープされなければなりません.例えば,raw"\"""\"" と等価です.すべての文字列を表現できるようにするために,バックスラッシュもエスケープする必要があります.ただし引用符の直前に記述された場合に限ります:

julia> println(raw"\\ \\\"")
\\ \"

最初の 2 つのバックスラッシュは引用符の前ではないので,出力にそのまま現れます.しかし次のバックスラッシュはそれに続くバックスラッシュをエスケープし,最後のバックスラッシュは引用符の前にあるので引用符をエスケープします.