トラック
/
Elm
Elm
/
演習
/
トップスコアラー
トップスコアラー

トップスコアラー

学習演習

はじめに

Dict

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の使い方と区別するためにあります。

1. 得点者を集計する

まず、ある選手のゴール数を初期化またはインクリメントするupdateGoalCountForPlayerを実装します。 次に、updateGoalCountForPlayerを使うことで、各選手が何ゴール決めたかを数えるaggregateScorers関数を実装します。 この関数はList PlayerName(得点した選手の名前のリストで、同じ選手の名前が重複して含まれることもあります)を受け取り、リスト内のすべての選手と、それぞれが決めたゴール数を表すDict PlayerName Intを返します。

aggregateScorers [ "Betty", "Cedd", "Betty" ]
--> Dict.fromList [ ( "Betty", 2 ), ( "Cedd", 1 ) ]

2. 重要でない選手を除外する

removeInsignificantPlayers関数を実装して、しきい値よりも少ないゴールしか決めていない選手を除外します。

removeInsignificantPlayers 2 (Dict.fromList [ ( "Betty", 2 ), ( "Cedd", 1 ) ] )
--> Dict.fromList [ ( "Betty", 2 ) ]

3. 選手のゴール数をリセットする

resetPlayerGoalCount関数を実装して、ある選手のゴール数を0にリセットします。

resetPlayerGoalCount "Cedd" (Dict.fromList [ ( "Betty", 2 ), ( "Cedd", 1 ) ] )
--> Dict.fromList [ ( "Betty", 2 ), ( "Cedd", 0 ) ]

4. 1人の選手のゴール数を整形する

1人の選手のゴール数を文字列として整形するformatPlayer関数を実装します。その選手がdictに存在しない場合は、ゴール数を0とみなします。

formatPlayer "Betty" (Dict.fromList [ ( "Betty", 2 ), ( "Cedd", 1 ) ] )
--> "Betty: 2"

5. すべての選手のゴール数を整形する

すべての選手のゴール数を文字列として整形するformatPlayers関数を実装します。選手は名前の順に並べます。

formatPlayers (Dict.fromList [ ( "Betty", 2 ), ( "Cedd", 1 ) ] )
--> "Betty: 2, Cedd: 1"

6. 試合を合算する

combineGames関数を実装して、両方の試合で各選手が決めたゴールの合計数を数えます。

combine Games
  (Dict.fromList [ ( "Betty", 2 ), ( "Cedd", 1 ) ] )
  (Dict.fromList [ ( "Betty", 2 ), ( "Mario", 3 ) ] )
--> Dict.fromList [ ( "Betty", 4 ), ( "Cedd", 1 ), ( "Mario", 3 ) ]
GitHubで編集する リンクは新しいウィンドウまたはタブで開きます
Elm Exercism

トップスコアラーを始める準備はできましたか?

Exercismに登録すれば、28個のコンセプト110個の演習、そして本物の人間によるメンタリングとともに、Elmを学んでマスターできます。すべて無料です。