文字列
文字列は、文字の有限列です。しかし文字とは何であるか、と問うてみると問題は複雑であることに気づきます。英語を話す人にとってよく知る文字とは、文字A
、B
、C
等と数字や一般的な句読点です。これらの文字と0から127までの整数値とのマッピングはASCII標準によって標準化されています。もちろん、文字には、英語以外で使われる数々の文字が含まれるわけです。アクセントやその他の変更を加えた変形ASCII文字、キリル文字やギリシャ語などの関連スクリプト、ASCIIや英語とは全く関係のない言語体系である、アラビア語、中国語、ヘブライ語、ヒンディー語、日本語、韓国語などです。Unicode標準は、文字が正確に何であるかの複雑さに取り組み、この問題に対処する決定的な標準として広く受け入れられています。ニーズに応じて、これらの複雑さを完全に無視して、ASCII 文字のみが存在するふりをするか、ASCII 以外のテキストを処理するときに発生する可能性のある文字またはエンコーディングを扱うコードを記述できます。Julia はプレーンな ASCII テキストをシンプルかつ効率的に処理し、Unicode の処理も可能な限りシンプルで効率的に処理します。特に、ASCII 文字列を処理するときには、C スタイルの文字列コードを記述でき、パフォーマンスとセマンティクスの両方で期待どおりに動作します。このようなコードで ASCII 以外のテキストが検出された場合は、間違った結果をただ返すのではなく明確なエラー メッセージで正常に失敗します。この場合、ASCII 以外のデータを処理するようにコードを変更するのは簡単です。
Juliaには、文字列に関係するいくつかの注目すべき高度な機能があります:
- ジュリアの文字列 (および文字列リテラル) に使用される組み込みの具体的な型は
String
です。 これはUTF-8エンコーディングによるUnicodeの全範囲をサポートしています。(他の Unicode エンコーディングとの間で変換するためにtranscode
関数が提供されます。 - すべての文字列型は抽象型
AbstractString
のサブタイプであり、外部パッケージは追加のAbstractString
サブタイプを定義します (例えば他のエンコーディング用などに)。 文字列引数を必要とする関数を定義する場合は、任意の文字列型を受け入れるために、型をAbstractString
として宣言する必要があります。 - C や Java と同様に、そしてほとんどの動的言語とは異なり、Julia には
AbstractChar
と呼ばれる単一の文字を表すファーストクラスの型があります。AbstractChar
の組み込みのChar
サブタイプは、任意の Unicode 文字を表すことができる 32 ビット プリミティブ型です (UTF-8 エンコーディングに基づいています)。 - Java と同様に、文字列は不変(イミュータブル)であり、
AbstractString
オブジェクトの値は変更できません。 別の文字列値を作成するには、他の文字列の一部から新しい文字列を作成します。 - 概念的には、文字列はインデックスから文字への 部分関数 です。これにより、文字インデックスではなく、エンコードされた表現のバイト インデックスによって文字列に効率的にインデックスを作成できます。
文字
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
32 ビット アーキテクチャでは、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+00
から U+d7ff
と U+e000
から U+10ffff
です。これらはすべてまだわかりやすい意味を割り当てられていないし、アプリケーションによって必ずしも解釈できるわけではありませんが、これらの値はすべて有効な Unicode 文字と見なされます。
単一引用符の中で、\u
を使用してその後に16進数で最大 4桁、または \U
の後に16進数で最大 8 桁(最長有効値は 6 桁)で、任意の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)
Julia は、システムのロケールと言語の設定を使用して、どの文字をそのまま支障なく出力できるか、どの文字が、\u
や \U
を使った一般系で出力する必要があるかを判断します。これらの 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[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 から始まります: どんなAbsdtractString
型に対しても、firstindex
はいつも 1
を返します。 (後で示しますが、しかしながら、lastindex(str)
は length(str)
とは一般的に同じでは ありません。一部のユニコード文字は、複数の符号単位を専有することがあるからです)
通常の値と同様に、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)
1より小さいインデックスや、end
より大きいインデックスを使うとエラーが発生します:
julia> str[0]
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 文字だけを含む文字列値です。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 文字列を完全にサポートしています。前述のように、文字リテラルでは、すべての標準 C エスケープ シーケンスと同様に、Unicode \u
,\U
エスケープ シーケンスを使ってUnicode の符号位置を表現することができます。すべての標準 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のバイト)を参照します。これは、固定長の構成要素で、任意の文字(コードポイント)をエンコードするのに使われます。つまり、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)
と続きます。
Since end
is always the last valid index into a collection, end-1
references an invalid byte index if the second-to-last character is multibyte.
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)
The first case works, because the last character y
and the space are one-byte characters, whereas end-2
indexes into the middle of the ∃
multibyte representation. The correct way for this case is using prevind(s, lastindex(s), 2)
or, if you're using that value to index into s
you can write s[prevind(s, end, 2)]
and end
expands to lastindex(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
If you need to obtain valid indices for a string, you can use the nextind
and prevind
functions to increment/decrement to the next/previous valid index, as mentioned above. You can also use the eachindex
function to iterate over the valid character indices:
julia> collect(eachindex(s))
7-element Array{Int64,1}:
1
4
5
6
7
10
11
To access the raw code units (bytes for UTF-8) of the encoding, you can use the codeunit(s,i)
function, where the index i
runs consecutively from 1
to ncodeunits(s)
. The codeunits(s)
function returns an AbstractVector{UInt8}
wrapper that lets you access these raw codeunits (bytes) as an array.
Julia の文字列には、無効なUTF-8コード単位シーケンスを含めることができます。この仕組みによって、任意のバイト シーケンスを String
として扱うことができます。このような状況では、左から右の文字までのコード単位のシーケンスを解析する場合、文字列は、次のいずれかのビット パターンの先頭に一致し、最も長い8 ビット コード単位のシーケンスよって形成されます (各 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}:
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バイト文字として受け入れられます。その次(3,4番め)の符号単位は、3バイトのUTFF-8文字の始まりとして有効ですが、5つめの符号単位\xe2
はその続きとして無効です。したがって3,4番めの符号単位も文字列の一部として不正な形式ということになります。5つめの符号単位の続きに|
がくることはなく有効でないので、5番目の符号単位も不正な形式です。最後に、文字列s2
は、符号位置が大きすぎます。
Julia はデフォルトで UTF-8 エンコーディングを使用しますが、パッケージを加えて、その他に新しいエンコーディングのサポートを追加することができます。 たとえば、LegacyStrings.jl
パッケージはUTF16String
と UTF32String
型を実装します。他のエンコーディングとそのサポートの実装方法については、当面はこのドキュメントでは説明しません。UTF-8 エンコーディングの問題の詳細については、以下のバイト配列リテラルのセクションを参照してください。 transcode
関数は、主に外部データやライブラリを操作するために、さまざまなUTF-xxエンコーディング間でデータを変換するために提供されます。
連結
最も一般的で有用な文字列操作の 1 つは、連結です:
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> greet * ", " * whom * ".\n"
"Hello, world.\n"
文字列連結に +
を使う言語のユーザーにとって、これは驚くべき選択肢のように思えるかもしれませんが、この *
の使用は、特に抽象代数での先例があります。
数学では、+
は通常、可換演算を示し、そこでは、オペランド順序は関係ありません。これに対応する例は、行列の足し算で、同じ形状を持つ行列 A
と B
に対して A + B = B + A
が成り立ちます。対照的に、*
は通常、非可換演算を示し、そこでは、オペランドの順序が重要です。これに対応する例は行列の乗算で、一般的には A * B != B * A
です。行列乗算と同様に、文字列連結は非可換です: 挨拶 * 誰が *誰が * 挨拶する
そのため、*
は、一般的な数学での使用例とも一致し、文字列連結の二項演算子として、より自然な選択です。
より正確には、すべての有限長文字列 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
ではなく、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.
三重引用符文字列リテラル
文字列を三重引用符 ("""..."""
) を使用して作成すると、長いテキスト ブロックを作成するのに役立つ特殊な挙動をします。
まず、三重引用符で囲まれた文字列も、最もインデントの少ない行までデデントされます。 これは、インデントされたコード内で文字列を定義する場合に便利です。例えば:
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
になるということです。単一引用符と三 重引用符のどちらを使用する場合でも、エディターが改行 \r
(CR) や、 CRLF を使用して行を終了する場合でも、この動作は変わりません。CR を文字列に含めるには、明示的なエスケープ \r
を使用します。たとえば、リテラル文字列 "CRLF 行末\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
も文字リテラルを探すことができる、という例です。
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)
は 任意のバイトインデックスi
に対して、そのインデックスが指すバイトデータが属する文字の最初のインデックスが得られます。nextind(str, i, n=1)
でインデックスi
から後ろに数えて、n
個めの文字の最初のインデックスが得られます。prevind(str, i, n=1)
でインデックスi
から前に数えて、n
個めの文字の最初のインデックスが得られます。
非標準文字列リテラル
文字列を作成さいたり使用するけれども、標準的な文字列構成の振る舞いが、その時必要とされているものとは違う、という状況があるでしょう。このような状況のために、Julia は 非標準文字列リテラルを提供しています。非標準の文字列リテラルは、通常の二重引用符で囲まれた文字列リテラルのように見えますが、リテラルの前に識別子がつけられていて、通常の文字列リテラルとは異なる動作をします。正規表現、バイト配列リテラル、およびバージョン番号リテラルは、以下に説明するように、非標準文字列リテラルの例です。その他の例はメタプログラミングセクションで示されています。
正規表現
Julia には Perl 互換の正規表現 (正規表現) があり、PCRE ライブラリによって提供されます。(構文に関する説明は、ここを参照)正規表現は 2 つの点で文字列に関連しています: 明らかな関連は、正規表現が文字列内の規則的なパターンを見つけるために使用されるということです。もう 1 つは、正規表現自体が文字列として入力され、文字列内のパターンを効率的に検索するためのステート マシンとして解析されることです。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("#")
正規表現が指定された文字列にマッチしない場合、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 から始まっているので、文字列へのゼロ オフセットは無効です)。やや工夫された例を次に示します:
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の意味と同じです:
i 大文字と小文字を区別しないでパターンマッチを行います。
ロケールマッチングが有効な場合、符号位置 255以下に対しては、現在のロケールから、255より大きな符号位置に対しては、ユニコードのルールからケースマップが作成されます。
ただし ユニコードルールと、非ユニコードルールの境界をまたぐ(符号位置が 255以下と 256以上) 場合のマッチは失敗するでしょう。
m 文字列を複数行とみなします。つまり、"^" と "$" の解釈を 文字列の開始と終了とマッチするのではなく、文字列内のどの行の開始・終了ともマッチするような動作になります。
s 文字列を1行とみなします。つまり、 "." が、どんな文字ともマッチするようになります。それがたとえ、改行のように通常マッチしない文字だったとしてもです。
as r""ms, というように、mとsが一緒に使われると、"." はどんな文字ともマッチしながら、"^" と"$" はそれぞれ、改行の前後とマッチするようになります。
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
バイト配列リテラル
もう 1 つの便利な非標準文字列リテラルは、バイト配列文字列リテラルです。b"..."
のように書きます。この形式では、読み取り専用のリテラルバイト配列、つまりUInt8
値の配列を表すのに、文字列表記を使用することができます。これらのオブジェクトの型はCodeUnits{UInt8, String}
です。 バイト配列リテラルの規則は次のとおりです:
- ASCII 文字と ASCII エスケープは、1 バイトを生成します。
\x
および8進としてエスケープした文字は、エスケープ値に対応する バイト を生成します。- 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 で 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 エンコーディングは、対応する \x
エスケープによって生成される単一バイトにすぎません。この違いを無視しても問題ありません。ただし、エスケープ \x80
から \xff
までを、\u80
から\uff
までと比較すると大きな違いがあります: 前者はすべて単一バイトにエンコードされますが、ごく一部のバイト列を除いて無効なUTF-8データになります。後者はすべてUnicodeの符号位置を表現し、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のリリースよりバージョンが低いことを示します。したがって、上記のコード例の意味は、
ifで囲まれたコードが、安定版の
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"
に対してtrue
を返します。
このような特別なバージョンを使った比較は非常に実践的です(特に、後続の -
は、正当な理由がない限り常に上限で使用する必要があります)が、セマンティック バージョニングの方式としては無効なものなので、実際のバージョン番号として使用してはなりません。
VERSION
定数に使う以外に、VersionNumber
オブジェクトはPkg
でパッケージのバージョンとその依存関係を指定するのに広く使われています。
生文字列リテラル
式展開やエスケープ処理のない生文字列は、raw"...'
' という形式の非標準文字列リテラルで表現できます。生文字列リテラルは、引用符の中身を式展開やエスケープ処理することなくそのまま含んだ通常のString
オブジェクトを作成します。これは、$
または \
を特殊文字として使用する他言語のコードまたはマークアップ言語を含む文字列に便利です。
例外は、引用符をエスケープする必要がある場合です。例えばraw"\""
は"\""
と同等です。 すべての文字列を表現しようとすると、引用符文字の直前に表示される場合だけですが、バックスラッシュもエスケープする必要があります:
julia> println(raw"\\ \\\"")
\\ \"
最初の 2 つのバックスラッシュは、引用符文字の前にないため、そのまま出力されることに注意してください。 ただし、次のバックスラッシュは、その後に続くバックスラッシュをエスケープし、最後のバックスラッシュは引用符の前に表示されるので、引用符をエスケープします。