正規表現は、文字列を_パターンマッチング_するための非常に汎用性の高い方法で、そのために設計されたドメイン固有言語(DSL)を使います。
他のいくつかのプログラミング言語と同様に、Juliaは独自の正規表現ライブラリを実装しようとはしていません。代わりに、広く使われているPCRE2ライブラリをラップすることで、例えばGleamとまったく同じ、そしてJavaScriptにとてもよく似た正規表現の構文を提供しています。
このJuliaのシラバスは、基本的な正規表現の構文にすでに慣れていることを前提としています。 ここでは、Julia固有の機能だけに焦点を当てます。
正規表現の知識を復習するためのリソースを以下にいくつか挙げます。
Juliaの正規表現へのインターフェースは、マニュアルで説明されています。
Juliaにおける正規表現は、開き"の前にrを付けた文字列にすぎません。基本的な機能はすべて標準ライブラリに含まれています。
実際、Stringsの概念ですでに説明した関数の多くは、occursin()のように、標準で正規表現検索のために設計されています。
julia> re = r"test$"
r"test$"
julia> typeof(re)
Regex
# Does a string end with "test"?
julia> occursin(re, "this is a test")
true
julia> occursin(re, "these are tests")
false
修飾文字は閉じ引用符の後に続けることができ、例えばiは大文字と小文字を区別しないマッチを表します。
julia> occursin(r"test", "Testing")
false
julia> occursin(r"test"i, "Testing")
true
たいていの場合、_何_がマッチしたのかを知りたいはずです。これは、正規表現内に括弧でキャプチャグループを含めてから、match()関数を使うことで実現できます。
julia> m = match(r"(\d+g) .* (\d+ml)", "dissolve 25g sugar in 200ml water")
RegexMatch("25g sugar in 200ml", 1="25g", 2="200ml")
julia> m.captures
2-element Vector{Union{Nothing, SubString{String}}}:
"25g"
"200ml"
# how many matches?
julia> length(m.captures)
2
# what matched?
julia> m[1], m[2]
("25g", "200ml")
# Starting positions of the matches (character index)
julia> m.offsets
2-element Vector{Int64}:
10
23
もちろん、マッチは失敗することもあります。その場合、結果はRegexMatchではなく特別な値Nothingになるので、これに備えてテストできるようにしておきましょう。
# failed match
m = match(r"(not here)", "dissolve 25g sugar in 200ml water")
julia> typeof(m)
Nothing
julia> isnothing(m)
true
matchはデフォルトでは文字列の先頭から開始しますが、オフセットnを指定して最初のn文字を無視することもできます。
# capture first match
julia> m = match(r"(\wat)", "cat, sat, mat")
RegexMatch("cat", 1="cat")
# ignore first 5 characters, then match
julia> m = match(r"(\wat)", "cat, sat, mat", 5)
RegexMatch("sat", 1="sat")
Juliaでは、match()は対象の文字列内で_最初_のマッチしか見つけません。他の言語にあるようなグローバル修飾子はありません。
代わりに、マッチのイテレーターを返すeachmatch()があります。これは遅延評価されるので、目的の形式に変換する必要があるかもしれません。
julia> matches = eachmatch(r"(\wat)", "cat, sat, mat")
Base.RegexMatchIterator{String}(r"(\wat)", "cat, sat, mat", false)
# convert to vector
julia> collect(matches)
3-element Vector{RegexMatch}:
RegexMatch("cat", 1="cat")
RegexMatch("sat", 1="sat")
RegexMatch("mat", 1="mat")
# convert with comprehension
julia> [m.match for m in matches]
3-element Vector{SubString{String}}:
"cat"
"sat"
"mat"
# broadcast an anonymous function
julia> (m -> m.match).(matches)
3-element Vector{SubString{String}}:
"cat"
"sat"
"mat"
重なったマッチはデフォルトでは許可されていません。これを上書きするには、キーワード引数としてoverlap = trueを追加します。
julia> eachmatch(r"aba", "abababa") |> collect # matches at positions 1, 5
2-element Vector{RegexMatch}:
RegexMatch("aba")
RegexMatch("aba")
julia> eachmatch(r"aba", "abababa"; overlap = true) |> collect # also matches at position 3
3-element Vector{RegexMatch}:
RegexMatch("aba")
RegexMatch("aba")
RegexMatch("aba")
正規表現を使うよくある理由の1つは、マッチした部分を別の文字列に置き換えることです。
replace()関数は、検索対象に文字列リテラルを使う方法としてStringsの概念で説明しました。同じ関数で、正規表現マッチングの持つ力を余すところなく活用できます。
julia> replace("some string", r"[aeiou]" => "*")
"s*m* str*ng"
julia> replace("first second", r"(\w+) (?<agroup>\w+)" => s"\g<agroup> \1")
"second first"
上の2番目の例は、s" "文字列の中で、番号付きと名前付きの両方のキャプチャグループを置換に使う方法を示しています。
詳細はマニュアルを参照してください。これは、ほとんどのプログラマーを絶えずドキュメントに戻らせるトピックです!