Skip to content

Commit 67714f2

Browse files
authored
Add alphametics (#154)
1 parent 61a50fc commit 67714f2

10 files changed

Lines changed: 364 additions & 3 deletions

File tree

CONTRIBUTING.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ It's a module that returns a table.
129129
130130
- [`dnd-character`][dnd-character] -- `assert.between value, min, max`
131131
- [`space-age`][space-age] -- `assert.approx_equal #{case.expected}, result`
132-
- [`word-count`][word-count] -- `assert.has.same_kv result, expected`
132+
- [`alphametics`][alphametics] -- `assert.has.same_kv result, expected`
133133
134134
#### Helper functions for formatting test cases
135135
@@ -246,9 +246,9 @@ Here, the value `4` was chosen to reflect the max depth of the expected value:
246246
[generate-spec-exported]: ./bin/generate-spec#L51
247247
[test-helpers]: ./lib/test_helpers.moon
248248
[space-age]: ./exercises/practice/space-age/.meta/spec_generator.moon
249-
[word-count]: ./exercises/practice/word-count/.meta/spec_generator.moon
249+
[alphametics]: ./exercises/practice/alphametics/.meta/spec_generator.moon
250250
[dnd-character]: ./exercises/practice/dnd-character/.meta/spec_generator.moon
251251
[gigasecond]: ./exercises/practice/gigasecond/.meta/spec_generator.moon
252252
[simple-linked-list]: ./exercises/practice/simple-linked-list/.meta/spec_generator.moon
253253
[custom-set]: ./exercises/practice/custom-set/.meta/spec_generator.moon#L42
254-
[robot-name]: ./exercises/practice/robot-name/robot_name_spec.moon#L59
254+
[robot-name]: ./exercises/practice/robot-name/robot_name_spec.moon#L59

config.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -882,6 +882,14 @@
882882
"prerequisites": [],
883883
"difficulty": 7
884884
},
885+
{
886+
"slug": "alphametics",
887+
"name": "Alphametics",
888+
"uuid": "11e3eb84-464f-49ce-8f4c-983c552cc748",
889+
"practices": [],
890+
"prerequisites": [],
891+
"difficulty": 8
892+
},
885893
{
886894
"slug": "forth",
887895
"name": "Forth",
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
return {
2+
default = {
3+
ROOT = { '.' }
4+
}
5+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# Instructions
2+
3+
Given an alphametics puzzle, find the correct solution.
4+
5+
[Alphametics][alphametics] is a puzzle where letters in words are replaced with numbers.
6+
7+
For example `SEND + MORE = MONEY`:
8+
9+
```text
10+
S E N D
11+
M O R E +
12+
-----------
13+
M O N E Y
14+
```
15+
16+
Replacing these with valid numbers gives:
17+
18+
```text
19+
9 5 6 7
20+
1 0 8 5 +
21+
-----------
22+
1 0 6 5 2
23+
```
24+
25+
This is correct because every letter is replaced by a different number and the words, translated into numbers, then make a valid sum.
26+
27+
Each letter must represent a different digit, and the leading digit of a multi-digit number must not be zero.
28+
29+
[alphametics]: https://en.wikipedia.org/wiki/Alphametics
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"authors": [
3+
"glennj"
4+
],
5+
"files": {
6+
"solution": [
7+
"alphametics.moon"
8+
],
9+
"test": [
10+
"alphametics_spec.moon"
11+
],
12+
"example": [
13+
".meta/example.moon"
14+
]
15+
},
16+
"blurb": "Given an alphametics puzzle, find the correct solution."
17+
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
-- Returns a _map_ where the keys are initial letters of words in the puzzle.
2+
-- These letters cannot be mapped to digit zero.
3+
getLeadingDigits = (str) ->
4+
{c, true for c in str\gmatch '%f[%u].'}
5+
6+
-- Returns an ordered list of the last letters of each word in the puzzle.
7+
getLastDigits = (str) ->
8+
[word\sub(-1) for word in str\gmatch '%u+']
9+
10+
-- Returns a list of the letters in the puzzle, prioritizing:
11+
-- a) last letters
12+
-- b) most frequently occurring letters
13+
extractUniqueLetters = (str, lastLetters) ->
14+
alreadySeen, rest = {}, {}
15+
for c in *lastLetters
16+
alreadySeen[c] = (alreadySeen[c] or 0) + 1
17+
for c in str\gmatch '%u'
18+
if not alreadySeen[c]
19+
rest[c] = (rest[c] or 0) + 1
20+
21+
orderedKeys = (freq) ->
22+
keys = [k for k, _ in pairs freq]
23+
table.sort keys, (a, b) -> freq[a] > freq[b]
24+
keys
25+
26+
all = {}
27+
all[#all + 1] = c for c in *orderedKeys(alreadySeen)
28+
all[#all + 1] = c for c in *orderedKeys(rest)
29+
all
30+
31+
-- Does the mapping solve the puzzle?
32+
isValid = (map, str) ->
33+
eqn = str\gsub '%a', (c) -> map[c]
34+
numbers = [tonumber num for num in eqn\gmatch '%d+']
35+
sum = 0
36+
sum += numbers[i] for i = 1, #numbers - 1
37+
sum == numbers[#numbers]
38+
39+
-- Do the numbers in the last column add up?
40+
-- * Return nil if not all the letters have been mapped
41+
-- * Otherwise return true or false
42+
isLastColumnValid = (map, lastLetters) ->
43+
return nil if not map[lastLetters[#lastLetters]]
44+
sum = 0
45+
for i = 1, #lastLetters - 1
46+
return nil if not map[lastLetters[i]]
47+
sum += map[lastLetters[i]]
48+
(sum % 10) == map[lastLetters[#lastLetters]]
49+
50+
-- Does a key-value table contain a given falue
51+
containsValue = (t, value) ->
52+
for key, val in pairs t
53+
if val == value
54+
return true
55+
false
56+
57+
-- --------------------------------------------------------------------------
58+
solveAlphametics = (equation) ->
59+
leadingLetters = getLeadingDigits equation
60+
lastLetters = getLastDigits equation
61+
variables = extractUniqueLetters equation, lastLetters
62+
63+
backtrack = (assignment, index) ->
64+
if index > #variables
65+
return if isValid(assignment, equation) then assignment else nil
66+
67+
currentVar = variables[index]
68+
start = if leadingLetters[currentVar] then 1 else 0
69+
70+
for digit = start, 9
71+
if not containsValue assignment, digit
72+
assignment[currentVar] = digit
73+
constraint = isLastColumnValid assignment, lastLetters
74+
if constraint == nil or constraint == true
75+
result = backtrack assignment, index + 1
76+
return result if result
77+
assignment[currentVar] = nil
78+
79+
nil -- no solution found
80+
81+
backtrack {}, 1
82+
83+
{
84+
solve: solveAlphametics
85+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import kv_table from require 'test_helpers'
2+
3+
{
4+
module_imports: {'solve'},
5+
6+
generate_test: (case, level) ->
7+
lines = if is_json_null case.expected
8+
{
9+
"puzzle = #{quote case.input.puzzle}",
10+
"assert.is.falsy solve puzzle"
11+
}
12+
else
13+
{
14+
"puzzle = #{quote case.input.puzzle}",
15+
"result = solve puzzle",
16+
"expected = #{kv_table case.expected, level}",
17+
"assert.is.same_kv result, expected"
18+
}
19+
table.concat [indent line, level for line in *lines], '\n'
20+
21+
test_helpers: [[
22+
-- ----------------------------------------------------------
23+
same_kv = (state, arguments) ->
24+
actual = arguments[1]
25+
return false if type(actual) != 'table'
26+
expected = arguments[2]
27+
size = (t) -> #[k for k, _ in pairs t]
28+
return false if size(expected) != size(actual)
29+
for k, v in pairs expected
30+
return false if actual[k] != v
31+
true
32+
33+
say = require 'say'
34+
say\set 'assertion.same_kv.positive', 'Actual result\n%s\ndoes not have the same keys and values as expected\n%s'
35+
say\set 'assertion.same_kv.negative', 'Actual result\n%s\nwas not supposed to be the same as expected\n%s'
36+
assert\register 'assertion', 'same_kv', same_kv, 'assertion.same_kv.positive', 'assertion.same_kv.negative'
37+
-- ----------------------------------------------------------
38+
]]
39+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# This is an auto-generated file.
2+
#
3+
# Regenerating this file via `configlet sync` will:
4+
# - Recreate every `description` key/value pair
5+
# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications
6+
# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion)
7+
# - Preserve any other key/value pair
8+
#
9+
# As user-added comments (using the # character) will be removed when this file
10+
# is regenerated, comments can be added via a `comment` key.
11+
12+
[e0c08b07-9028-4d5f-91e1-d178fead8e1a]
13+
description = "puzzle with three letters"
14+
15+
[a504ee41-cb92-4ec2-9f11-c37e95ab3f25]
16+
description = "solution must have unique value for each letter"
17+
18+
[4e3b81d2-be7b-4c5c-9a80-cd72bc6d465a]
19+
description = "leading zero solution is invalid"
20+
21+
[8a3e3168-d1ee-4df7-94c7-b9c54845ac3a]
22+
description = "puzzle with two digits final carry"
23+
24+
[a9630645-15bd-48b6-a61e-d85c4021cc09]
25+
description = "puzzle with four letters"
26+
27+
[3d905a86-5a52-4e4e-bf80-8951535791bd]
28+
description = "puzzle with six letters"
29+
30+
[4febca56-e7b7-4789-97b9-530d09ba95f0]
31+
description = "puzzle with seven letters"
32+
33+
[12125a75-7284-4f9a-a5fa-191471e0d44f]
34+
description = "puzzle with eight letters"
35+
36+
[fb05955f-38dc-477a-a0b6-5ef78969fffa]
37+
description = "puzzle with ten letters"
38+
39+
[9a101e81-9216-472b-b458-b513a7adacf7]
40+
description = "puzzle with ten letters and 199 addends"
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
solve: (puzzle) ->
3+
error 'Implement me'
4+
}
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
import solve from require 'alphametics'
2+
3+
describe 'alphametics', ->
4+
-- ----------------------------------------------------------
5+
same_kv = (state, arguments) ->
6+
actual = arguments[1]
7+
return false if type(actual) != 'table'
8+
expected = arguments[2]
9+
size = (t) -> #[k for k, _ in pairs t]
10+
return false if size(expected) != size(actual)
11+
for k, v in pairs expected
12+
return false if actual[k] != v
13+
true
14+
15+
say = require 'say'
16+
say\set 'assertion.same_kv.positive', 'Actual result\n%s\ndoes not have the same keys and values as expected\n%s'
17+
say\set 'assertion.same_kv.negative', 'Actual result\n%s\nwas not supposed to be the same as expected\n%s'
18+
assert\register 'assertion', 'same_kv', same_kv, 'assertion.same_kv.positive', 'assertion.same_kv.negative'
19+
-- ----------------------------------------------------------
20+
21+
it 'puzzle with three letters', ->
22+
puzzle = 'I + BB == ILL'
23+
result = solve puzzle
24+
expected = {
25+
I: 1,
26+
L: 0,
27+
B: 9,
28+
}
29+
assert.is.same_kv result, expected
30+
31+
pending 'solution must have unique value for each letter', ->
32+
puzzle = 'A == B'
33+
assert.is.falsy solve puzzle
34+
35+
pending 'leading zero solution is invalid', ->
36+
puzzle = 'ACA + DD == BD'
37+
assert.is.falsy solve puzzle
38+
39+
pending 'puzzle with two digits final carry', ->
40+
puzzle = 'A + A + A + A + A + A + A + A + A + A + A + B == BCC'
41+
result = solve puzzle
42+
expected = {
43+
A: 9,
44+
C: 0,
45+
B: 1,
46+
}
47+
assert.is.same_kv result, expected
48+
49+
pending 'puzzle with four letters', ->
50+
puzzle = 'AS + A == MOM'
51+
result = solve puzzle
52+
expected = {
53+
A: 9,
54+
O: 0,
55+
S: 2,
56+
M: 1,
57+
}
58+
assert.is.same_kv result, expected
59+
60+
pending 'puzzle with six letters', ->
61+
puzzle = 'NO + NO + TOO == LATE'
62+
result = solve puzzle
63+
expected = {
64+
E: 2,
65+
L: 1,
66+
A: 0,
67+
T: 9,
68+
O: 4,
69+
N: 7,
70+
}
71+
assert.is.same_kv result, expected
72+
73+
pending 'puzzle with seven letters', ->
74+
puzzle = 'HE + SEES + THE == LIGHT'
75+
result = solve puzzle
76+
expected = {
77+
E: 4,
78+
L: 1,
79+
S: 9,
80+
I: 0,
81+
H: 5,
82+
G: 2,
83+
T: 7,
84+
}
85+
assert.is.same_kv result, expected
86+
87+
pending 'puzzle with eight letters', ->
88+
puzzle = 'SEND + MORE == MONEY'
89+
result = solve puzzle
90+
expected = {
91+
E: 5,
92+
D: 7,
93+
S: 9,
94+
R: 8,
95+
Y: 2,
96+
M: 1,
97+
O: 0,
98+
N: 6,
99+
}
100+
assert.is.same_kv result, expected
101+
102+
pending 'puzzle with ten letters', ->
103+
puzzle = 'AND + A + STRONG + OFFENSE + AS + A + GOOD == DEFENSE'
104+
result = solve puzzle
105+
expected = {
106+
A: 5,
107+
O: 2,
108+
N: 0,
109+
E: 4,
110+
D: 3,
111+
S: 6,
112+
R: 1,
113+
T: 9,
114+
G: 8,
115+
F: 7,
116+
}
117+
assert.is.same_kv result, expected
118+
119+
pending 'puzzle with ten letters and 199 addends', ->
120+
puzzle = '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'
121+
result = solve puzzle
122+
expected = {
123+
L: 2,
124+
A: 1,
125+
O: 6,
126+
E: 0,
127+
T: 9,
128+
S: 4,
129+
R: 3,
130+
I: 7,
131+
H: 8,
132+
F: 5,
133+
}
134+
assert.is.same_kv result, expected

0 commit comments

Comments
 (0)