Elixirのキーワードリストとマップ

Tsukasa OISHI

Elixirの勉強の続き( 前回の記事 )。
ElixirにもRubyのHashに似たデータ構造を持つものがあります。それがキーワードリストとマップです。

キーワードリスト

キーワードリストはリストですが、以下のような特徴を持っています。

  • リストのすべての要素はタプルである
  • ひとつの要素のタプルは2つの要素を持つ
  • タプルの最初の値はアトムである

一見にしかずなので、実際に見てみます。

iex(1)> keywords = [{:a, 1}, {:b, 2}, {:c, 3}]
[a: 1, b: 2, c: 3]

iexが表示したキーワードリストの形がおもしろいですね。こういう書き方もできるのです(この書き方しか使わないと思いますが)。

iex(1)> keywords = [a: 1, b: 2, c: 3]
[a: 1, b: 2, c: 3]

RubyのHashの記法に似ていますね。わかりやすい。[] でくくっているので、これがリストであることがわかります。
リストで特定の要素にアクセスするための関数があるのかどうなのか、まだ勉強中の身ではわからないのですが、キーワードリストはRubyのHashのようにアクセスできます。

iex(1)> keywords = [a: 1, b: 2, c: 3]
[a: 1, b: 2, c: 3]
iex(2)> keywords[:a]
1
iex(3)> keywords[:b]
2

Hashのようにアクセスできるといっても、これはリストなので最後のほうの要素にアクセスするほどコストがかかります。また、当然ですが要素の順番はちゃんと決まっています。
これはリストなので、いわゆるキーの重複が可能です。

iex(1)> keywords = [a: 1, b: 2, c: 3]
[a: 1, b: 2, c: 3]
iex(2)> keywords = keywords ++ [a: 2, d: 4]
[a: 1, b: 2, c: 3, a: 2, d: 4]
iex(3)> keywords[:a]
1

[] のアクセスでは最初の要素しか取得できません。あるキーの値をすべて欲しいときは以下のような関数を使うことができます。

iex(1)> keywords = [a: 1, b: 2, c: 3, a: 2, d: 4]
[a: 1, b: 2, c: 3, a: 2, d: 4]
iex(2)> keywords[:a]
1
iex(3)> Keyword.get_values(keywords, :a)
[1, 2]

キーワードリストに関する関数は Keywordモジュールにいろいろとあります。

キーワードリストも当然パターンマッチができます。

iex(1)> keywords = [a: 1, b: 2, c: 3]
[a: 1, b: 2, c: 3]
iex(2)> [a: x, b: y, c: z] = keywords                                 
[a: 1, b: 2, c: 3]
iex(3)> x  
1
iex(4)> y
2
iex(5)> z
3
iex(6)> [a: h] = keywords
** (MatchError) no match of right hand side value: [a: 1, b: 2, c: 3]

iex(6)> [a: i, c: j, b: k] = keywords
** (MatchError) no match of right hand side value: [a: 1, b: 2, c: 3]

キーと順番が完全に一致していないとマッチしません。あまりパターンマッチで使うことはなさそうです。

マップ

マップは、よりRubyのHashに近い印象です。以下のように、 %{} でマップを表すことができます。タプルと記法が似ているので typo に気をつけたほうがよさそうです。

マップは順序が決まっていません。また、要素へのアクセスは高速です。

iex(1)> map = %{:a => 1, "b" => 2, 3 => 4}
%{3 => 4, :a => 1, "b" => 2}
iex(2)> map[:a]
1
iex(3)> map["b"]
2
iex(4)> map[3]
4

マップのキーにはどんな型でも使うことができます。

iex(5)> map2 = %{{:a, 1} => 2, [b: 2] => 3} 
%{{:a, 1} => 2, [b: 2] => 3}

すべてのキーがアトムなら、見やすい記法を使うことができます。

iex(6)> map3 = %{a: 1, b: 2, c: 3}
%{a: 1, b: 2, c: 3}

それからキーの重複はできません。重複したキーがあるときは後のものが上書きします。

iex(1)> map = %{a: 1, a: 2, a: 3}
%{a: 3}

[] ではなく、. でも要素にアクセスできます。

iex(1)> map = %{a: 1, b: 2, c: 3}
%{a: 1, b: 2, c: 3}
iex(2)> map.c
3

マップの更新には以下のようなやり方があります。

iex(1)> map = %{a: 1, b: 2}
%{a: 1, b: 2}
iex(2)> %{map | b: 3}
%{a: 1, b: 3}
iex(3)> %{map | c: 4}
** (KeyError) key :c not found in: %{a: 1, b: 2}

この記法では存在しないキーを割り当てることはできないようです。
マップに関する関数はMapモジュールにあります。

iex(1)> map = %{a: 1, b: 2}
%{a: 1, b: 2}
iex(2)> Map.merge(map, %{c: 4})
%{a: 1, b: 2, c: 4}

マップも当然パターンマッチが使えます。

iex(1)> map = %{a: 1, b: 2}
%{a: 1, b: 2}
iex(2)> %{a: x, b: y} = map
%{a: 1, b: 2}
iex(3)> x
1
iex(4)> y
2
iex(5)> %{a: z} = map
%{a: 1, b: 2}
iex(6)> z
1
iex(7)> %{} = map
%{a: 1, b: 2}
iex(8)> %{c: h} = map
** (MatchError) no match of right hand side value: %{a: 1, b: 2}

マップのパターンマッチはキーが完全に一致している必要はありません。対象のキーが存在していればマッチします。空のマップでもマッチします。対象のキーがないときだけマッチしません。

ディクショナリ

キーワードリストもマップも、両者ともディクショナリとも呼ばれます。

実際、両者ともディクショナリの振る舞いを持っていて、それはDictモジュールに定義されています。