🙆‍♀️

FRICK/Elixir/基本/#03 リスト/タプル/マップ

2022/08/27に公開

初めに

この記事はFRICKのElixir学習のアウトプットである。
Elixirスキルシートに基づいて学習している。
以下のサイトを参考にしている。
https://elixirschool.com/ja/lessons/basics/collections

リスト

リストは値の単純なコレクションで、複数の型を含むことが出来ます。
また、一位ではない値を含むことが出来ます。

iex(1)> [3.14,:pie,"Apple"]
[3.14, :pie, "Apple"]

Elixirはリストコレクションを連結リストとして実装しています。
すなわちリストの長さを得るのは線形時間(O(n))の処理となります。
このことから、リスト先頭への追加はほとんどの場合リストの末尾への追加より高速です。

iex(2)> list = [3.14,:pie,"Apple"]
[3.14, :pie, "Apple"]
iex(3)> ["π" | list]
["π", 3.14, :pie, "Apple"]
iex(4)> list ++ ["Cherry"]
[3.14, :pie, "Apple", "Cherry"]

1/4 : リストの連結

リストの連結には ++ 演算子を使います。

iex(5)> [1,2] ++ [3,4,1]
[1, 2, 3, 4, 1]

上記の名前の形式についての追記:
Elixir(とErlang)において、関数や演算子の名前は2つの部分、
与えられた名前とそのアリティからなります。
アリティは関数や演算子が取る引数の数です。
名前とアリティは/で繋げられます。

2/4 : リストの減算

減算に対応するために -- 演算子が用意されています。
存在しない値を弾いてしまっても安全です。

iex(6)> ["foo",:bar,42] -- [42,"bar"]
["foo", :bar]

重複した値に注意してください。
右辺の要素のそれぞれに対し、左辺の要素のうち初めて登場した同じ値が順次削除されます。

iex(7)> [1,2,2,3,2,3] -- [1,2,3,2]
[2, 3]

3/4 : 頭部/尾部

リストを扱う際には、よくリストの頭部と尾部を利用したりします。
頭部はそのリストの最初の要素で、尾部は残りの要素になります。
Elixirはこれらを扱う為に、hdとtlという二つの便利な関数を用意しています。

iex(8)> hd [3.14,:pie,"Apple"]
3.14
iex(9)> tl [3.14,:pie,"Apple"]
[:pie, "Apple"]

前述した関数に加えて、リストを頭部と尾部にわけるのに
パターンマッチングやcons演算子を使う事も出来ます。

iex(10)> [head | tail] = [3.14,:pie,"Apple"]
[3.14, :pie, "Apple"]
iex(11)> head
3.14
iex(12)> tail
[:pie, "Apple"]

4/4 : キーワードリスト

キーワードリストとマップはElixirの関連コレクションです。
Elixirでは、キーワードリストは最初の要素がアトムのタプルからなる特別なリストで、
リストと同様の性能になります。

iex(15)> [foo: "bar",hello: "world"]
[foo: "bar", hello: "world"]
iex(16)> [{:foo,"bar"},{:hello,"world"}]
[foo: "bar", hello: "world"]

キーワードリストの重要性は次の三つの特徴によって強調付けられています。
・キーはアトムです。
・キーは順序付けされています。
・キーの一意性は保証されません。
こうした理由から、キーワードリストは関数にオプションを渡すためによく用いられます。

タプル

タプルの各要素はメモリ上に隣接して格納されます。
このためタプルの長さを得るのは高速ですが、修正を行うのは高コストになります。
というのも、新しいタプルは全ての要素がメモリにコピーされるからです。
タプルは波括弧を用いて定義されます。

iex(13)> {3.14,:pie,"Apple"}
{3.14, :pie, "Apple"}

タプルは関数から補助的な情報を返す仕組みとしてよく利用されます。
この便利さは、パターンマッチングについて扱う時より明らかになります。

iex(14)> File.read("path/to/unknown/file")
{:error, :enoent}

マップ

マップはキーワードリストとは違ってどんな型のキーも使え、順序付けされません。
マップは%{}構文で定義することが出来ます。

%{:foo => "bar", "hello" => :world}
iex(18)> map[:foo]
"bar"
iex(19)> map["hello"]
:world

Elixir 1.2では変数をマップのキーにすることが出来ます。

iex(20)> key = "hello"
"hello"
iex(21)> %{key => "world"}
%{"hello" => "world"}

重複したキーが追加された場合は、前の値が置き換えられます。

iex(22)> %{:foo => "bar",:foo => "hello world"}
warning: key :foo will be overridden in map
  iex:22
%{foo: "hello world"}

上気の出力から分かる通り、アトムのキーだけを含んだマップには特別な構文があります。

%{foo: "hello world"}
iex(23)> %{foo: "bar",hello: "world"}
%{foo: "bar", hello: "world"}
iex(24)> %{foo: "bar",hello: "world"} == %{:foo => "bar",:hello => "world"}
true

加えて、アトムのキーにアクセスするための特別な構文もあります。

iex(25)> map = %{foo: "bar",hello: "world"}
%{foo: "bar", hello: "world"}
iex(26)> map.hello
"world"

マップのもう一つの興味深い特性は、マップの更新の為の固有の構文がある事です。
(※更新と言っていますが、新しいmapが作成されます。)

iex(27)> map = %{foo: "bar",hello: "world"}
%{foo: "bar", hello: "world"}
iex(28)> %{map | foo: "baz"}
%{foo: "baz", hello: "world"}

※この構文はマップに既に存在するキーを更新する場合にのみ機能します。
存在しない場合、KeyErrorが発生します。

新しいキーを作成するには代わりにMap.putを使用します。

iex(29)> map = %{hello: "world"}
%{hello: "world"}
iex(30)> Map.put(map, :foo , "paz")
%{foo: "paz", hello: "world"}

Discussion