満年齢は英語で何というでしょう?欧米には「数え年」がありません。"age"としかいいようはないのです。むしろ、数え年を英語で説明する方が難しいでしょう。さておき、お題はElixirで満年齢を計算することです。こちらは、さほど難しくありません。ただ、ちょっとしたポイントをご紹介します。
タプルの比較を使う
現在の2019年から生まれた年を引けば、今年中になる歳が求められます。誕生日前のときは、1差し引けばよいということです。関数は、単純に年月日をふた組、都合6つの数値を引数とします。
def calculate_age(birth_year, birth_month, birth_date, year, month, date) do
age = year - birth_year
if {birth_month, birth_date} > {month, date} do
age - 1
else
age
end
end
ポイントは、if/2
の条件において月日をタプルで比べていることです。タプルの大きさはつぎの2段階で決まります(「Operators」の「Term ordering」参照)。
- まず、サイズ(
tuple_size/1
)の大きさ。 - つぎに、要素同士を先頭から順に。
したがって、{月, 日}
のタプルを比べれば、どちらが先の日付ががわかるのです。
ビット演算を使う
マクロBitwise
を使えば、2進数のビット演算ができます。コンピュータが、もっとも得意とするシンプルな処理です。ビット演算で満年齢はつぎのように求められます。
def calculate_age(birth_year, birth_month, birth_date, year, month, date) do
use Bitwise
birth = birth_year <<< 9
||| birth_month <<< 5
||| birth_date
current = year <<< 9
||| month <<< 5
||| date
(current - birth) >>> 9
end
使ったビット演算子は、つぎのとおりです。
-
<<</2
: ビットの左シフト。2進数の桁を左に指定数移します。 -
>>>/2
: ビットの右シフト。2進数の桁を右に指定数移します。小数値は除かれます。 -
|||
: 排他的論理和(XOR)。両辺をビット毎に調べ、どちらか一方のみが1のとき1、他は0になります。
考え方は10進法を使った場合で見た方がわかりやすいでしょう。排他的論理和(|||
)は、この場合和(+
)に置き替えても構いません。
# 誕生日: 1999年3月1日
birth = 1999 * 10000
+ 3 * 100
+ 1
# = 19990301
# 現在: 2019年2月28日
current = 2019 * 10000
+ 2 * 100
+ 28
# = 20190228
# current - birth = 199927 <- 下4桁に意味はない
div(current - birth, 10000) # = 19
引き算したあとの下4桁の数値は意味がありません。けれど、日が足りないときは月から、月が足りないときは年から「借りてくる」繰り下がりが重要になります。その結果、誕生日前には年が1差し引かれる結果になるのです。
関数に複数の句を加える
実際に関数を使う場合には、整数6つの引数は勝手がよくないかもしれません。そういうときは、関数に引数の異なる句を加えて、パターンマッチングさせればよいでしょう(「[Elixir入門 09: 再帰]」の「再帰によるループ」参照)。たとえば、Date
またはタプルで日付を渡せるようにする場合、定めるふたつの句はつぎのとおりです。
def calculate_age(
%Date{year: birth_year, month: birth_month, day: birth_date},
%Date{year: year, month: month, day: date}) do
calculate_age(birth_year, birth_month, birth_date, year, month, date)
end
def calculate_age(
{birth_year, birth_month, birth_date},
{year, month, date}) do
calculate_age(birth_year, birth_month, birth_date, year, month, date)
end