轨道
/
Elm
Elm
/
练习
/
最佳射手
最佳射手

最佳射手

学习练习

简介

Dict

Elm 中的 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

可以用下面的方式创建字典:

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获取元素。 因为某个键可能存在于字典中,也可能不存在,所以结果会被包裹在Maybe里。

alice = Dict.fromList [ ( 0, "Alice" ) ]
keyExists = Dict.get 0 alice --> Just "Alice"
keyNotPresent = Dict.get 1 alice --> Nothing

可以使用insert添加元素。 如果键已经存在于字典中,它对应的元素会被替换。

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把键值对的列表转换成字典。

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 ) ]

可以使用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 ) ]

任何看似会修改字典的函数或运算符(比如添加一个元素),实际上都会返回一个新的字典。 性能通常不是问题,因为实现避免了不必要的分配和复制。

在 Elm 中,通常最好使用更高层的抽象,比如Dict.map、Dict.filter和Dict.merge,而不是Dict.get和Dict.remove这类更低层的抽象,当然这也要看具体情况。

说明

你的任务是拿到一份比赛中进球球员的名单,然后统计每位球员打进了多少球。

PlayerName是String的类型别名,这样代码更容易阅读,也能把String的这种用法与它的其他用法区分开。

1. 汇总得分球员

首先实现updateGoalCountForPlayer,用来初始化或自增某位球员的进球数。 然后实现aggregateScorers函数,借助updateGoalCountForPlayer统计每位球员打进了多少球。 这个函数接收一个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函数,把某位球员的进球数重置为零。

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

4. 格式化单个球员的进球数

实现formatPlayer函数,把单个球员的进球数格式化成字符串。如果字典中没有该球员,就认为他的进球数为零。

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,全部免费。