文字列
文字列とは、文字の有限列のことです。 しかし、文字とは何かと自問すると、なかなか厄介です。 英語圏で馴染みのある文字は、A
、B
、C
などとか、数字とか、句読点などの普通の記号でしょう。 こうした文字はまとめて、0から127までの整数値を対応付けるASCII 規格として標準化されています。 当然、非英語圏には、多数の別の文字があります。 アクセントや修飾文字をつけた、ASCII文字の変種、近縁の言語であるキリル文字やギリシア文字、 ASCIIや英語に全く無縁の言語であるアラビア語、中国語、ヘブライ語、ヒンディ語、日本語、韓国語などです。 ユニコード規格は、文字とは正確には何なのかという複雑さに挑んだ結果、 最も信頼のおける規格として広く受け入れられています。 必要に応じて、複雑さを全く無視してASCII文字しか存在しないような振りもできますし、 ASCII以外の文書を扱う時に出会う、どんな文字やエンコードでも扱えるコードを書くこともできます。 Juliaでは、ASCIIの文書の扱いが簡単で効率がいいですし、Unicodeの扱いもできるだけ簡単で効率的にしようとしています。 特に、C言語流でASCII文字列を処理するコードを書くが可能で、パフォーマンス的にもセマンティック的にも期待通りに動作します。 こうしたコードは非ASCII文書に出会うと、明快なエラーメッセージを出して制御のもとに終了し、黙ったまま壊れた結果を取り込もうとはしません。 こうした場合に、非ASCIIデータを扱えるようにコードを変更するのは簡単です。
Juliaの文字列には、顕著な高水準の特徴がいくつかあります。
- Juliaで文字列(や文字列リテラル)に使う組込みの複合型が
String
です。 Unicode 全範囲の文字がUTF-8 エンコーディングで利用可能です。 (関数transcode
が他のユニコードのエンコーディングとの双方向の変換に利用可能です。) - すべての文字列型は抽象型
AbstractString
のサブタイプであり、外部パッケージで追加の(別のエンコーディングなどの)AbstractString
のサブタイプを定義します。 引数として文字列を想定する関数を作るときは、どんな文字列でも受け取れるように、型宣言はAbstractString
にすべきでしょう。 - C言語やJavaと同様に、しかし大概の動的言語とは違って、Juliaには、文字1字を表す
AbstractChar
と呼ばれる、 第一級の型があります。 32bitのプリミティブ型である組込み型のChar
は、AbstractChar
のサブタイプで、 任意のユニコードの文字を表現できます(UTF-8エンコードに基づいています)。 - Javaと同様に文字列は不変です。
AbstractString
オブジェクトの値はかえられません。 別の文字列を構成するには別の文字列の一部から構成します。 - 概念的には、文字列はインデックスから文字への 部分関数 です。 インデックスの一部は文字の値を返さず、かわりに例外を投げます。 このためエンコード表現のバイトインデックスも利用でき、効率的な文字列のインデックス呼び出しが可能になります。 文字によるインデックスだと、ユニコード文字列はエンコーディングの変数の幅が変わるので、効率的かつ単純さとはならないのです。
文字
Char
の値は1文字を表しています。 これは32bitのプリミティブ型で、特別なリテラル表現をもち、算術が妥当な場合は可能です。 そして、符号位置を表す数値に変換する事ができます。 (Juliaのパッケージでは別のAbstractChar
のサブタイプを定義することができます。 例えば、別の文字エンコーディングの操作に最適化されたものなど) 以下にChar
の値の入力・表示の方法を示します。
julia> 'x'
'x': ASCII/Unicode U+0078 (category Ll: Letter, lowercase)
julia> typeof(ans)
Char
You can easily convert a Char
to its integer value, i.e. code point:
julia> Int('x')
120
julia> typeof(ans)
Int64
32bitアーキテクチャなら、typeof(ans)
はInt32
になるでしょう。 整数値をChar
に戻すことも簡単にできます。
julia> Char(120)
'x': ASCII/Unicode U+0078 (category Ll: Letter, lowercase)
すべての整数値に有効なユニコードの符号位置が割り当てられているわけではありません。 しかしパフォーマンスをあげるため、Char
の変換は文字の値が有効かどうかは検査しません。 各変換値が有効な符号位置かどうか調べるには、isvalid
関数を利用してください。
julia> Char(0x110000)
'\U110000': Unicode U+110000 (category In: Invalid, too high)
julia> isvalid(Char, 0x110000)
false
執筆時点では有効なユニコードの符号位置は、U+00
からU+d7ff
までとU+e000
からU+10ffff
までです。 この値は分かりやすい意味があるわけもなく、必ずしもアプリケーションが解釈できるわけではないですが、 すべて有効なユニコード文字だと考えられています。 ユニコードとして入力可能なのは、一重引用符で囲んだ、\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)
文字列の基本
文字列リテラルは、二重引用符、または、3連二重引用符で区切ることができます。
julia> str = "Hello, world.\n"
"Hello, world.\n"
julia> """Contains "quote" characters"""
"Contains \"quote\" characters"
文字列から文字を抜き出したいときは、インデックスが使えます。
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)
で得られます。 キーワードend
は、インデックス操作時に該当次元の最後のインデックスを示す簡略記法として利用できます。 Juliaでは殆どのインデックスが1を基準としています。 整数でインデックス付けされたオブジェクトの多くは、最初の要素のインデックスが1です。 (しかし、あとで見るように、文字列の長さn
が、そのまま最後の要素のインデックスとなるわけでは、必ずしもありません。)
julia> str[end-1]
'.': ASCII/Unicode U+002e (category Po: Punctuation, other)
julia> str[end÷2]
' ': ASCII/Unicode U+0020 (category Zs: Separator, space)
インデックスが1より小さい場合やend
より大きい場合はエラーが生じます。
julia> str[0]
ERROR: BoundsError: attempt to access "Hello, world.\n"
at index [0]
[...]
julia> str[end+1]
ERROR: BoundsError: attempt to access "Hello, world.\n"
at index [15]
Stacktrace:
[...]
部分文字列を範囲インデックスを使って抜き出すこともできます。
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字しかない文字列の値です Juliaではこれらは全く別物です。
範囲インデックスでは、元の文字列の選択部分のコピーを作成します。 別の方法として、SubString
型を使って、文字列に対するビューを作成することも可能です。
julia> str = "long string"
"long string"
julia> substr = SubString(str, 1, 4)
"long"
julia> typeof(substr)
SubString{String}
いくつかの標準的な関数chop
、chomp
、 strip
などは、 SubString
の型を返します。
ユニコードとUTF-8
Juliaはユニコードの文字と文字列に完全対応しています。 上述のように、ユニコードの\u
や\U
のエスケープシーケンスや、 すべての標準的なC言語のエスケープシーケンスなどの文字リテラルを使って、ユニコードの符号位置を表現することができます。 これらは、文字列リテラルにも利用できます。
julia> s = "\u2200 x \u2203 y"
"∀ x ∃ y"
ユニコードの文字がエスケープされて表示されるか、特殊な文字で表示されるかは、 ターミナルのロケールの設定や、ユニコードへの対応状況に依存します。 文字列リテラルは、UTF-8でエンコードされます。 UTF-8は可変長のエンコーディングです。つまり、すべての文字が同じバイト数にエンコードされるわけではありません。 UTF-8ではASCII文字、つまり符号位置が0x80 (128)未満の文字は、 ASCIIのまま1バイトでエンコードされます。 他方符号位置が0x80 (128)以上の文字は、1字あたり4文字以下の複数の文字でエンコードされます。 このため、UTF-8の文字列に対するバイトインデックスすべてが、有効な文字へのインデックスとは限りません。 もし文字列に対して、そんな無効なインデックス呼び出しをすれば、エラーが投げられます。
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)
と続きます。
部分文字列を抜き出す範囲インデックスも有効なインデックスを想定しており、無効であればエラーが生じます。
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
Juliaでは、UTF-8の文字列としては有効ではないバイト列を文字列に含むことができます。 このしくみによって、任意のバイト列をString
として扱うことができます。 そんな条件のもと、左から右へバイト列を解析する規則は、 下記のビットパターンから始まる最長のバイト列を取り出すことです。 (各x
は0
でも1
でもよい)
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}:
false
false
false
true
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
は符号位置が大きすぎます。
JuliaはデフォルトでUTF-8でエンコーディングを行いますが、パッケージを加えて新しいエンコーディングに対応することができます。 例えば、LegacyStrings.jlパッケージは、UTF16String
型やUTF32String
型を実装しています。 別のエンコーディングや、それに対応するための実装方法に関する議論は、当面、この文書の範囲外です。 UTF-8エンコーディングの問題に関するさらなる議論は、下記セクションのバイト列リテラルを参照してください。 transcode
関数は、様々なUTF-xxエンコーディング間でデータを変換し、主に外部のデータやライブラリと共に動作させます。
連結
最も普通で役に立つ文字列操作の一つが、連結です。
julia> greet = "Hello"
"Hello"
julia> whom = "world"
"world"
julia> string(greet, ", ", whom, ".\n")
"Hello, world.\n"
無効な文字列を連結する場合に起こりうる潜在的な危険に注意を払うことは重要です。 連結した文字列は、入力した文字列と変わるかもしれないし、文字数がもとの文字列の文字数の合計よりも、少なくなるかもしれません。 例えば、
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> greet * ", " * whom * ".\n"
"Hello, world.\n"
文字列の連結に+
を使う言語のユーザーは、*
を使う選択にびっくりするかもしれませんが、 これには数学、特に抽象代数に先例があります。
数学では、+
はたいてい 可換的 操作を表し、演算対象の順序は影響しません。 例として行列の足し算の場合は、形の同じ任意の行列 A
とB
に対して、A + B == B + A
が成り立ちます。 一方、*
ふつうは 非可換的 操作を表し、演算対象の順序は影響します。 例として行列の掛け算の場合は、一般には、行列 A
とB
に対して、A * B != B * A
が成り立ちます。 行列の掛け算と同様に文字列の連結も非可換です。 greet * whom != whom * greet
です。 よって、通常の数学での用例との一貫性を考えると、文字列連結の中置演算には、*
を選択するほうが、より自然なのです。
更に正確には、すべての有限長の文字列の集合 S と 文字列連結演算子 *
は、 自由モノイド (S, *
)を形成します。 単位元は空の文字列""
です。 自由モノイドが非可換の時は、演算子は通常、\cdot
、*
などの記号で表し、通常、可換性を示唆する+
は使いません。
文字列展開
しかし、文字列の構成に連結を使うと厄介になる場合もあります。string
の冗長な呼び出しや乗算記号の反復をへらすために、 Juliaでは、Perlのように$
を使った、文字列リテラルの展開が可能です。
julia> "$greet, $whom.\n"
"Hello, world.\n"
これは、読みやすく、便利で、上記の文字列連結と同等です。 これは、システムが、見た目は1つの文字列リテラルを、変数を持つ文字列リテラルの連結に書き換えているのです。
$
に続く最短の完全な式を、展開の対象とみなすので、括弧を使えば任意の式を展開することができます。
julia> "1 + 2 = $(1 + 2)"
"1 + 2 = 3"
連結も文字列展開もstring
を呼び出して、オブジェクトを文字列に変換します。 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.
三連クオート文字列リテラル
3連クオート("""..."""
)を使って生成した文字列は、長い文章のブロックを作るのに役立つ特殊な挙動をします。
まず、3連クオート文字列、1番インデントの少ない行に合わせて、インデントを除去します。 これは、コード内にインデントのある文字列を定義するのに便利です。例えば、
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."
続きの空白は変わらないままです。
3連クオートには、エスケープなしで"
記号を入れることができます。
注意が必要なのは、文字列中の改行は、クオートが1連でも3連でも、ラインフィード(LF)文字の\n
になります。 エディタの設定がキャリッジリターン(CR)\r
やC組み合わせの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
関数を使って検索できます。
julia> findfirst(isequal('x'), "xylophone")
1
julia> findfirst(isequal('p'), "xylophone")
5
julia> findfirst(isequal('z'), "xylophone")
findnext
は3番目の引数で指定した数だけ、検索の開始位置をずらすことができます。
julia> findnext(isequal('o'), "xylophone", 1)
4
julia> findnext(isequal('o'), "xylophone", 5)
7
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
が、文字リテラルを探す時にも利用できることを示しています。
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)
では、 文字列中の符号単位の数が得られます。codeunit(str, i)
では、文字列str
のインデックスがi
符号単位の値が得られます。thisind(str, i)
では、任意のインデックスに対して、その指し示す文字に対する最初のインデックスが得られます。nextind(str, i, n=1)
インデックスi
の文字の'n
個後ろの文字の先頭のインデックスか得られます。prevind(str, i, n=1)
インデックスi
の文字の'n
個前の文字の先頭のインデックスか得られます。
非標準文字列リテラル
文字列は使いたいけど、通常の使い方では、要求にそぐわないという状況もあるでしょう。 そんな場合のために、Juliaには、 非標準文字列リテラルが用意されています。 非標準文字列リテラルは、ふつうの二重引用符による文字列リテラルに似ていますが、識別用の文字が頭についていて、挙動が通常の文字とは異なります。 正規表現、バイト列リテラル、バージョン番号リテラルなど、非標準文字列リテラルの例をこれからいくつか見ていきます。 他の例はメタプログラミング セクションで扱います。
正規表現
JuliaではPerl互換の正規表現が利用可能で、PCREライブラリを利用しています。 正規表現と文字列には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
は単に正規表現が文字列と一致するかどうか真偽値を返すだけです。 しかし、通常は、単に一致しているかどうかだけでなく、どのように 一致しているかを知りたいものです。 一致に関する情報を捕捉するには、 match
関数をかわりに使います。
julia> match(r"^\s*(?:#|$)", "not a comment")
julia> match(r"^\s*(?:#|$)", "# a comment")
RegexMatch("#")
正規表現が文字列と一致しない場合、match
関数は 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のインデックスは無効であることを思い出してください) ちょっと不自然な例を2,3挙げます。
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"
正規表現の挙動を変えるには、i
, m
,s
,x
といったフラグの組み合わせを、閉じるほうの二重引用符の後ろにつけます。 フラグの意味はPerlと同じなので、説明を perlre manpageから抜粋します。
例えば、以下の正規表現では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
3連クオートの正規表現も、r"""..."""
といった形で利用可能です。 (二重引用符や改行のある正規表現で便利かもしれません)
バイト列リテラル
もう一つの役立つ非標準の文字列リテラルが、b"..."
といったバイト列文字列リテラルです。 この形式を使うと、読み取り専用のバイト列リテラル、つまりUInt8
の値の配列を文字列表記で、 表現することができます。 このオブジェクトの型は、CodeUnits{UInt8, String}
です。 バイト列リテラルの規則は以下のようになります。
- ASCII文字や、エスケープしたASCII文字は、1バイトを生成する。
\x
や8進エスケープした文字は、エスケープした値に対応する バイト を生成する。- ニコードのエスケープ列は、その符号位置をUTF-8でエンコーディングしたバイト列を生成する。
この規則には、重なりがあり、x
と8進エスケープは0x80 (128)では始めの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
からは、1バイト255が生成されます。 \u2200
はUTF-8エンコードの3バイト 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
には重大な違いがあります。 前者のエスケープシーケンスは バイト 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 エンコーディングは1バイトで、対応する \x
エスケープで生成されます。 そのため、この違いを無視しても安全です。 しかし\x80
から\xff
までと、\u80
から\uff
までを比較すると、大きな違いがあります。 前者はすべて1バイトにエンコードされますが、ごく一部の連なりを除いて、有効なUTF-8のデータではありません。 一方、後者は、すべてユニコードの符号位置を表現し、2バイトにエンコードされます。
もし大混乱しているようなら、 "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
オブジェクトはたいていの場合、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"
に対してはfalse
、v"0.2-rc2"
に対してはture
を返します。
このような特殊なバージョンを使った比較は、優れて実践的です(特に末尾に-
をつけた上限は、理由のない限り常に使うべきでしょう)。 しかしセマンティックバージョニングの方式としては無効なものなので、実際のバージョン番号にはいずれも使用できません。
VERSION
定数に使う以外に、VersionNumber
オブジェクトはPkg
モジュールで、 パッケージのバージョンとその依存関係を指定するために、広く利用されています。
生文字列リテラル
式展開やエスケープの処理を行わない生文字列は、raw"..."
という形の非標準の文字列リテラルとして表現可能です。 生文字列リテラルは、普通のString
オブジェクトで、引用符の中身を、式展開やエスケープ処理をせず、そのまま含んでいます。 これは、$
や \
を特殊な文字として使う、コードやマークアップ言語を含む文字列に役立ちます。
例外として、引用符はエスケープ処理が必要です。 つまり、raw"\""
は"\""
と同等です。 すべての文字列を表現するには、バックスラッシュにはエスケープ処理が必要ですが、引用符の直前にあるときだけです。
julia> println(raw"\\ \\\"")
\\ \"
始めの2つのバックスラッシュは、、引用符の直前にはないので、そのまま出力される点に注意してください。 しかし、次のバックスラッシュは直後のバックスラッシュをエスケープし、最後のバックスラッシュは引用符を エスケープします。これらのバックスラッシュは引用符の直前に出現するからです。