Skip to content

Commit

Permalink
Add tournament exercise
Browse files Browse the repository at this point in the history
  • Loading branch information
ageron committed Oct 13, 2024
1 parent d53f7d1 commit acc49f3
Show file tree
Hide file tree
Showing 8 changed files with 465 additions and 0 deletions.
8 changes: 8 additions & 0 deletions config.json
Original file line number Diff line number Diff line change
Expand Up @@ -715,6 +715,14 @@
"prerequisites": [],
"difficulty": 6
},
{
"slug": "tournament",
"name": "Tournament",
"uuid": "aca2366a-03d8-47c5-b007-2d89f73a54dc",
"practices": [],
"prerequisites": [],
"difficulty": 6
},
{
"slug": "two-bucket",
"name": "Two Bucket",
Expand Down
66 changes: 66 additions & 0 deletions exercises/practice/tournament/.docs/instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# Instructions

Tally the results of a small football competition.

Based on an input file containing which team played against which and what the outcome was, create a file with a table like this:

```text
Team | MP | W | D | L | P
Devastating Donkeys | 3 | 2 | 1 | 0 | 7
Allegoric Alaskans | 3 | 2 | 0 | 1 | 6
Blithering Badgers | 3 | 1 | 0 | 2 | 3
Courageous Californians | 3 | 0 | 1 | 2 | 1
```

What do those abbreviations mean?

- MP: Matches Played
- W: Matches Won
- D: Matches Drawn (Tied)
- L: Matches Lost
- P: Points

A win earns a team 3 points.
A draw earns 1.
A loss earns 0.

The outcome is ordered by points, descending.
In case of a tie, teams are ordered alphabetically.

## Input

Your tallying program will receive input that looks like:

```text
Allegoric Alaskans;Blithering Badgers;win
Devastating Donkeys;Courageous Californians;draw
Devastating Donkeys;Allegoric Alaskans;win
Courageous Californians;Blithering Badgers;loss
Blithering Badgers;Devastating Donkeys;loss
Allegoric Alaskans;Courageous Californians;win
```

The result of the match refers to the first team listed.
So this line:

```text
Allegoric Alaskans;Blithering Badgers;win
```

means that the Allegoric Alaskans beat the Blithering Badgers.

This line:

```text
Courageous Californians;Blithering Badgers;loss
```

means that the Blithering Badgers beat the Courageous Californians.

And this line:

```text
Devastating Donkeys;Courageous Californians;draw
```

means that the Devastating Donkeys and Courageous Californians tied.
108 changes: 108 additions & 0 deletions exercises/practice/tournament/.meta/Example.roc
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
module [tally]

MatchResult : [Win, Loss, Draw]
TeamTally : { mp : U64, w : U64, d : U64, l : U64, p : U64 }

tally : Str -> Result Str [InvalidRow Str, InvalidResult Str]
tally = \table ->
if table == "" then
Ok header
else

table
|> Str.split "\n"
|> List.mapTry? \row ->
when row |> Str.split ";" is
[team1, team2, resultStr] ->
result = resultStr |> parseResult?
Ok (team1, team2, result)

_ -> Err (InvalidRow row)
|> List.walk (Dict.empty {}) \tallyDict, (team1, team2, result) ->
tallyDict
|> updateTallyDict team1 result
|> updateTallyDict team2 (oppositeResult result)
|> tallyDictToTable
|> Ok

parseResult : Str -> Result MatchResult [InvalidResult Str]
parseResult = \resultStr ->
when resultStr is
"win" -> Ok Win
"loss" -> Ok Loss
"draw" -> Ok Draw
_ -> Err (InvalidResult resultStr)

oppositeResult : MatchResult -> MatchResult
oppositeResult = \result ->
when result is
Win -> Loss
Loss -> Win
Draw -> Draw

updateTallyDict : Dict Str TeamTally, Str, MatchResult -> Dict Str TeamTally
updateTallyDict = \tallyDict, team, result ->
tallyDict
|> Dict.update team \maybeTeamTally ->
when maybeTeamTally is
Ok teamTally -> Ok (teamTally |> updateTeamTally result)
Err Missing -> Ok ({ mp: 0, w: 0, d: 0, l: 0, p: 0 } |> updateTeamTally result)

updateTeamTally : TeamTally, MatchResult -> TeamTally
updateTeamTally = \teamTally, result ->
when result is
Win -> { teamTally & mp: teamTally.mp + 1, w: teamTally.w + 1, p: teamTally.p + 3 }
Draw -> { teamTally & mp: teamTally.mp + 1, d: teamTally.d + 1, p: teamTally.p + 1 }
Loss -> { teamTally & mp: teamTally.mp + 1, l: teamTally.l + 1 }

tallyDictToTable : Dict Str TeamTally -> Str
tallyDictToTable = \tallyDict ->
tableContent =
tallyDict
|> Dict.toList
|> List.sortWith \(team1, teamTally1), (team2, teamTally2) ->
when Num.compare teamTally1.p teamTally2.p is
GT -> LT
LT -> GT
EQ -> compareStrings team1 team2
|> List.map \(team, teamTally) ->
tallyColumns =
[.mp, .w, .d, .l, .p]
|> List.map \field -> teamTally |> field |> alignRight
|> Str.joinWith " | "
"$(team |> padRight 30) | $(tallyColumns)"
|> Str.joinWith "\n"
"$(header)\n$(tableContent)"

header : Str
header = "Team | MP | W | D | L | P"

## Compare two strings, first by their UTF8 representations, then by length:
## "" < "ABC" < "abc" < "abcdef"
compareStrings : Str, Str -> [LT, EQ, GT]
compareStrings = \string1, string2 ->
b1 = string1 |> Str.toUtf8
b2 = string2 |> Str.toUtf8
result =
List.map2 b1 b2 \c1, c2 -> Num.compare c1 c2
|> List.walkTry (Ok EQ) \_state, cmp ->
when cmp is
EQ -> Ok EQ
res -> Err res
when result is
Ok _cmp -> Num.compare (List.len b1) (List.len b2)
Err res -> res

## Pad a string with spaces on the right to reach a given width.
## Warning: this function assumes that the input string is ASCII
padRight : Str, U64 -> Str
padRight = \string, width ->
chars = string |> Str.toUtf8
length = chars |> List.len
spaces = if length < width then List.repeat " " (width - length) |> Str.joinWith "" else ""
"$(string)$(spaces)"

## Convert a number to a right-aligned string of width 2 or more
alignRight : U64 -> Str
alignRight = \number ->
if number < 10 then " $(number |> Num.toStr)" else "$(number |> Num.toStr)"
17 changes: 17 additions & 0 deletions exercises/practice/tournament/.meta/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"authors": [
"ageron"
],
"files": {
"solution": [
"Tournament.roc"
],
"test": [
"tournament-test.roc"
],
"example": [
".meta/Example.roc"
]
},
"blurb": "Tally the results of a small football competition."
}
15 changes: 15 additions & 0 deletions exercises/practice/tournament/.meta/template.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{%- import "generator_macros.j2" as macros with context -%}
{{ macros.canonical_ref() }}
{{ macros.header() }}

import {{ exercise | to_pascal }} exposing [{{ cases[0]["property"] | to_camel }}]

{% for case in cases -%}
# {{ case["description"] }}
expect
table = {{ case["input"]["rows"] | to_roc_multiline_string | indent(8) }}
result = {{ case["property"] | to_camel }} table
expected = Ok {{ case["expected"] | to_roc_multiline_string | indent(8) }}
result == expected

{% endfor %}
46 changes: 46 additions & 0 deletions exercises/practice/tournament/.meta/tests.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# This is an auto-generated file.
#
# Regenerating this file via `configlet sync` will:
# - Recreate every `description` key/value pair
# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications
# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion)
# - Preserve any other key/value pair
#
# As user-added comments (using the # character) will be removed when this file
# is regenerated, comments can be added via a `comment` key.

[67e9fab1-07c1-49cf-9159-bc8671cc7c9c]
description = "just the header if no input"

[1b4a8aef-0734-4007-80a2-0626178c88f4]
description = "a win is three points, a loss is zero points"

[5f45ac09-4efe-46e7-8ddb-75ad85f86e05]
description = "a win can also be expressed as a loss"

[fd297368-efa0-442d-9f37-dd3f9a437239]
description = "a different team can win"

[26c016f9-e753-4a93-94e9-842f7b4d70fc]
description = "a draw is one point each"

[731204f6-4f34-4928-97eb-1c307ba83e62]
description = "There can be more than one match"

[49dc2463-42af-4ea6-95dc-f06cc5776adf]
description = "There can be more than one winner"

[6d930f33-435c-4e6f-9e2d-63fa85ce7dc7]
description = "There can be more than two teams"

[97022974-0c8a-4a50-8fe7-e36bdd8a5945]
description = "typical input"

[fe562f0d-ac0a-4c62-b9c9-44ee3236392b]
description = "incomplete competition (not all pairs have played)"

[3aa0386f-150b-4f99-90bb-5195e7b7d3b8]
description = "ties broken alphabetically"

[f9e20931-8a65-442a-81f6-503c0205b17a]
description = "ensure points sorted numerically"
5 changes: 5 additions & 0 deletions exercises/practice/tournament/Tournament.roc
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module [tally]

tally : Str -> Result Str _
tally = \table ->
crash "Please implement the 'tally' function"
Loading

0 comments on commit acc49f3

Please sign in to comment.