プログラミング
Elixir

Elixir: 満年齢を計算する - タプルの比較とビット演算

満年齢は英語で何というでしょう?欧米には「数え年」がありません。"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」参照)。


  1. まず、サイズ(tuple_size/1)の大きさ。

  2. つぎに、要素同士を先頭から順に。

したがって、{月, 日}のタプルを比べれば、どちらが先の日付ががわかるのです。


ビット演算を使う

マクロ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