ElmのDictは、0個以上のキーと値のペアを扱うイミュータブルな辞書です。
Dictは、Dictモジュールで定義されている関数と演算子によって操作します。
Dictの型注釈は、次のように書きます。
Dict String (List Int) --> a dict with String keys and List Int values
Dict Int String --> a dict with Int keys and String values
Dictは、次のようにして作成できます。
empty : Dict Int String
empty = Dict.empty
singleValue = Dict.singleton 5 "Value for key 5" --> Dict Int String
twoValues = Dict.fromList [ ( "Alice", 0 ), ( "Bob", 1 ) ] --> Dict String Int
要素はgetを使って取得できます。
キーがDictに存在する場合もあれば存在しない場合もあるため、結果はMaybeで包まれます。
alice = Dict.fromList [ ( 0, "Alice" ) ]
keyExists = Dict.get 0 alice --> Just "Alice"
keyNotPresent = Dict.get 1 alice --> Nothing
要素はinsertを使って追加できます。
すでにDictにあるキーの要素は置き換えられます。
alice = Dict.fromList [ ( "Alice", 0 ) ]
aliceAndBob = Dict.insert "Bob" 1 alice --> Dict.fromList [ ( "Alice", 0 ), ( "Bob", 1 ) ]
aliceAndAlice = Dict.insert "Alice" 1 alice --> Dict.fromList [ ( "Alice", 1 ) ]
要素はupdateを使って更新できます。
キーが存在しない場合は、更新関数がNothingとともに呼び出されます。
alice = Dict.fromList [ ( 0, "Alice" ) ]
aliceUpperCase = Dict.update 0 (Maybe.map String.toUpper) alice --> Dict.fromList [ ( 0, "ALICE" ) ]
aliceNoCase = Dict.update 0 (\_ -> Nothing) alice --> Dict.empty
bobUpperCase = Dict.update 1 (\_ -> Just "BOB") alice --> Dict.fromList [ ( 0, "Alice" ), ( 1, "BOB" ) ]
bobNoCase = Dict.update 1 (\_ -> Nothing) alice --> Dict.fromList [ ( 0, "Alice" ) ]
要素はremoveを使って削除できます。
キーが存在しない場合は、何も変更されません。
alice = Dict.fromList [ ( 0, "Alice" ) ]
stillAlice = Dict.remove 1 --> ( 0, "Alice" )
empty = Dict.remove 0 alice --> Dict.empty
キーと値のペアの配列は、fromListでDictに変換できます。
alice = Dict.fromList [ ( 0, "Bob" ), ( 0, "Alice" ) ]
--> True Dict.fromList [ ( 0, "Alice" ) ]
要素はtoListを使って配列に変換できます。
配列はキーによって並べ替えられます。
aliceAndBob = Dict.fromList [ ( 1, "Alice" ), ( 0, "Bob" ) ]
bobAndAlice = Dict.toList aliceAndBob
--> [ ( 0, "Bob" ), ( 1, "Alice" ) ]
複数の要素はfilterを使って削除できます。
aliceAndBob = Dict.fromList [ ( "Alice", 1 ), ( "Bob", 0 ) ]
bob = Dict.filter (\name count -> name == "Bob") aliceAndBob --> Dict.fromList [ ( "Bob", 0 ) ]
要素はmapを使って変換できます。
alice = Dict.fromList [ ( "Alice", 0 ) ]
empty = Dict.map (\player count -> count + 1) alice --> Dict.fromList [ ( "Alice", 1 ) ]
Dictはmergeを使って結合・変換できます。
aliceAndBob = Dict.fromList [ ( "Alice", 1 ), ( "Bob", 1 ) ]
bobAndCedd = Dict.fromList [ ( "Bob", 1 ), ( "Cedd", 1 ) ]
firstDictCountsDouble = Dict.merge
-- when only in aliceAndBob
(\name count mergedCounts -> Dict.insert name (2 * count) mergedCounts)
-- when in aliceAndBob and bobAndCedd
(\name aliceAndBobCount bobAndCeddCount mergedCounts -> Dict.insert name (2 * aliceAndBobCount + bobAndCeddCount) mergedCounts)
-- when only in bobAndCedd
(\name count mergedCounts -> Dict.insert name count mergedCounts)
-- the two dicts to merge
aliceAndBob
bobAndCedd
-- the initial state of the merge
Dict.empty
--> Dict.fromList [ ( "Alice", 2 ), ( "Bob", 3 ), ( "Cedd", 1 ) ]
Dictを変更するように見える関数や演算子(要素の追加など)は、実際には新しいDictを返します。 実装が不要な割り当てやコピーを防ぐため、通常はパフォーマンスが問題になることはありません。
Elmでは一般に、Dict.getやDict.removeのような低レベルな抽象化よりも、Dict.map、Dict.filter、Dict.mergeのような高レベルな抽象化を使うほうがよいとされています。もちろん、状況によって異なります。
この課題では、試合の得点者のリストを受け取り、各選手が何ゴール決めたかを数えます。
PlayerNameはStringの型エイリアスです。コードを読みやすくし、このStringの使い方を他のStringの使い方と区別するためにあります。
まず、ある選手のゴール数を初期化またはインクリメントするupdateGoalCountForPlayerを実装します。
次に、updateGoalCountForPlayerを使うことで、各選手が何ゴール決めたかを数えるaggregateScorers関数を実装します。
この関数はList PlayerName(得点した選手の名前のリストで、同じ選手の名前が重複して含まれることもあります)を受け取り、リスト内のすべての選手と、それぞれが決めたゴール数を表すDict PlayerName Intを返します。
aggregateScorers [ "Betty", "Cedd", "Betty" ]
--> Dict.fromList [ ( "Betty", 2 ), ( "Cedd", 1 ) ]
removeInsignificantPlayers関数を実装して、しきい値よりも少ないゴールしか決めていない選手を除外します。
removeInsignificantPlayers 2 (Dict.fromList [ ( "Betty", 2 ), ( "Cedd", 1 ) ] )
--> Dict.fromList [ ( "Betty", 2 ) ]
resetPlayerGoalCount関数を実装して、ある選手のゴール数を0にリセットします。
resetPlayerGoalCount "Cedd" (Dict.fromList [ ( "Betty", 2 ), ( "Cedd", 1 ) ] )
--> Dict.fromList [ ( "Betty", 2 ), ( "Cedd", 0 ) ]
1人の選手のゴール数を文字列として整形するformatPlayer関数を実装します。その選手がdictに存在しない場合は、ゴール数を0とみなします。
formatPlayer "Betty" (Dict.fromList [ ( "Betty", 2 ), ( "Cedd", 1 ) ] )
--> "Betty: 2"
すべての選手のゴール数を文字列として整形するformatPlayers関数を実装します。選手は名前の順に並べます。
formatPlayers (Dict.fromList [ ( "Betty", 2 ), ( "Cedd", 1 ) ] )
--> "Betty: 2, Cedd: 1"
combineGames関数を実装して、両方の試合で各選手が決めたゴールの合計数を数えます。
combine Games
(Dict.fromList [ ( "Betty", 2 ), ( "Cedd", 1 ) ] )
(Dict.fromList [ ( "Betty", 2 ), ( "Mario", 3 ) ] )
--> Dict.fromList [ ( "Betty", 4 ), ( "Cedd", 1 ), ( "Mario", 3 ) ]