LoginSignup
5
0

More than 5 years have passed since last update.

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

Last updated at Posted at 2019-02-26

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

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
0