文字列
文字列は文字の有限列です。もちろん、本当の問題は文字とは何かを考え始めたときに姿を現します。英語話者にとっての「文字」とは A
, B
, C
, ... といったアルファベットと数字およびいくつかの記号であり、こういった文字は ASCII 規格によって 0 から 127 の整数への対応関係が標準化されています。しかし当然、英語でない言語で使われる文字も多くあります。例えばアクセントなどを改変した ASCII 文字の変種、あるいはキリル文字やギリシャ文字といった英語の親戚にあたる言語の文字があり、さらに ASCII とも英語とも関係のないアラブ語・中国語・ヘブライ語・ヒンディー語・日本語・韓国語の文字も存在します。Unicode 規格は「文字とは何か」という複雑な問題に真正面から取り組むものであり、この問題を解決する決定的な規格として多くの人々に受け入れられています。必要に応じて、この複雑な問題を完全に無視してこの世には ASCII 文字しか存在しないとしてプログラムを書くか、ASCII でないテキストを処理するときに遭遇する可能性のある全ての文字とエンコーディングに対応したプログラムを書くかを選択してください。
Julia はプレーンの ASCII テキストを簡単かつ効率良く処理でき、Unicode も可能な限り簡単かつ効率良く処理できます。特に ASCII 文字列を処理するときは、C スタイルの文字列処理コードを書けば性能と動作の両方が C と同等になります。C スタイルの Julia コードが ASCII でないテキストに遭遇すると適切なエラーメッセージと共に処理が中断され、知らないうちに破損した結果が生成されることがないようになっています。このエラーが起きたときは、ASCII でないデータも処理できるようコードを書き換えた方がよいでしょう。
Julia の文字列が持つ注目に値する特徴を示します:
- Julia の文字列 (および文字列リテラル) に対する組み込みの具象型は
String
です。この型は UTF-8 エンコーディングを使った全範囲の Unicode 文字をサポートします。他の Unicode エンコーディングとの相互変換はtranscode
を使って行います。 - 全ての文字列型は抽象型
AbstractString
の部分型です。 - Julia には単一の文字を表すファーストクラスの型
AbstractChar
があります。これは C と Java と同様であり、多くの動的言語と異なります。組み込みのChar
型はAbstractChar
の部分型であり、任意の Unicode 一文字 (の UTF-8 エンコーディング) を表す 32 ビットのプリミティブ型です。 - Java と同様、Julia の文字列は不変 (immutable) です:
AbstractString
オブジェクトの値は変更できません。異なる文字列値を作成するときは、他の文字列の一部を使って新しく文字列を作成します。 - 概念的に言えば、Julia の文字列は添え字から文字への部分関数です。一部の添え字には対応する文字が存在せず、アクセスしようとすると例外が発生します。この仕様により、符号化されたバイト列に対するバイトインデックスを使った効率的なアクセスが可能となります。これに対して、文字の添え字を使ったアクセスは Unicode 文字列の可変幅エンコーディングに対して効率良く単純に実装できません。
文字
Char
型の値は単一の文字を表します: Char
は特別なリテラル表現と適切な算術演算子を持つただの 32 ビットのプリミティブ型であり、Unicode 符号位置を表す数値への変換が可能です (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
typeof(ans)
は 32 ビットアーキテクチャのシステムでは 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
までです。この符号位置の全てに明瞭な意味が付与されているわけではないので、アプリケーションで全てを解釈できる必要はないのですが、Julia では全て正当な Unicode 文字とみなされます。
Unicode 文字は一重引用符の中で \u
の後に最大四桁の十六進数を続けるか、\U
の後に最大八桁の十六進数を続けることで入力できます (最も符号位置の大きな Unicode 文字でも必要なのは六桁です):
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)
ある文字を \u
または \U
を使った一般的なエスケープ形式で出力するか、それともそのまま文字として出力するかを決めるのに、Julia はシステムのロケールと言語の設定を利用します。
Unicode のエスケープ形式に加えて、伝統的な 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"
文字列から文字を取り出すには、添え字アクセスを行います:
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)
で取得できます。添え字演算子の中では、現在の次元の最初と最後の添え字をそれぞれ begin
と end
というキーワードで参照できます。Julia における他の多くの添え字と同様、文字列の添え字は 1 始まりです。AbstractString
では firstindex(str)
が必ず 1
を返しますが、lastindex(str)
が length(str)
を返すとは限りません。一部の Unicode 文字は複数の符号単位からなるためです。
begin
と end
に対しては通常の算術演算を行えます:
julia> str[begin+1]
'e': ASCII/Unicode U+0065 (category Ll: Letter, lowercase)
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]
","
str[6]
が返すのは単一の文字を表す Char
型の値であり、str[6:6]
が返すのは単一の文字からなる文字列です。Julia においてこの二つは全く異なる値です。
区間を添え字にすると、元の文字列の選択された部分のコピーが作成されます。代わりに SubString
型を使えば、文字列に対するビューを作成できます。例を示します:
julia> str = "long string"
"long string"
julia> substr = SubString(str, 1, 4)
"long"
julia> typeof(substr)
SubString{String}
chop
, chomp
, strip
といったいくつかの標準的な関数は SubString
を返します。
Unicode と UTF-8
Julia は Unicode 文字と 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 のまま一バイトに符号化されますが、符号位置が 0x80 以上の文字は複数のバイトで符号化されます (一つの文字は最大四バイトです)。
Julia において文字列の添え字の単位は符号単位です。符号単位 (code unit) とは任意の文字 (符号位置) の符号化における固定幅の基礎単位であり、例えば 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)
s
の最初の文字 ∀
は三バイトの文字であるために添え字 2
と 3
は無効であり、次の文字の添え字は 4
となります。この 4
という値は nextind(s,1)
で計算できます。このさらに次の添え字は nextind(s,4)
で計算でき、以下同様です。
end
はコレクションに対する最後の正当な添え字を表します。そのため、最後から二文字目がマルチバイト文字のとき 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)
一つ目の s[end-1]
でエラーが起きないのは、最後の文字 y
の一つ前の文字 ' '
(空白) が一バイト文字であるためです。一方で二つ目の s[end-2]
はマルチバイト文字 ∃
の途中のバイトを指しているので、エラーが起きます。この場合の正しい添え字は prevind(s, lastindex(s), 2)
です。また s
への添え字としてなら s[prevind(s, end, 2)]
と書いても構いません (end
は lastindex(s)
へ展開されます)。
区間の添え字を使った部分文字列の切り出しが期待するのも正当な添え字 (バイトオフセット) であり、渡した添え字が正当でなければエラーが発生します:
julia> s[1:1]
"∀"
julia> s[1:2]
ERROR: StringIndexError("∀ x ∃ y", 2)
Stacktrace:
[...]
julia> s[1:4]
"∀ "
UTF-8 というエンコーディングが可変幅なので、文字列 s
に含まれる文字の数 length(s)
は最後の正当な添え字 lastindex(s)
と常に等しいわけではありません。添え字を 1 から lastindex(s)
まで増やしながら s
にアクセスしたとすれば、エラーが出ずに返った文字の列が文字列 s
に含まれる文字の全てです。それぞれの文字には添え字が存在するので、length(s) <= lastindex(s)
という不等式が成り立ちます。s
に含まれる全ての文字を反復する冗長で効率の悪い例を次に示します。出力の空白行には空白文字が一つあります:
julia> for i = firstindex(s):lastindex(s)
try
println(s[i])
catch
# 添え字エラーを無視する。
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)
関数を使います。ここで添え字 i
には 1
から ncodeunits(s)
までの値を切れ目なく指定できます。また codeunits(s)
関数は生の符号単位 (バイト) に配列としてアクセスするためのラッパー (AbstractVector{UInt8}
型の値) を返します。
Julia の文字列には無効な符号単位の列を含めることができます。このため、任意のバイト列を String
として扱うことが可能です。文字列が持つバイト列のパースでは、次のパターンのいずれかとマッチする最も長い 8 ビット符号単位の列を文字として認識しながら左から右へとパースが行われます (x
は 0
と 1
のどちらでも構いません):
0xxxxxxx
10xxxxxx
110xxxxx
10xxxxxx
1110xxxx
10xxxxxx
10xxxxxx
11110xxx
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
の最初の二つの符号単位は空白文字を冗長にエンコードしています。これは不正なバイト列ですが、一つの文字として認識されます。次の二つの符号単位 (\xe2\x88
) は三バイトの UTF-8 シーケンスの最初の二バイトとして有効ですが、五バイト目 (\xe2
) がその次のバイトとして無効です。そのため三番目と四番目の符号単位が一つの不正な形式の文字を形成します。同様に五番目の符号単位 \xe2
の後に |
が続くのは有効でないので、\xe2
が不正な形式の文字となります。二つ目の例では s2
が高すぎる符号位置を含んでいます。
Julia はデフォルトで UTF-8 エンコーディングを使いますが、新しいエンコーディングに対するサポートをパッケージから追加することもできます。例えば LegacyStrings.jl パッケージは UTF16String
型と UTF32String
型を実装します。他のエンコーディングとその実装についてここでこれ以上話すとドキュメントの目的から逸れてしまうので話しませんが、UTF-8 エンコーディングの問題はこの章のバイト列リテラルの節で触れます。種々の UTF-xx エンコーディングの間でデータを変換する transcode
関数も提供されます。この関数の主な目的は外部のデータやライブラリとのやり取りです。
連結
最もよく使われる文字列演算の一つが連結 (concatenation) です:
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 = string(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> greet * ", " * whom * ".\n"
"Hello, world.\n"
文字列の連結に +
を使う言語のユーザーは *
が選ばれているのに驚くかもしれません。文字列の連結に対する *
の利用は数学 (特に抽象代数) に先例があります。
数学では普通 +
は可換な演算を表します。可換な演算とはオペランドの順序が結果に影響しない演算のことであり、例えば行列の加算では同じ形状を持つ任意の行列 A
, B
に対して A + B == B + A
が成り立つので、行列の加算は可換です。これに対して *
は通常非可換な演算を表します。非可換な演算とは、一般に A * B != B * A
となる演算のことです。greet * whom != whom * greet
なので、行列の乗算と同様、文字列の連結は非可換です。したがって、文字列連結を表す中置演算子としては *
が一般的な数学での使われ方と一致する自然な選択となります。
さらに正確に言うと、有限長の文字列全ての集合 S と文字列連結演算 *
は自由モノイド (S, *
) を構成します。この集合の単位元は空文字列 ""
です。自由モノイドが非可換であるとき、その演算は ⋅
や *
といった記号で表記されます。そして上述の通り、+
は可換な演算を表す記号として区別されます。
補間
連結を使って文字列を構築するのが手間である場合もあります。string
や乗算 (連結) を何度も呼び出すのを避けるために、Julia の文字列リテラルでは $
を使った Perl 風の補間 (interpolation) が利用可能です:
julia> "$greet, $whom.\n"
"Hello, world.\n"
こうすれば、上で示した文字列の連結を簡単かつ読みやすく表現できます。Julia は単一の文字列リテラルに見えるこの式を string(greet, ", ", whom, ".\n")
という呼び出しに書き換えます。
$
に続く最も短い完全な式が文字列に補間される値となります。そのため、括弧を使えば任意の式を補間できます:
julia> "1 + 2 = $(1 + 2)"
"1 + 2 = 3"
文字列の連結と文字列の補間はどちらも string
を呼び出してオブジェクトを文字列に変換します。ただし string
は内部で print
を呼び出しているだけなので、新しい型は string
ではなく print
(または show
) に新しいメソッドを追加するべきです。
AbstractString
でないオブジェクトから文字列への変換では、リテラルとして入力されるときの文字列に非常に近い文字列が返ります:
julia> v = [1,2,3]
3-element Array{Int64,1}:
1
2
3
julia> "v: $v"
"v: [1, 2, 3]"
string
は AbstractString
と AbstractChar
に対する恒等写像です。そのため、これらの値を文字列に補間すると、クオートもエスケープもされない文字 (列) が挿入されます:
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.
三重クオート文字列リテラル
三重クオート """..."""
を使って三重クオート文字列リテラル (triple-quoted string literals) を作成すると、長いテキストの作成で役立つ特殊な処理が行われます。
まず、三重クオート文字列リテラルは一番小さなインデントを持つ行と同じレベルまで字下げされます。これはインデントされているコードで文字列を定義するときに便利です。例を示します:
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."
最後の空白はそのまま残されます。
三重クオート文字列リテラルには "
をエスケープせずに書くことができます。
"..."
と """..."""
の文字列リテラルの両方で、改行は改行文字 \n
(LF) になることに注意してください。エディタが \r
(CR) や CRLF で改行していたとしても関係はありません。文字列に CR を含めるには、エスケープシーケンス \r
を直接使って "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")
文字探索を指定したオフセットから開始するには findnext
関数または findprev
関数を使います:
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
は文字リテラル (の表す Char
型の値) が文字列に含まれるかどうかを判定するのにも利用できます。
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)
はi
からj
の間にあるstr
に対する添え字として正当な整数の個数を返します。 -
ncodeunits(str)
は文字列に含まれる符号単位の数を返します。 -
codeunit(str, i)
はstr
の添え字i
にある符号単位の値を返します。 -
thisind(str, i)
は添え字i
が指す符号単位を含む文字の最初の添え字を返します。 -
nextind(str, i, n=1)
は添え字i
が指す符号単位を含む文字の次の文字から数えてn
番目の文字が始まる添え字を返します。 -
prevind(str, i, n=1)
は添え字i
が指す符号単位を含む文字から後に数えてn
番目の文字が始まる添え字を返します。
非標準文字列リテラル
標準の構文を使うと上手く作成できない文字列リテラルが必要になったり、文字列の意味論を使った他のオブジェクトの作成が必要になる場合があります。そのような場合には Julia の非標準文字列リテラル (non-standard String Literal) という機能が利用できます。非標準文字列リテラルは二重引用符で囲まれた通常の文字列の前に識別子を付けたものであり、通常の文字列リテラルとは異なる動作をします。以下で説明する正規表現・バイト列リテラル・バージョン番号リテラルは非標準文字列リテラルの例であり、その他の例はメタプログラミングの章で説明されます。
正規表現
Julia は Perl 互換の正規表現 (regular expression, regex) を持ちます。実装には PCRE ライブラリが使われており、構文は PCRE のドキュメントから確認できます。正規表現は二つの意味で文字列と関係を持ちます。一つは正規表現が文字列中のパターンを見つけるのに使われるという明らかな関係であり、もう一つは正規表現自体が文字列を使って表されるという関係です。正規表現を表す文字列はパースされて状態機械となり、この状態機械が文字列中のパターンの効率的な探索に利用されます。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
が返すのは第一引数の正規表現が第二引数の文字列中に現れるかどうかを表す真偽値だけです。しかし通常はマッチしたかどうかだけではなく、どのようにマッチしたかの情報が必要とされます。マッチに関する情報を手に入れるには、代わりに match
関数を使います:
julia> match(r"^\s*(?:#|$)", "not a comment")
julia> match(r"^\s*(?:#|$)", "# a comment")
RegexMatch("#")
正規表現が文字列にマッチしないと、match
は nothing
を返します。nothing
は特別な値であり、対話プロンプトに表示されません。対話プロンプトに表示されない点以外は完全に通常の値と同じなので、プログラムから等価性を判断することもできます:
m = match(r"^\s*(?:#|$)", line)
if m === nothing
println("not a comment")
else
println("blank or comment")
end
正規表現がマッチした場合には、match
は RegexMatch
オブジェクトを返します。このオブジェクトは正規表現がどのようにマッチしたかを記録し、例えばマッチした部分文字列やキャプチャされた部分文字列といった情報を含みます。上の例ではマッチした部分文字列全体だけをキャプチャしていますが、コメント文字の後ろにある空白でないテキストをキャプチャする必要があるかもしれません。そのときは次のようにできます:
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
の位置には 0 が入ります (Julia では添え字が 1 始まりなので、添え字 0 を使った文字列へのアクセスは無効です)。少し入り組んだ例を示します:
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
で文字列を置換するときは、置換文字列の前に s
を付ければ \n
で n
番目のキャプチャグループを表せます。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"
正規表現を表すリテラルを閉じる最後の二乗引用符の後にフラグ i
, m
, s
, x
の組み合わせを書くことで、正規表現の動作を変更できます。これらのフラグの意味は Perl と同じです。perlre の man ページ にある解説をここに示します:
[訳注: 権利の都合により、man ページをここに転載/翻訳することはできませんでした。リンク先の英語ドキュメント、または perldoc.jp による翻訳を参照してください。]
三つのフラグを有効にして正規表現を使う例を示します:
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[\") ]") # name の値を補間
r"[\"( ]Jon[\") ]"
julia> match(regex_name," Jon ")
RegexMatch(" Jon ")
julia> match(regex_name,"[Jon]") === nothing
true
バイト配列リテラル
もう一つの便利な非表示文字列リテラルがバイト配列文字列リテラル (byte-array string literal) b"..."
です。これを使うと、文字列を使って読み込み専用のバイト (UInt8
) 配列を構築できます。構築されるオブジェクトの型は CodeUnits{UInt8, String}
です。バイト配列リテラルの規則は次の通りです:
- ASCII 文字と ASCII エスケープ文字は単一のバイトを生成する。
- 十六進数および八進数を使ったエスケープシーケンス (
\x
,\0
) は、それが表す値を生成する。 - Unicode エスケープシーケンス (
\U
,\u
) は、その符号位置を UTF-8 でエンコードしたバイト列を生成する。
0x80 (128) より小さいバイトは最初の二つの規則のどちらを使っても生成できるので規則に重複はありますが、矛盾はありません。この三つの規則を組み合わせると、ASCII 文字・任意のバイト値・UTF-8 シーケンスからバイト列を簡単に生成できます。バイト配列リテラルの使用例を示します:
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 となります。こうしてできるバイト配列が正当な 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
ここで \xff
と \uff
の重要な違いの理解を確認してください。\xff
はバイト 255 そのものを表すエスケープシーケンスであり、\uff
は符号位置 255 を UTF-8 でエンコードしたバイト列を表すエスケープシーケンスです:
julia> b"\xff"
1-element Base.CodeUnits{UInt8,String}:
0xff
julia> b"\uff"
2-element Base.CodeUnits{UInt8,String}:
0xc3
0xbf
文字リテラルでも同様です。
\u80
より小さい符号位置に対する UTF-8 エンコーディングはたまたま符号位置からなる単一のバイトなので、この範囲に限っては \xXX
と \uXX
が一致します。しかし \x80
から \xff
と \u80
から \uff
では、大きな違いがあります: 前者が生成するのは単一のバイトであり、(継続バイトが続く少数の例外を除いて) 正当な UTF-8 データを構成しません。これに対して後者は Unicode 符号位置を表す正当な二バイトのエンコーディングを必ず生成します。
こういった話がさっぱり理解できない場合には、"The Absolute Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and Character Sets" を読んでみてください。これは Unicode と UTF-8 への素晴らしい入門記事であり、この話題に関して混乱した頭を整理するのに役立つでしょう。
バージョン番号リテラル
バージョン番号は v"..."
の形をしたバージョン番号リテラルと呼ばれる非標準文字列リテラルを使って簡単に表現できます。バージョン番号リテラルはセマンティックバージョニングの仕様に従う VersionNumber
型のオブジェクトを作成します。このオブジェクトはメジャー・マイナー・パッチバージョンを表す三つの数値と、リリースやビルドの状態を表す英数字からなる文字列を持ちます。例えば v"0.2.1-rc1+win64"
はメジャーバージョン 0
, マイナーバージョン 2
, パッチバージョン 1
, リリース状態 rc1
, ビルド win64
であるバージョンを表します。
バージョン番号リテラルではメジャーバージョン番号以外を全て省略できます。例えば v"0.2"
は v"0.2.0"
(リリース状態やビルドに関する注釈を持たないバージョン番号) と等価であり、v"2"
は v"2.0.0"
と等価です。
VersionNumber
オブジェクトは二つのバージョンを比較するときに最も活躍します。比較は簡単かつ正確に行えます。例えば定数 VERSION
は Julia のバージョン番号を VersionNumber
オブジェクトとして保持するので、あるバージョンに特有の振る舞いを次の簡単なコードで書くことができます:
if v"0.2" <= VERSION < v"0.3-"
# 0.2 リリース系列に特有な処理を行う
end
上の例では最後に -
が付いた非標準的なバージョン番号 v"0.3-"
が使われています。これは Julia による規格の拡張であり、任意の 0.3
リリース (プレリリースを含む) より前のバージョンを表します。つまり上のコードの if
文の中身は安定版の 0.2
バージョンでだけ実行され、v"0.3.0-rc1"
のようなバージョンでは実行されません。もし安定でない (プレリリースの) 0.2
バージョンでもコードを実行したいときは、下限のチェックを v"0.2-" <= VERSION
と変更することになります。
非標準なバージョン記法にはもう一つ、最後に +
を付けるものがあります。これはビルドバージョンの上限を表し、例えば VERSION > v"0.2-rc1+"
は現在のバージョンが 0.2-rc1
(の任意のビルド) より高いときに true
となります。つまり VERSION
が v"0.2-rc1+win64"
のとき false
で、 v"0.2-rc2"
のとき true
です。
比較ではこういった特殊なバージョン番号を使うのが良い習慣です (特に、比較の上限には特別な理由がない限り最後に -
を付けるべきです)。しかし、特殊なバージョン番号はセマンティックバージョニングの規格から外れているので、本当のバージョンとして使ってはいけません。
定数 VERSION
の他にも、VersionNumber
オブジェクトは Pkg
モジュールの中でパッケージのバージョンや依存関係を指定するために広く使われています。
生文字列リテラル
補間やエスケープの処理を行わない "生" (raw) な文字列は非標準文字列リテラル raw"..."
を使って表現できます。生文字列リテラルは通常の String
オブジェクトを作成し、そのとき補間とエスケープの処理を行いません。$
や \
を特殊な文字として使う他の言語のコードやマークアップを文字列として使うときに便利です。
二重引用符は例外で、生文字列リテラルでもエスケープが必要です。つまり raw"\""
は "\""
と等価です。また全ての文字列を表現できるように、二重引用符の直前に付くバックスラッシュにもエスケープが必要となっています:
julia> println(raw"\\ \\\"")
\\ \"
最初の二つのバックスラッシュには二重引用符が続かないのでそのまま文字列に出力されます。一方、三つ目のバックスラッシュはその次のバックスラッシュをエスケープし、最後のバックスラッシュは二重引用符をエスケープします。最後の三つのバックスラッシュがこういった振る舞いとなるのは、二重引用符の前に現れているためです。