트랙
/
Elm
Elm
/
연습 문제
/
최고 득점자
최고 득점자

최고 득점자

학습 연습 문제

소개

Dict

Elm의 Dict는 키-값 쌍을 0개 이상 담는 불변 딕셔너리예요.

딕셔너리는 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.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. 선수의 골 수 초기화하기

선수가 넣은 골 수를 0으로 초기화하는 resetPlayerGoalCount 함수를 구현해요.

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

4. 한 선수의 골 수 포맷하기

한 선수의 골 수를 문자열로 포맷하는 formatPlayer 함수를 구현해요. 딕셔너리에 그 선수가 없으면 골 수가 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에 가입하고 Elm 트랙을 개념 28개연습 문제 110개, 그리고 실제 사람의 멘토링과 함께 배우고 익혀 보세요. 모두 무료예요.