やわらかテック

興味のあること。業務を通して得られた発見。個人的に試してみたことをアウトプットしています🍵

ゆるく理解するElixirのデータ構造体と簡単なパターンマッチング例

elixirでのデータ構造体

  • リスト
  • タプル
  • マップ
  • キーワードリスト
  • (構造体) ※今回は触れません

間違えやすいelixirにおけるデータ構造のグループ
よく混同するのでまとめときました

リスト[] (list)

javascriptpythonでいう配列(array)です
elixirではリストと名乗ってます

_simple_numbers = [1,2,3,4,5] #もっともシンプルな配列
_use_range = [1..5] #ちょっとクールに

_string_lst = ["apple", "banana", "lemon"] #文字列
_bool_lst = [false, true, false, false] #ブール値
_atom_lst = [:foo, :bar, :tmp] #アトム

#ごっちゃまぜ(使い道?そんなものはない)
_anything = [1, "apple", 2, :foo, true, "banana", 5, false]

# リストinマップ
_user_info_map = [
  %{name: "稲葉浩志", role: "ボーカル"},
  %{name: "松本孝弘", role: "ギター"},
]

# リストinタプル
_user_info_tuple = [
  {:auto, :visible, :not_auth},
  {:not_auto, :unvisible, :auth},
  {:auto, :visible, :auth},
]

使用可能なモジュール

Enumが使えるのがクソ便利。Listも便利です。全部便利です(アホ

パターンマッチ

#各要素にアクセスする
[apple, banana] = ["apple", "banana"]
IO.puts(apple) #result: "apple"
IO.puts(banana) #result: "banana"

#要素の数が合わないとerrorになる
[a, b] = ["a", "b", "c"] # (MatchError) no match of right hand side value: ["a", "b", "c"]
[a, b, _] = ["a", "b", "c"] 
IO.puts(a) #result: "a"
IO.puts(b) #result: "b"

# elixirでは「 _ 」は非使用変数なので出力するとerror
IO.puts(_) #result: (Compile Error)

#最初の要素にアクセスする
_first_enum = Enum.at([1,2,3], 0) #Enumを使って
_first_list = List.first([1,2,3]) #Listを使って

#headとtailを使って
[head | tail] = [1,2,3]
IO.puts(head) #result: 1
IO.puts(inspect tail) #result: [2,3]

タプル{} (tuple)

正直あんまり使ってません。使い道があんまり思いつかない...
公式のドキュメント見るとこんなことが書いてありますね

A tuple may contain elements of different types, which are stored contiguously in memory. Accessing any element takes constant time, but modifying a tuple, which produces a shallow copy, takes linear time. Tuples are good for reading data while lists are better for traversals.

タプルは、メモリに連続して格納されているさまざまなタイプの要素を含むことができます。任意の要素にアクセスするには一定の時間がかかります。タプルはデータの読み取りに適していますが、リストはデータの検索に適しています。

ふむふむ...
つまりは要素の読み取りには強い(速い)けど要素の変更はリストの方がいいよってことですね
不変データ群とかを作るのが良さそうです(read onlyのものとか)

_nums = {1,2,3,4,5} 
_nums_cool = {1..10}
_string = {"apple", "banana", "peach"}

#セーブデータ(場所, 職業, 性別)
_save_data = {:dungeon, :soldier, :man}

#ごちゃまぜ
_anything = {1, "apple", false, :foo}

使用可能なモジュール

使えるモジュールが少ないのでちょっと扱いづらい
いつもTuple.to_list使ってリストに変換してます(ボソ

パターンマッチ

#リストと同様に各要素にアクセスできる
{a,b,c} = {1,2,3}

#成否判定に良さそう
{:ok, res} = {:ok, "happy birthday"}
{:error, res} = {:error, "normal day"} 

マップ%{} (map)

リストと合わせてよく使います
javascriptでいうobject。pythonでいう辞書みたいなもんです
複数の属性を持たせるのに便利。もうリストとマップだけでいいんじゃね

elixirにはマップの書き方が2種類あります。これくそややこしい
基本的には「:」を使った記法の方が楽です
特別な事情がなければこちらでいいかなって

#キーの型を自由につくれる方法( => を使う)
_user_info = %{"name" => "okb", "age" => 22, "sex" =>  :man, "married" => false}

#同じキーがあると上書きされる
_user_info = %{"name" => "okb", "age" => 22, "sex" =>  :man, "married" => false, "name" => "ookb"}
#キーの型がアトム固定( : を使う)
_user_info = %{name: "okb", age: 22, sex: :man, married: false}

#やっぱり上書きされる(知ってた
_user_info = %{name: "okb", age: 22, sex: :man, married: false, name: "ookb"}
#おなじみのB'zデータ(リストinマップ)
_lst_data = [
  %{title: "熱き鼓動の果て", singer: "B'z", released: 2002},
  %{title: "ZERO", singer: "B'z", released: 1992},
  %{title: "calling", singer: "B'z", released: 1997},
  %{title: "さまよえる蒼い弾丸", singer: "B'z", released: 1998},
  %{title: "TIME", singer: "B'z", released: 2002},
  %{title: "love me, I love you", singer: "B'z", released: 1995},
  %{title: "CHAMP", singer: "B'z", released: 2017},
  %{title: "HOME", singer: "B'z", released: 1998},
]

使用可能なモジュール

  • Dict ※これ廃止予定なので使用を避けましょう
  • map
  • Enum ※mapだと正直あまり使わない

パターンマッチ

info = %{name: "okb", age: 22}
IO.puts(info.name) # "okb"
IO.puts(info[:name]) # "okb"

#キーが存在しているか(複数一気にいける)
%{name: name, age: age} = info
IO.puts("#{name}, #{age}") #okb, 25

#内包表記を使ってキーにアクセスする
lst_data = [
  %{title: "熱き鼓動の果て", singer: "B'z", released: 2002},
  %{title: "ZERO", singer: "B'z", released: 1992},
  %{title: "calling", singer: "B'z", released: 1997},
  %{title: "さまよえる蒼い弾丸", singer: "B'z", released: 1998},
  %{title: "TIME", singer: "B'z", released: 2002},
  %{title: "love me, I love you", singer: "B'z", released: 1995},
  %{title: "CHAMP", singer: "B'z", released: 2017},
  %{title: "HOME", singer: "B'z", released: 1998},
]

res = for info = %{released: released} <- lst_data, released < 1998, do: info
IO.inspect(res)
# %{released: 1992, singer: "B'z", title: "ZERO"},
# %{released: 1997, singer: "B'z", title: "calling"},
# %{released: 1995, singer: "B'z", title: "love me, I love you"}]

キーワードリスト[] (keyword list)

性質的にはマップに似てます。syntaxはリストに似ている(????
正直、ほとんど使ったことがないです
基本的にはマップを使えばいいと思ってます
公式ドキュメントに記述がありますが

A keyword is a list of two-element tuples where the first element of the tuple is an atom and the second element can be any value.

どうやらキーワードリストとは2要素のタプルを持つリストのことみたいですね

マップとの大きな性質の違いは

  • 要素の順番が保証される
  • 同じキーがあっても問題ない

んー、こういう場合ってあるもんなんですかね。やっぱりマップだね

#キーワードリスト
[age: 22, name: "okb", age: 100022]
#result: [age: 22, name: "okb", age: 100022]

#マップ
%{"age" => 22, "name" => "okb", "age" => 100022}
# result: %{"age" => 100022, "name" => "okb"}

マップの場合はキーが重複した場合に上書きされて
要素はキーでソートされてます

使用可能なモジュール

  • Keyword
  • Enum ※使う機会は少なそう

パターンマッチ

info = [age: 22, name: "okb", age: 100022] 
IO.puts(info[:age]) #result: 22
[age, name, real_age] = info
IO.puts(real_age) #result: {:age, 100022} 出力はタプルになる
Keyword.get(info :name) #result: "okb" stringで取得できた

まとめ

ざっくりまとめたつもりでしたがアホほど長くなってしまった
おかしなこと言ってたら教えてください
改めて自分でまとめてみると勉強になりました

個人的には...
リスト > マップ > タプル > キーワードリスト
で使用頻度が高いです
やっぱりEnumモジュールが強力で可能な限り使いたいってのがあって
リスト、もしくはリストinマップみたいな使い方がクールかなって

下まで見ていただいてありがとうございました