-
-
Notifications
You must be signed in to change notification settings - Fork 8
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add Alphametics exercise #105
Merged
Merged
Changes from 7 commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
1851ac4
generate tests
isaacvando b58723d
tests passing
isaacvando 8b026d2
return list instead of set to make test failure output useful
isaacvando 248ae27
renaming
isaacvando 4012af4
cleanup
isaacvando 815aa54
bump difficulty
isaacvando 0bb3b57
more renaming
isaacvando 13df32d
update stub type
isaacvando 3da6be5
merge
isaacvando File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
# Instructions | ||
|
||
Given an alphametics puzzle, find the correct solution. | ||
|
||
[Alphametics][alphametics] is a puzzle where letters in words are replaced with numbers. | ||
|
||
For example `SEND + MORE = MONEY`: | ||
|
||
```text | ||
S E N D | ||
M O R E + | ||
----------- | ||
M O N E Y | ||
``` | ||
|
||
Replacing these with valid numbers gives: | ||
|
||
```text | ||
9 5 6 7 | ||
1 0 8 5 + | ||
----------- | ||
1 0 6 5 2 | ||
``` | ||
|
||
This is correct because every letter is replaced by a different number and the words, translated into numbers, then make a valid sum. | ||
|
||
Each letter must represent a different digit, and the leading digit of a multi-digit number must not be zero. | ||
|
||
[alphametics]: https://en.wikipedia.org/wiki/Alphametics |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
module [solve] | ||
|
||
solve : Str -> Result (List (U8, U8)) _ | ||
solve = \problem -> | ||
{ addends, sum } = parse? problem | ||
|
||
# We can represent the equation as a dictionary of the letters mapped to their coefficients | ||
# when we simplify the equation. For example, we can write AB + A + B == C as 11A + 2B + (-1)C == 0. | ||
# That then becomes this dictionary: `Dict.fromList [('A', 11), ('B', 2), ('C', -1)] | ||
equation = | ||
List.walk addends (Dict.empty {}) \dict, term -> | ||
insertTerm dict term 1 | ||
|> insertTerm sum -1 | ||
|
||
leadingDigits = | ||
List.map addends \letters -> | ||
List.first letters |> Result.withDefault 0 | ||
|> Set.fromList | ||
|> Set.insert (List.first sum |> Result.withDefault 0) | ||
|
||
findMatch = \assignments, remainingVars, remainingDigits -> | ||
when remainingVars is | ||
[] -> | ||
totalVal = | ||
List.walk assignments 0 \total, (letter, value) -> | ||
Dict.get equation letter | ||
|> Result.withDefault 0 | ||
|> Num.mul (Num.toI64 value) | ||
|> Num.add total | ||
|
||
if totalVal != 0 then | ||
Err InvalidAssignment | ||
else | ||
|
||
Ok assignments | ||
|
||
[letter, .. as rest] -> | ||
findFirstOk remainingDigits \digit -> | ||
if digit == 0 && Set.contains leadingDigits letter then | ||
Err InvalidAssignment | ||
else | ||
|
||
# Each digit has to be unique, so once we use a digit we remove it from the pool | ||
findMatch (List.append assignments (letter, digit)) rest (Set.remove remainingDigits digit) | ||
|
||
digits = List.range { start: At 0, end: At 9 } |> Set.fromList | ||
findMatch [] (Dict.keys equation) digits | ||
|
||
# Apply a function to each element of a list until the function returns an Ok, then return that value | ||
findFirstOk : Set a, (a -> Result b err) -> Result b [NotFound] | ||
findFirstOk = \set, func -> | ||
Set.walkUntil set (Err NotFound) \state, elem -> | ||
when func elem is | ||
Err _ -> Continue state | ||
Ok val -> Break (Ok val) | ||
|
||
# Update the equation with the values of a term | ||
insertTerm : Dict U8 I64, List U8, I64 -> Dict U8 I64 | ||
insertTerm = \equation, letters, polarity -> | ||
List.reverse letters | ||
|> List.walkWithIndex equation \dict, letter, index -> | ||
coeff = | ||
Num.powInt 10 index | ||
|> Num.toI64 | ||
|> Num.mul polarity | ||
Dict.update dict letter \val -> | ||
when val is | ||
Missing -> Present coeff | ||
Present c -> Present (c + coeff) | ||
|
||
parse : Str -> Result { addends : List (List U8), sum : List U8 } _ | ||
parse = \problem -> | ||
{ before, after } = Str.splitFirst? problem " == " | ||
addends = | ||
Str.split before " + " | ||
|> List.map Str.toUtf8 | ||
Ok { addends, sum: Str.toUtf8 after } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
{ | ||
"authors": [ | ||
"isaacvando" | ||
], | ||
"files": { | ||
"solution": [ | ||
"Alphametics.roc" | ||
], | ||
"test": [ | ||
"alphametics-test.roc" | ||
], | ||
"example": [ | ||
".meta/Example.roc" | ||
] | ||
}, | ||
"blurb": "Given an alphametics puzzle, find the correct solution." | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
{%- 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 | ||
result = {{ case["property"] | to_camel }} {{ case["input"]["puzzle"] | to_roc }} | ||
{%- if case["expected"] %} | ||
Result.withDefault result [] |> Set.fromList == Set.fromList [ | ||
{%- for letter, value in case["expected"].items() %} | ||
('{{ letter }}', {{ value }}), | ||
{%- endfor %} | ||
] | ||
{%- else %} | ||
Result.isErr result | ||
{%- endif %} | ||
|
||
{% endfor %} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
# 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. | ||
|
||
[e0c08b07-9028-4d5f-91e1-d178fead8e1a] | ||
description = "puzzle with three letters" | ||
|
||
[a504ee41-cb92-4ec2-9f11-c37e95ab3f25] | ||
description = "solution must have unique value for each letter" | ||
|
||
[4e3b81d2-be7b-4c5c-9a80-cd72bc6d465a] | ||
description = "leading zero solution is invalid" | ||
|
||
[8a3e3168-d1ee-4df7-94c7-b9c54845ac3a] | ||
description = "puzzle with two digits final carry" | ||
|
||
[a9630645-15bd-48b6-a61e-d85c4021cc09] | ||
description = "puzzle with four letters" | ||
|
||
[3d905a86-5a52-4e4e-bf80-8951535791bd] | ||
description = "puzzle with six letters" | ||
|
||
[4febca56-e7b7-4789-97b9-530d09ba95f0] | ||
description = "puzzle with seven letters" | ||
|
||
[12125a75-7284-4f9a-a5fa-191471e0d44f] | ||
description = "puzzle with eight letters" | ||
|
||
[fb05955f-38dc-477a-a0b6-5ef78969fffa] | ||
description = "puzzle with ten letters" | ||
|
||
[9a101e81-9216-472b-b458-b513a7adacf7] | ||
description = "puzzle with ten letters and 199 addends" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
module [solve] | ||
|
||
solve : Str -> Result (List (U8, U64)) _ | ||
solve = \problem -> | ||
crash "Please implement 'solve'" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,137 @@ | ||
# These tests are auto-generated with test data from: | ||
# https://github.com/exercism/problem-specifications/tree/main/exercises/alphametics/canonical-data.json | ||
# File last updated on 2024-09-22 | ||
app [main] { | ||
pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.15.0/SlwdbJ-3GR7uBWQo6zlmYWNYOxnvo8r6YABXD-45UOw.tar.br", | ||
} | ||
|
||
main = | ||
Task.ok {} | ||
|
||
import Alphametics exposing [solve] | ||
|
||
# puzzle with three letters | ||
expect | ||
result = solve "I + BB == ILL" | ||
Result.withDefault result [] | ||
|> Set.fromList | ||
== Set.fromList [ | ||
('I', 1), | ||
('B', 9), | ||
('L', 0), | ||
] | ||
|
||
# solution must have unique value for each letter | ||
expect | ||
result = solve "A == B" | ||
Result.isErr result | ||
|
||
# leading zero solution is invalid | ||
expect | ||
result = solve "ACA + DD == BD" | ||
Result.isErr result | ||
|
||
# puzzle with two digits final carry | ||
expect | ||
result = solve "A + A + A + A + A + A + A + A + A + A + A + B == BCC" | ||
Result.withDefault result [] | ||
|> Set.fromList | ||
== Set.fromList [ | ||
('A', 9), | ||
('B', 1), | ||
('C', 0), | ||
] | ||
|
||
# puzzle with four letters | ||
expect | ||
result = solve "AS + A == MOM" | ||
Result.withDefault result [] | ||
|> Set.fromList | ||
== Set.fromList [ | ||
('A', 9), | ||
('S', 2), | ||
('M', 1), | ||
('O', 0), | ||
] | ||
|
||
# puzzle with six letters | ||
expect | ||
result = solve "NO + NO + TOO == LATE" | ||
Result.withDefault result [] | ||
|> Set.fromList | ||
== Set.fromList [ | ||
('N', 7), | ||
('O', 4), | ||
('T', 9), | ||
('L', 1), | ||
('A', 0), | ||
('E', 2), | ||
] | ||
|
||
# puzzle with seven letters | ||
expect | ||
result = solve "HE + SEES + THE == LIGHT" | ||
Result.withDefault result [] | ||
|> Set.fromList | ||
== Set.fromList [ | ||
('E', 4), | ||
('G', 2), | ||
('H', 5), | ||
('I', 0), | ||
('L', 1), | ||
('S', 9), | ||
('T', 7), | ||
] | ||
|
||
# puzzle with eight letters | ||
expect | ||
result = solve "SEND + MORE == MONEY" | ||
Result.withDefault result [] | ||
|> Set.fromList | ||
== Set.fromList [ | ||
('S', 9), | ||
('E', 5), | ||
('N', 6), | ||
('D', 7), | ||
('M', 1), | ||
('O', 0), | ||
('R', 8), | ||
('Y', 2), | ||
] | ||
|
||
# puzzle with ten letters | ||
expect | ||
result = solve "AND + A + STRONG + OFFENSE + AS + A + GOOD == DEFENSE" | ||
Result.withDefault result [] | ||
|> Set.fromList | ||
== Set.fromList [ | ||
('A', 5), | ||
('D', 3), | ||
('E', 4), | ||
('F', 7), | ||
('G', 8), | ||
('N', 0), | ||
('O', 2), | ||
('R', 1), | ||
('S', 6), | ||
('T', 9), | ||
] | ||
|
||
# puzzle with ten letters and 199 addends | ||
expect | ||
result = solve "THIS + A + FIRE + THEREFORE + FOR + ALL + HISTORIES + I + TELL + A + TALE + THAT + FALSIFIES + ITS + TITLE + TIS + A + LIE + THE + TALE + OF + THE + LAST + FIRE + HORSES + LATE + AFTER + THE + FIRST + FATHERS + FORESEE + THE + HORRORS + THE + LAST + FREE + TROLL + TERRIFIES + THE + HORSES + OF + FIRE + THE + TROLL + RESTS + AT + THE + HOLE + OF + LOSSES + IT + IS + THERE + THAT + SHE + STORES + ROLES + OF + LEATHERS + AFTER + SHE + SATISFIES + HER + HATE + OFF + THOSE + FEARS + A + TASTE + RISES + AS + SHE + HEARS + THE + LEAST + FAR + HORSE + THOSE + FAST + HORSES + THAT + FIRST + HEAR + THE + TROLL + FLEE + OFF + TO + THE + FOREST + THE + HORSES + THAT + ALERTS + RAISE + THE + STARES + OF + THE + OTHERS + AS + THE + TROLL + ASSAILS + AT + THE + TOTAL + SHIFT + HER + TEETH + TEAR + HOOF + OFF + TORSO + AS + THE + LAST + HORSE + FORFEITS + ITS + LIFE + THE + FIRST + FATHERS + HEAR + OF + THE + HORRORS + THEIR + FEARS + THAT + THE + FIRES + FOR + THEIR + FEASTS + ARREST + AS + THE + FIRST + FATHERS + RESETTLE + THE + LAST + OF + THE + FIRE + HORSES + THE + LAST + TROLL + HARASSES + THE + FOREST + HEART + FREE + AT + LAST + OF + THE + LAST + TROLL + ALL + OFFER + THEIR + FIRE + HEAT + TO + THE + ASSISTERS + FAR + OFF + THE + TROLL + FASTS + ITS + LIFE + SHORTER + AS + STARS + RISE + THE + HORSES + REST + SAFE + AFTER + ALL + SHARE + HOT + FISH + AS + THEIR + AFFILIATES + TAILOR + A + ROOFS + FOR + THEIR + SAFE == FORTRESSES" | ||
Result.withDefault result [] | ||
|> Set.fromList | ||
== Set.fromList [ | ||
('A', 1), | ||
('E', 0), | ||
('F', 5), | ||
('H', 8), | ||
('I', 7), | ||
('L', 2), | ||
('O', 6), | ||
('R', 3), | ||
('S', 4), | ||
('T', 9), | ||
] | ||
|
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This annotation differs slightly from the one in
.meta/Example.roc
: it uses(U8, U64)
instead of(U8, U8)
. I suppose you want to use the latter?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch, thanks! I will update this