Skip to content

Commit

Permalink
Add alphametics exercise (#379)
Browse files Browse the repository at this point in the history
  • Loading branch information
ErikSchierboom authored Mar 19, 2024
1 parent bb782b7 commit 0f9ef25
Show file tree
Hide file tree
Showing 7 changed files with 239 additions and 10 deletions.
50 changes: 40 additions & 10 deletions config.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,18 @@
"average_run_time": 1
},
"files": {
"solution": ["%{snake_slug}.pl"],
"test": ["%{snake_slug}_tests.plt"],
"example": [".meta/%{snake_slug}.example.pl"],
"exemplar": [".meta/%{snake_slug}.exemplar.pl"]
"solution": [
"%{snake_slug}.pl"
],
"test": [
"%{snake_slug}_tests.plt"
],
"example": [
".meta/%{snake_slug}.example.pl"
],
"exemplar": [
".meta/%{snake_slug}.exemplar.pl"
]
},
"exercises": {
"practice": [
Expand Down Expand Up @@ -65,7 +73,9 @@
"practices": [],
"prerequisites": [],
"difficulty": 1,
"topics": ["math"]
"topics": [
"math"
]
},
{
"slug": "rna-transcription",
Expand All @@ -90,7 +100,9 @@
"practices": [],
"prerequisites": [],
"difficulty": 1,
"topics": ["math"]
"topics": [
"math"
]
},
{
"slug": "dominoes",
Expand All @@ -107,7 +119,9 @@
"practices": [],
"prerequisites": [],
"difficulty": 1,
"topics": ["math"]
"topics": [
"math"
]
},
{
"slug": "grains",
Expand Down Expand Up @@ -148,7 +162,11 @@
"practices": [],
"prerequisites": [],
"difficulty": 1,
"topics": ["binary_trees", "recursion", "tree_traversals"]
"topics": [
"binary_trees",
"recursion",
"tree_traversals"
]
},
{
"slug": "space-age",
Expand All @@ -173,7 +191,9 @@
"practices": [],
"prerequisites": [],
"difficulty": 1,
"topics": ["math"]
"topics": [
"math"
]
},
{
"slug": "wordy",
Expand All @@ -182,7 +202,9 @@
"practices": [],
"prerequisites": [],
"difficulty": 1,
"topics": ["parsing"]
"topics": [
"parsing"
]
},
{
"slug": "zebra-puzzle",
Expand Down Expand Up @@ -663,6 +685,14 @@
"practices": [],
"prerequisites": [],
"difficulty": 1
},
{
"slug": "alphametics",
"name": "Alphametics",
"uuid": "9f430940-9e6f-4f80-9ae4-0ebcc91cf055",
"practices": [],
"prerequisites": [],
"difficulty": 1
}
],
"foregone": [
Expand Down
31 changes: 31 additions & 0 deletions exercises/practice/alphametics/.docs/instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Instructions

Write a function to solve alphametics puzzles.

[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.

Write a function to solve alphametics puzzles.

[alphametics]: https://en.wikipedia.org/wiki/Alphametics
55 changes: 55 additions & 0 deletions exercises/practice/alphametics/.meta/alphametics.example.pl
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
:- use_module(library(dcg/basics)).
:- use_module(library(clpfd)).
:- set_prolog_flag(double_quotes, chars).

letter(Letter) --> [Letter], { char_type(Letter, upper) }.
operand([Letter|Letters]) --> letter(Letter), operand(Letters).
operand([Letter]) --> letter(Letter).

operands([Operand|Operands]) --> operand(Operand), " + ", operands(Operands).
operands([Operand]) --> operand(Operand).

equation(Left, Right) --> operands(Left), " == ", operands(Right).

letter_map(Sign, Letters, Mapping) :-
reverse(Letters, ReversedLetters),
findall(L-C, (nth0(I, ReversedLetters, L), C is Sign * 10 ^ I), Mapping).

map_merge(Mappings, Mapping) :-
setof(Key, Value^member(Key-Value, Mappings), Keys),
findall(Key-Sum, (member(Key, Keys), aggregate(sum(Value), member(Key-Value, Mappings), Sum)), Mapping).

mapping(Left, Right, Mapping) :-
maplist(letter_map(1), Left, LeftMappings),
maplist(letter_map(-1), Right, RightMappings),
append(LeftMappings, RightMappings, NestedMappings),
flatten(NestedMappings, Mappings),
map_merge(Mappings, Mapping).

zero_letters(Left, Right, ZeroLetters) :-
append(Left, Right, LeftAndRight),
findall(ZeroChar, member([ZeroChar|_], LeftAndRight), ALlZeroLetters),
sort(ALlZeroLetters, ZeroLetters).

parse(Equation, Mapping, Letters, ZeroLetters) :-
string_chars(Equation, Chars),
phrase(equation(Left, Right), Chars),
mapping(Left, Right, Mapping),
zero_letters(Left, Right, ZeroLetters),
pairs_keys(Mapping, Letters).

add_range(ZeroLetters, Letter, Digit) :-
(member(Letter, ZeroLetters) -> Min = 1; Min = 0),
Digit in Min..9.

solve(Equation, Solution) :-
parse(Equation, Mapping, Letters, ZeroLetters),
length(Letters, Count),
length(Digits, Count),
all_different(Digits),
maplist(add_range(ZeroLetters), Letters, Digits),
pairs_values(Mapping, Multipliers),
scalar_product(Multipliers, Digits, #=, 0),
pairs_keys_values(Solution, Letters, Digits),
label(Digits),
!.
17 changes: 17 additions & 0 deletions exercises/practice/alphametics/.meta/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"authors": [
"erikschierboom"
],
"files": {
"solution": [
"alphametics.pl"
],
"test": [
"alphametics_tests.plt"
],
"example": [
".meta/alphametics.example.pl"
]
},
"blurb": "Write a function to solve alphametics puzzles."
}
40 changes: 40 additions & 0 deletions exercises/practice/alphametics/.meta/tests.toml
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"
1 change: 1 addition & 0 deletions exercises/practice/alphametics/alphametics.pl
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
solve(Equation, Solution).
55 changes: 55 additions & 0 deletions exercises/practice/alphametics/alphametics_tests.plt
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
pending :-
current_prolog_flag(argv, ['--all'|_]).
pending :-
write('\nA TEST_IS PENDING!\n'),
fail.

:- begin_tests(alphametics).

test(puzzle_with_three_letters, condition(true)) :-
solve("I + BB == ILL", Solution),
sort(Solution, SortedSolution),
SortedSolution == ['B'-9, 'I'-1, 'L'-0].

test(solution_must_have_unique_value_for_each_letter, [fail, condition(pending)]) :-
solve("A == B", _).

test(leading_zero_solution_is_invalid, [fail, condition(pending)]) :-
solve("ACA + DD == BD", _).

test(puzzle_with_two_digits_final_carry, condition(pending)) :-
solve("A + A + A + A + A + A + A + A + A + A + A + B == BCC", Solution),
sort(Solution, SortedSolution),
SortedSolution == ['A'-9, 'B'-1, 'C'-0].

test(puzzle_with_four_letters, condition(pending)) :-
solve("AS + A == MOM", Solution),
sort(Solution, SortedSolution),
SortedSolution == ['A'-9, 'M'-1, 'O'-0, 'S'-2].

test(puzzle_with_six_letters, condition(pending)) :-
solve("NO + NO + TOO == LATE", Solution),
sort(Solution, SortedSolution),
SortedSolution == ['A'-0, 'E'-2, 'L'-1, 'N'-7, 'O'-4, 'T'-9].

test(puzzle_with_seven_letters, condition(pending)) :-
solve("HE + SEES + THE == LIGHT", Solution),
sort(Solution, SortedSolution),
SortedSolution == ['E'-4, 'G'-2, 'H'-5, 'I'-0, 'L'-1, 'S'-9, 'T'-7].

test(puzzle_with_eight_letters, condition(pending)) :-
solve("SEND + MORE == MONEY", Solution),
sort(Solution, SortedSolution),
SortedSolution == ['D'-7, 'E'-5, 'M'-1, 'N'-6, 'O'-0, 'R'-8, 'S'-9, 'Y'-2].

test(puzzle_with_ten_letters, condition(pending)) :-
solve("AND + A + STRONG + OFFENSE + AS + A + GOOD == DEFENSE", Solution),
sort(Solution, SortedSolution),
SortedSolution == ['A'-5, 'D'-3, 'E'-4, 'F'-7, 'G'-8, 'N'-0, 'O'-2, 'R'-1, 'S'-6, 'T'-9].

test(puzzle_with_ten_letters_and_199_addends, condition(pending)) :-
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", Solution),
sort(Solution, SortedSolution),
SortedSolution == ['A'-1, 'E'-0, 'F'-5, 'H'-8, 'I'-7, 'L'-2, 'O'-6, 'R'-3, 'S'-4, 'T'-9].

:- end_tests(alphametics).

0 comments on commit 0f9ef25

Please sign in to comment.