正規表現は、文字列の_パターンマッチング_を行うための非常に汎用性の高い方法で、そのために設計されたドメイン固有言語(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"
正規表現を使うよくある理由の一つは、マッチした部分を別の文字列に置き換えることです。
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" "文字列の中で、番号付きと名前付きの両方のキャプチャグループを置換に使えることを示しています。
詳しくはマニュアルを参照してください。これは、ほとんどのプログラマーがいつもドキュメントに戻らされてしまうトピックです!
あなたは、チャットボットを開発している会社に、正規表現スペシャリストとして採用されました。
開発はまだごく初期の段階なので、正規表現を使って、チャットボットが自然言語を理解したり生成したりする能力を高めることが任務です。
学習のため、これらのタスクは正規表現を使って解いてください。
別の方法が使える場合もあること(むしろ性能が高いことさえあります!)は分かっています。
チャットボットは賢いだけでなく、忠実なアシスタントでもあります。
チャットボットに何かを尋ねるには、ユーザーはコマンドの先頭で「Chatbot」という単語を言う必要があります。
キーワードが大文字でも小文字でもかまいません。ここで重要なのは、その単語の位置です。
ユーザーがコマンドを出しているのかをチャットボットが認識できるようにする関数is_valid_command()を実装しましょう。
julia> is_valid_command("Chatbot, play a song from the 80's.")
True
julia> is_valid_command("Hey Chatbot, where is the closest pharmacy?")
False
julia> is_valid_command("CHATBOT, do you have a solution for this challenge?")
True
チャットボットは、人間が感情を表すために絵文字をどう使うのかを理解するのが苦手です。
チャットボットがユーザーからのメッセージを受け取ると、各絵文字は「emoji」という文字列と、それに続く_id_番号で表されます。
文字列を受け取り、メッセージ全体から絵文字の暗号化をすべて取り除くメソッドremove_emoji()を実装しましょう。
絵文字のテキストを含まない行は、そのまま返してください。
絵文字の文字列を取り除くだけにしてください。空白は調整しようとしないでください。
julia> remove_emoji("I love playing videogames emoji3465 it's one of my hobbies")
"I love playing videogames it's one of my hobbies"
モバイルアプリでのチャットボット機能のダウンロードを想定すると、ユーザーは会話の中で電話番号を入力することが求められます。
問題は、チャットボットが特定の形式の番号しか読み取って検証できないことです。
番号が有効な場合(正規表現で指定された文字列の並びに一致する場合)、チャットボットはユーザーに感謝し、番号を確認するメッセージで答えます。番号が無効な場合、関数は電話番号が有効でないことをユーザーに伝えます。
期待される形式は次のとおりです。(+##) ###-###-###
julia> check_phone_number("(+34) 659-771-594")
"Thanks! You can now download me to your phone."
julia> check_phone_number("659-771-594")
"Oops, it seems like I can't reach out to 659-771-594"
チャットボットはとても好奇心旺盛なソフトウェアです。特定のトピックについてインターネットで検索できるにもかかわらず、関連する情報を見つけるために、面白いウェブサイトやURLをユーザーに尋ねるのが好きです。
会話の例:
各ウェブサイトのリンクだけを含む配列を返す関数getURL()を実装しましょう。
julia> getURL("I learned a lot from exercism.org")
["exercism.org"]
会話したすべての人のデータを保存するために、チャットボットはユーザーのプロフィールから次の形式でフルネームを取得できます: “smith, john”。
こうすることで、チャットボットをとても礼儀正しくして、良い印象を与えたいのです。
ユーザーのフルネームを表す文字列を受け取り、文字列**“Nice to meet you, John Smith”**を返す関数nice_to_meet_you()を書いてください。
julia> str = "Smith, John"
julia> nice_to_meet_you(str)
"Nice to meet you, John Smith"