トラック
/
Elixir
Elixir
/
演習
/
日付パーサー
日付パーサー

日付パーサー

学習演習

はじめに

正規表現

正規表現(regex)は、Elixirで文字列を扱うための強力なツールです。Elixirの正規表現は、PCRE仕様(Perl Compatible Regular Expressions)に従います。正規表現の意味を表す文字列パターンは、まずコンパイルされ、その後、文字列全体または一部のマッチングに使われます。

Elixirで正規表現を作る最も一般的な方法は、~rシジルを使うことです。シジルは、Elixirでよくある作業を短く書くための_糖衣構文_を提供します。_文字列リテラル_にマッチさせるには、シジルの後ろにその文字列自体をパターンとして書きます。

~r/test/

=~/2演算子を使うと、文字列に対して正規表現のマッチを行い、booleanの結果を得ることができます。

"this is a test" =~ ~r/test/
# => true

シジルを使う際の注意点が2つあります。

  • 要件に応じて、/以外にもさまざまな区切り文字を使えます
  • 文字列パターンはすでに_エスケープ_されています。正規表現を使わずにパターンを文字列として書く場合は、バックスラッシュ(\)を_エスケープ_する必要があります

文字クラス

角括弧[]を使って文字の範囲を指定すると、_文字クラス_を定義できます。これは、クラスに含まれる文字のいずれか1文字にマッチします。始点と終点が連続したコードポイントの範囲を表していれば、a-zのように文字の範囲を指定することもできます。

regex = ~r/[a-z][ADKZ][0-9][!?]/
"jZ5!" =~ regex
# => true
"jB5?" =~ regex
# => false

_省略形の文字クラス_を使うと、パターンをより簡潔に書けます。たとえば、次のとおりです。

  • \dは[0-9]の省略形(任意の数字)
  • \wは[A-Za-z0-9_]の省略形(任意の「単語」文字)
  • \sは[ \t\r\n\f]の省略形(任意の空白文字)

_省略形の文字クラス_をシジルの外で使う場合は、エスケープする必要があります:"\\d"

選択

_選択_では、|を特殊文字として使い、どちらか一方にマッチすることを表します。

regex = ~r/cat|bat/
"bat" =~ regex
# => true
"cat" =~ regex
# => true

量指定子

_量指定子_を使うと、正規表現でパターンを繰り返せます。量指定子は、その直前のグループに作用します。

  • {N, M}:Nが繰り返しの最小回数、Mが最大回数
  • {N,}:N回以上の繰り返しにマッチ
    • {0,}は*とも書けます:0回以上の繰り返しにマッチ
    • {1,}は+とも書けます:1回以上の繰り返しにマッチ
  • {,N}:最大N回の繰り返しにマッチ

グループ

丸括弧()は、_グループ_と_キャプチャ_を表すために使います。グループは、場合によっては_キャプチャ_され、あとで使えるように返されます。Elixirでは、キャプチャに名前を付けることも、付けないこともできます。名前を付けるには、開き括弧の後に?<name>を追加します。グループは、_量指定子_が続く場合のように、1つのまとまりとして機能します。

regex = ~r/(h)at/
Regex.replace(regex, "hat", "\\1op")
# => "hop"

regex = ~r/(?<letter_b>b)/
Regex.scan(regex, "blueberry", capture: :all_names)
# => [["b"], ["b"]]

アンカー

_アンカー_は、正規表現をマッチ対象の文字列の先頭または末尾に固定するために使います。

  • ^は文字列の先頭に固定します
  • $は文字列の末尾に固定します

補間

~rは"pattern" |> Regex.escape() |> Regex.compile!()の省略形なので、文字列補間を使って正規表現パターンを動的に組み立てることもできます。

anchor = "$"
regex = ~r/end of the line#{anchor}/
"end of the line?" =~ regex
# => false
"end of the line" =~ regex
# => true

説明

イベントを取り込むサービスを書くことになりました。各イベントには日付が紐づいていますが、サービスのエンドポイントには3つの異なる形式が送られてくることに気づきます。

  • "01/01/1970"
  • "January 1, 1970"
  • "Thursday, January 1, 1970"

それぞれに共通点があることに気づき、組み合わせ可能な正規表現パターンを書くことにします。

1. 日付から日、月、年をマッチさせる

day/0、month/0、year/0を実装し、コンパイルすると"01/01/1970"(dd/mm/yyyy)の数値部分にマッチする文字列パターンを返すようにしましょう。日と月は1または01(0で左詰め)のように現れることがあります。

"31" =~ DateParser.day() |> Regex.compile!()
# => true
"12" =~ DateParser.month() |> Regex.compile!()
# => true
"1970" =~ DateParser.year() |> Regex.compile!()
# => true

2. 曜日と月名をマッチさせる

day_names/0とmonth_names/0を実装し、コンパイルするとそれぞれ曜日名と月名にマッチする文字列パターンを返すようにしましょう。

"Tuesday" =~ DateParser.day_names() |> Regex.compile!()
# => true
"June" =~ DateParser.month_names() |> Regex.compile!()
# => true

3. 日、月、年をキャプチャする

capture_day/0、capture_month/0、capture_year/0、capture_day_name/0、capture_month_name/0を実装し、それぞれの部分を"day"、"month"、"year"、"day_name"、"month_name"という名前でキャプチャする文字列パターンを返すようにしましょう。

DateParser.capture_month_name()
|> Regex.compile!()
|> Regex.named_captures("December")

# => %{"month_name" => "December"}

4. キャプチャを組み合わせて日付全体をキャプチャする

capture_numeric_date/0、capture_month_name_date()、capture_day_month_name_date/0を実装し、パート3の各部分を、それぞれの日付形式に従ってキャプチャする文字列パターンを返すようにしましょう。

  • 数値の日付 - "01/01/1970"
  • 月名の日付 - "January 1, 1970"
  • 曜日と月名の日付 - "Thursday, January 1, 1970"
DateParser.capture_numeric_date()
|> Regex.compile!()
|> Regex.named_captures("01/01/1970")

# => %{"day" => "01", "month" => "01", "year" => "1970"}

5. キャプチャを絞り込んで日付だけにマッチさせる

match_numeric_date/0、match_month_name_date/0、match_day_month_name_date/0を実装し、日付だけにマッチし、かつ各部分をキャプチャできるコンパイル済みの正規表現を返すようにしましょう。

"Thursday, January 1, 1970 was the Unix epoch." =~ DateParser.match_day_month_name_date()
# => false
"Thursday, January 1, 1970" =~ DateParser.match_day_month_name_date()
# => true

DateParser.match_day_month_name_date()
|> Regex.named_captures("Thursday, January 1, 1970")

# => %{
#     "day" => "1",
#     "day_name" => "Thursday",
#     "month_name" => "January",
#     "year" => "1970"
#   }
GitHubで編集する リンクは新しいウィンドウまたはタブで開きます
Elixir Exercism

日付パーサーを始める準備はできましたか?

Exercismに登録すれば、58個のコンセプト168個の演習、そして本物の人間によるメンタリングとともに、Elixirを学んでマスターできます。すべて無料です。