Skip to content

Latest commit

 

History

History
791 lines (505 loc) · 30.5 KB

moyo_assoc.md

File metadata and controls

791 lines (505 loc) · 30.5 KB

Module moyo_assoc

連想リストに関する処理を集めたユーティリティモジュール.

Copyright (c) 2013-2015 DWANGO Co., Ltd. All Rights Reserved.

Description

連想リストとは、以下のようにキーと値が対となったタプルを要素として保持するリストのことを表す。

  [
    {key, value},
    {"キー", "値"},
    {editors, [<<"vim">>, <<"emacs">>, <<"notepad">>]}
  ]

重複したキーを持つ要素を複数リスト内に保持することは可能だが、
その場合、連想リストの操作時には、同じキーを有する要素群の内の最初のもの以外は無視される。

例えば、検索関数の場合は、最初に出現した要素の値が採用され、
削除関数では、最初に出現した要素のみが削除されることになる。
(つまり、一回の削除関数呼び出しで、重複キーを持つ全ての要素が除去されることはない)

ただし、明示的に重複キーの扱い方が定義されている関数に関しては、その限りではない。

本モジュールは連想リストをセットの一種として扱うため、原則として要素の順番は考慮されない。

そのため、ある関数を適用した結果、連想リストの論理的な内容は同一でも、
実際の内容(要素の並び順)は、適用前とは変わっていることもあるので、注意が必要。
※ ただし、例外として連想リストが重複キーを含む場合は、それらの間の順番(前後関係)は常に維持される。

Data Types


assoc_list() = assoc_list(key(), value())

assoc_list(Key, Value) = [{Key, Value}]

key() = term()

validate_entry_spec() = {KeySpec::key() | {key(), key()}, ValueSpec::moyo_validator:spec(), Options::[moyo_validator:option() | validate_option_ext()]}

要素のバリデーション指定.

[KeySpec]
対象要素のキー名を指定する.
{From, To}形式で指定した場合は、検索はFromで行われ、結果としてはToがキー名として使用される. (キー名のリネーム)

[ValueSpec]
値のバリデーション方法を指定する.
詳細はmoyo_validator:spec/0を参照のこと.

[Options]
バリデーションオプションを指定する.
詳細はmoyo_validator:option/0および'moyo_assoc:validate_option_ext/0'を参照のこと.


validate_option_ext() = {default, DefaultValue::term()} | optional

moyo_assoc独自のバリデートオプション:

  • {default, DefaultValue}: 指定された場合は、要素が存在しない場合に、エラーではなくDefaultValueを返す
  • optional: 指定された場合は、要素が存在しない場合に、エラーではなく単に結果から除外される

value() = term()

Function Index

delete/2キーに対応する要素を削除する.
diff/22つの連想リストから共通のタプルとどちらかのリストにしかないタプルとキーがList1とLIst2で異なるタプルを分ける.
equal/22つの連想リストが同じかどうかを比較する.
fetch/2キーに対応する値を取得する.
fetch/3キーに対応する値を取得する.
fetch_as/3キーに対応する値をValueSpecで指定された方式で取得する.
fetch_values/2複数の値を一度に取得する.
from_map/1mapから連想リストを生成する.
from_map_recur/1ネストしたmapをassocListに変換する.
from_record/2レコードを連想リスト形式に変換する.
intersection_and_differences/22つの連想リストから共通のタプルとどちらかのリストにしかないタプルを分ける.
is_assoc_list/1引数の値が連想リストかどうかを判定する.
keys/1キーのリストを生成する.
keys_as_set/1キーの集合を生成する.
lookup/2キーに対応する値を検索する.
lookup_as/3キーに対応する値をValueSpecで指定された方式で取得する.
lookup_entries/2KeyListで指定されたエントリー(要素)一覧を取得する.
lookup_entries_as/2EntrySpecListで指定された方式で、エントリー(要素)一覧を取得する.
lookup_values/2KeyListで指定されたキーに対応する値一覧を取得する.
lookup_values_as/2EntrySpecListで指定された方式で、値一覧を取得する.
merge/22つの連想リストをマージする.
pop/2キーに対応するリストの先頭から値を取り出す.
push/3キーに対応するリストの先頭に値を追加する.
rfetch/2再帰的にキーに対応する値を取得する.
rfetch/3再帰的にキーに対応する値を取得する.
rupdate/4キーリストに対応する要素の値を更新する.
store/3連想リストに要素を追加する.
store_if_not_exist/3既にキーが存在しない場合にのみ、連想リストに要素を追加する.
take/2キーに対応する要素を連想リストから取り出す(取り除く).
to_map/1連想リストからmapを生成する.
to_map_recur/1ネストしたassocListをmapに変換する.
to_record/3連想リストからレコードを生成する.
to_record_as/4連想リストからレコードを生成する.
unique_by_key/1重複したキーを持つ要素を除去した連想リストを生成する.
update/4キーに対応する要素の値を更新する.
values/1値のリストを生成する.

Function Details

delete/2


delete(Key::key(), AssocList::assoc_list()) -> assoc_list()

キーに対応する要素を削除する.

diff/2


diff(AssocList1, AssocList2) -> {EqualList, ValueDiffList, Only1List, Only2List}

2つの連想リストから共通のタプルとどちらかのリストにしかないタプルとキーがList1とLIst2で異なるタプルを分ける

出力は, {共通のタプル, キーが同じでvalueが変更されたタプル, リスト1にだけあるタプル, リスト2にだけあるタプル}.

最初に2つのリストの中身をdictに展開し、他方のリストの探索をしやすいようにする。
gb_treesをdictの代わりに使わないのは、gb_treesでは1と1.0が同一視される(=:=ではなく==での比較が行われているため)が、1.0と1は違うものと扱いたいから。
それらを元に、重複する要素を消したリストを生成する。
そのあと、EqualList,ValueDiffList,Only1List,Only2Listをリスト内包表記でフィルタリングしながら生成する。
key,valueともに値の比較は=:=で行っているため、1と1.0は別物として扱われる点に注意すること。

ex:

  1> moyo_assoc:diff
  1> ([{key1, value1}, {key2, value2}, {key3, value3}, {key4, value4}, {key5, value5}, {key7, value7}, {key1, value2} ],
  1> [{key1, value2}, {key2, value2}, {key3, value3}, {key4, value4}, {key6, value6}]).
  {[{key2, value2}, {key3, value3}, {key4, value4}],
   [{key1, {value1, value2}}],
   [{key5, value5}, {key7, value7}],
   [{key6, value6}]}

equal/2


equal(AssocList1, AssocList2) -> boolean()

2つの連想リストが同じかどうかを比較する.

同じ場合は, true, 異なる場合はfalse. ただし, 重複キーや順の扱いは他の連想リストと同じ扱いである.

ex:

  1> moyo_assoc:equal([{key1, value1}, {key2, value2}, {key1, different_value}], [{key2, value2}, {key1, value1}]).
  true

fetch/2


fetch(Key::key(), AssocList::assoc_list()) -> value()

キーに対応する値を取得する.

キーが存在しない場合は、例外が送出される.

fetch/3


fetch(Key::key(), AssocList::assoc_list(), DefaultValue::value()) -> value()

キーに対応する値を取得する.

キーが存在しない場合は、デフォルト値が代わりに返される.

fetch_as/3


fetch_as(Key, ValueSpec, AssocList::assoc_list()) -> value()

キーに対応する値をValueSpecで指定された方式で取得する

ValuesSpecの詳細はmoyo_validator:validate/3を参照のこと

fetch_values/2


fetch_values(KeyList::[key()], AssocList::assoc_list()) -> [value()]

複数の値を一度に取得する

from_map/1


from_map(Map::#{}) -> moyo_assoc:assoc_list()

mapから連想リストを生成する.

maps:to_list/1 の結果と同じ.

ex:

  1> moyo_assoc:from_map(#{key1 => value1,key2 => value2,key3 => value3,key4 => value4,key5 => value5}).
  [{key1,value1},
   {key2,value2},
   {key3,value3},
   {key4,value4},
   {key5,value5}]

from_map_recur/1


from_map_recur(NestMap::#{}) -> assoc_list()

ネストしたmapをassocListに変換する

Mapからassoclistに変換する また、各Valに対し再帰的にこの関数を適用する

ex:

  from_map_recur(#{#{}=>#{},[]=>#{}}).
  [{#{},[]},{[],[]}]

from_record/2


from_record(Fields::[atom()], Record) -> assoc_list()
  • Record = tuple()

レコードを連想リスト形式に変換する.

Fields の値は record_info(fields, RecordName) で取得できる.
以下、使用例:

  > rd(sample, {a, b, c}).
  > Record = #sample{a = 10, b = 20, c = 30}.
  #sample{a = 10,b = 20,c = 30}
  > Fields = record_info(fields, sample).
  [a,b,c]
  > moyo_assoc:from_record(Fields, Record).
  [{a,10},{b,20},{c,30}]

intersection_and_differences/2


intersection_and_differences(AssocList1, AssocList2) -> {Intersec, Diff1, Diff2}

2つの連想リストから共通のタプルとどちらかのリストにしかないタプルを分ける.

出力は, {共通のタプル, リスト1にだけあるタプル, リスト2にだけあるタプル}.

ex:

  1> moyo_assoc:intersection_and_differences
  1> ([{key2, value2}, {key1, value1}, {key4, value4}],
  1> [{key3, value3}, {key1, value1}, {key5, value5}, {key2, value4}, {key2, value2}, {key4, value4}]).
  {[{key1,value1},{key4,value4}],
   [{key2,value2}],
   [{key2,value4},{key3,value3},{key5,value5}]}

is_assoc_list/1


is_assoc_list(Value::term()) -> boolean()

引数の値が連想リストかどうかを判定する.

keys/1


keys(AssocList::assoc_list()) -> [key()]

キーのリストを生成する.

この関数は重複キーを考慮しない.
重複キーが除去された情報が欲しい場合は, 下記を検討すること.

  1. unique_by_key/1 との併用
  2. keys_as_set/1 の利用

ex:

  1> moyo_assoc:keys([{key1, value1}, {key2, value2}]).
  [key1, key2]

keys_as_set/1


keys_as_set(AssocList::assoc_list()) -> gb_sets:set(key())

キーの集合を生成する.

gb_sets:set() の性質上, 重複キーは除去される

lookup/2


lookup(Key::key(), AssocList::assoc_list()) -> error | {ok, value()}

キーに対応する値を検索する.

lookup_as/3


lookup_as(Key, EntrySpec, AssocList::assoc_list()) -> {ok, value()} | {error, Reason}

キーに対応する値をValueSpecで指定された方式で取得する

EntrySpecの詳細はmoyo_validator:validate/3を参照のこと

lookup_entries/2


lookup_entries(KeyList, AssocList::assoc_list()) -> {ok, assoc_list()} | {error, Reason}
  • KeyList = [key()]
  • Reason = term()

KeyListで指定されたエントリー(要素)一覧を取得する

lookup_entries_as/2


lookup_entries_as(EntrySpecList, AssocList::assoc_list()) -> {ok, assoc_list()} | {error, Reason}

EntrySpecListで指定された方式で、エントリー(要素)一覧を取得する

指定方法の詳細に関してはvalidate_entry_spec()のドキュメント及びmoyo_validatorモジュールを参照のこと.

使用例:

  > AssocList = [{key1, 10}, {key2, abc}].
   
  %% 基本的な使用方法
  > lookup_entries_as([{key1, integer, []},
                       {key2, atom, []}],
                      AssocList).
  {ok, [{key1, 10}, {key2, abc}]}
   
  %% キー名を変更する
  > lookup_entries_as([{{key1, new_key1}, integer, []}], AssocList).
  {ok, [{new_key1, 10}]}
   
  %% デフォルト値を指定する
  > lookup_entries_as([{key3, integer, [{default, 30}]}], AssocList).
  {ok, [{key3, 30}]}

lookup_values/2


lookup_values(KeyList, AssocList::assoc_list()) -> {ok, [value()]} | {error, Reason::term()}

KeyListで指定されたキーに対応する値一覧を取得する

lookup_values_as/2


lookup_values_as(EntrySpecList, AssocList::assoc_list()) -> {ok, [value()]} | {error, Reason::term()}

EntrySpecListで指定された方式で、値一覧を取得する

以下のコードとほぼ透過:

  > {ok, Entries} = lookup_entries_as(EntrySpecList, AssocList).
  > {ok, [V || {_, V} <- Entries]}.

merge/2


merge(AssocList1::assoc_list(), AssocList2::assoc_list()) -> Result::assoc_list()

2つの連想リストをマージする.

2つの連想リストにおいて、片方のリストにしかkeyが存在しないものは、そのまま結果のリストに加える。両方のリストに同じkeyがある場合、List1の方のkey、valueペアを結果のリストに加える。この関数において、keyの同値判定は=:=ではなく==で行っている。

出力は, {演算した結果の連想リスト}

2つのリストを++で連結したあと、ukeysortで重複するkeyは最初のもののみ考慮するようにするという実装方法で実現している。

ex:

  1> moyo_assoc:merge
  1> ([{key1, value11}, {key3, value31}, {1, value01}, {key3, value312}], [{key1, value12}, {key2, value22}, {1.0, value02}]).
  [{key1, value11}, {key3, value31}, {1, value01}, {key2, value22}]

pop/2


pop(Key::key(), AssocList::assoc_list()) -> {Result, assoc_list()}

キーに対応するリストの先頭から値を取り出す.

キーが存在しない場合は、要素の値が空リストとして扱われる (つまり結果として empty が返される).
キーに対応する値が存在し、かつリスト以外の場合は、例外が送出される.

push/3


push(Key::key(), Value::value(), AssocList::assoc_list()) -> assoc_list()

キーに対応するリストの先頭に値を追加する.

キーが存在しない場合は、追加する値のみを含むリストが新規に生成される.
キーに対応する値が存在し、かつリスト以外の場合は、例外が送出される.

rfetch/2


rfetch(KeyList::[key()], AssocList::assoc_list()) -> value()

再帰的にキーに対応する値を取得する.

KeyListの先頭から順番に対応する値を取得し、その値に対してfetchを適用する
キーが存在しない場合は、例外が送出される. 以下、使用例:

  > rfetch([key1_1, key2_1, key3_1],
  >        [{key1_1, [{key2_1, [{key3_1, value3_1}, {key3_2, value3_2}]}, {key2_2, value2_2}]}, {key1_2, value1_2}]
  >       ).
  value3_1

rfetch/3


rfetch(KeyList::[key()], AssocList::assoc_list(), DefaultValue::value()) -> value()

再帰的にキーに対応する値を取得する.

KeyListの先頭から順番に対応する値を取得し、その値に対してfetchを適用する
キーが存在しない場合は、デフォルト値が代わりに返される. 以下、使用例:

  > rfetch([key1_1, key2_1, key3_1, key4_1],
  >        [{key1_1, [{key2_1, [{key3_1, value3_1}, {key3_2, value3_2}]}, {key2_2, value2_2}]}, {key1_2, value1_2}],
  >        default_value
  >       ).
  default_value

rupdate/4


rupdate(KeyList::[key()], UpdateFun, Initial, AssocList::assoc_list()) -> assoc_list()

キーリストに対応する要素の値を更新する.

キー(リスト)に対応する要素が存在しない場合はInitialを値とする要素が新たに追加される.
キーリストの初めの方でタプルがなかった場合でも追加される.
以下、キーがない場合の例:

  > rupdate([banana, color], fun(X) -> X end, yellow, []).
  [{banana, [{color, yellow}]}]

store/3


store(Key::key(), Value::value(), AssocList::assoc_list()) -> assoc_list()

連想リストに要素を追加する.

追加しようとしている要素のキーが既に存在している場合は、その要素が取り除かれた上で、新しい要素が追加される。

store_if_not_exist/3


store_if_not_exist(Key::key(), Value::value(), AssocList::assoc_list()) -> {Stored::boolean(), assoc_list()}

既にキーが存在しない場合にのみ、連想リストに要素を追加する.

take/2


take(Key::key(), AssocList::assoc_list()) -> error | {ok, value(), assoc_list()}

キーに対応する要素を連想リストから取り出す(取り除く)

to_map/1


to_map(Fields::moyo_assoc:assoc_list()) -> #{}

連想リストからmapを生成する.

連想リスト内に重複するキーが存在した場合は先に現れた値が使われる.

ex:

  1> moyo_assoc:to_map([{key1, value1}, {key2, value2}, {key3, value3}, {key4, value4}, {key5, value5}]).
  #{key1 => value1,key2 => value2,key3 => value3,key4 => value4,key5 => value5}

to_map_recur/1


to_map_recur(NestAssocList::assoc_list()) -> #{}

ネストしたassocListをmapに変換する

assocListをMapに変換する また、各Valに対し再帰的にこの関数を適用する

ex:

  to_map_recur([{#{},[]},{[],[]}]).
  #{#{}=>#{},[]=>#{}}

to_record/3


to_record(RecordName, Fields, Params) -> Record
  • RecordName = atom()
  • Fields = [atom()]
  • Params = assoc_list()
  • Record = tuple()

連想リストからレコードを生成する.

Fields の値は record_info(fields, RecordName) で取得できる.
以下、使用例:

  > rd(sample, {a, b, c}).
  > Params = [{a, 10}, {b, 20}, {c, 30}].
  > moyo_assoc:to_record(sample, record_info(fields, sample), Params).
  #sample{a = 10,b = 20,c = 30}

to_record_as/4


to_record_as(RecordName, Fields, FieldSpecList, Params) -> {ok, assoc_list()} | {error, Reason}

連想リストからレコードを生成する.

連想リストから要素を取得する際にはmoyo_validatorを使用して、値の検証および変換が行われる.
以下のコードとほぼ等価:

  > {ok, Entries} = lookup_entries_as(FieldSpecList, Params).
  > to_record(RecordName, Fields, Entries).

unique_by_key/1


unique_by_key(AssocList::assoc_list()) -> assoc_list()

重複したキーを持つ要素を除去した連想リストを生成する.

重複するキーが存在した場合は先に現れた値が使われる.

ex:

  1> moyo_assoc:unique_by_key([{key1, value1}, {key1, value2}]).
  [{key1, value1}]

update/4


update(Key::key(), UpdateFun, Initial, AssocList::assoc_list()) -> assoc_list()

キーに対応する要素の値を更新する.

キーに対応する要素が存在しない場合はInitialを値とする要素が新たに追加される.

values/1


values(AssocList::assoc_list()) -> [value()]

値のリストを生成する.

この関数は重複キーを考慮しない.
重複キーが除去された情報が欲しい場合は, unique_by_key/1 との併用を検討すること.

ex:

  1> moyo_assoc:values([{key1, value1}, {key2, value2}]).
  [value1, value2]