Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ It's a module that returns a table.

- [`dnd-character`][dnd-character] -- `assert.between value, min, max`
- [`space-age`][space-age] -- `assert.approx_equal #{case.expected}, result`
- [`word-count`][word-count] -- `assert.has.same_kv result, expected`
- [`alphametics`][alphametics] -- `assert.has.same_kv result, expected`

#### Helper functions for formatting test cases

Expand Down Expand Up @@ -246,9 +246,9 @@ Here, the value `4` was chosen to reflect the max depth of the expected value:
[generate-spec-exported]: ./bin/generate-spec#L51
[test-helpers]: ./lib/test_helpers.moon
[space-age]: ./exercises/practice/space-age/.meta/spec_generator.moon
[word-count]: ./exercises/practice/word-count/.meta/spec_generator.moon
[alphametics]: ./exercises/practice/alphametics/.meta/spec_generator.moon
[dnd-character]: ./exercises/practice/dnd-character/.meta/spec_generator.moon
[gigasecond]: ./exercises/practice/gigasecond/.meta/spec_generator.moon
[simple-linked-list]: ./exercises/practice/simple-linked-list/.meta/spec_generator.moon
[custom-set]: ./exercises/practice/custom-set/.meta/spec_generator.moon#L42
[robot-name]: ./exercises/practice/robot-name/robot_name_spec.moon#L59
[robot-name]: ./exercises/practice/robot-name/robot_name_spec.moon#L59
8 changes: 8 additions & 0 deletions config.json
Original file line number Diff line number Diff line change
Expand Up @@ -882,6 +882,14 @@
"prerequisites": [],
"difficulty": 7
},
{
"slug": "alphametics",
"name": "Alphametics",
"uuid": "11e3eb84-464f-49ce-8f4c-983c552cc748",
"practices": [],
"prerequisites": [],
"difficulty": 8
},
{
"slug": "forth",
"name": "Forth",
Expand Down
5 changes: 5 additions & 0 deletions exercises/practice/alphametics/.busted
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
return {
default = {
ROOT = { '.' }
}
}
29 changes: 29 additions & 0 deletions exercises/practice/alphametics/.docs/instructions.md
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
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": [
"glennj"
],
"files": {
"solution": [
"alphametics.moon"
],
"test": [
"alphametics_spec.moon"
],
"example": [
".meta/example.moon"
]
},
"blurb": "Given an alphametics puzzle, find the correct solution."
}
85 changes: 85 additions & 0 deletions exercises/practice/alphametics/.meta/example.moon
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
-- Returns a _map_ where the keys are initial letters of words in the puzzle.
-- These letters cannot be mapped to digit zero.
getLeadingDigits = (str) ->
{c, true for c in str\gmatch '%f[%u].'}

-- Returns an ordered list of the last letters of each word in the puzzle.
getLastDigits = (str) ->
[word\sub(-1) for word in str\gmatch '%u+']

-- Returns a list of the letters in the puzzle, prioritizing:
-- a) last letters
-- b) most frequently occurring letters
extractUniqueLetters = (str, lastLetters) ->
alreadySeen, rest = {}, {}
for c in *lastLetters
alreadySeen[c] = (alreadySeen[c] or 0) + 1
for c in str\gmatch '%u'
if not alreadySeen[c]
rest[c] = (rest[c] or 0) + 1

orderedKeys = (freq) ->
keys = [k for k, _ in pairs freq]
table.sort keys, (a, b) -> freq[a] > freq[b]
keys

all = {}
all[#all + 1] = c for c in *orderedKeys(alreadySeen)
all[#all + 1] = c for c in *orderedKeys(rest)
all

-- Does the mapping solve the puzzle?
isValid = (map, str) ->
eqn = str\gsub '%a', (c) -> map[c]
numbers = [tonumber num for num in eqn\gmatch '%d+']
sum = 0
sum += numbers[i] for i = 1, #numbers - 1
sum == numbers[#numbers]

-- Do the numbers in the last column add up?
-- * Return nil if not all the letters have been mapped
-- * Otherwise return true or false
isLastColumnValid = (map, lastLetters) ->
return nil if not map[lastLetters[#lastLetters]]
sum = 0
for i = 1, #lastLetters - 1
return nil if not map[lastLetters[i]]
sum += map[lastLetters[i]]
(sum % 10) == map[lastLetters[#lastLetters]]

-- Does a key-value table contain a given falue
containsValue = (t, value) ->
for key, val in pairs t
if val == value
return true
false

-- --------------------------------------------------------------------------
solveAlphametics = (equation) ->
leadingLetters = getLeadingDigits equation
lastLetters = getLastDigits equation
variables = extractUniqueLetters equation, lastLetters

backtrack = (assignment, index) ->
if index > #variables
return if isValid(assignment, equation) then assignment else nil

currentVar = variables[index]
start = if leadingLetters[currentVar] then 1 else 0

for digit = start, 9
if not containsValue assignment, digit
assignment[currentVar] = digit
constraint = isLastColumnValid assignment, lastLetters
if constraint == nil or constraint == true
result = backtrack assignment, index + 1
return result if result
assignment[currentVar] = nil

nil -- no solution found

backtrack {}, 1

{
solve: solveAlphametics
}
39 changes: 39 additions & 0 deletions exercises/practice/alphametics/.meta/spec_generator.moon
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import kv_table from require 'test_helpers'

{
module_imports: {'solve'},

generate_test: (case, level) ->
lines = if is_json_null case.expected
{
"puzzle = #{quote case.input.puzzle}",
"assert.is.falsy solve puzzle"
}
else
{
"puzzle = #{quote case.input.puzzle}",
"result = solve puzzle",
"expected = #{kv_table case.expected, level}",
"assert.is.same_kv result, expected"
}
table.concat [indent line, level for line in *lines], '\n'

test_helpers: [[
-- ----------------------------------------------------------
same_kv = (state, arguments) ->
actual = arguments[1]
return false if type(actual) != 'table'
expected = arguments[2]
size = (t) -> #[k for k, _ in pairs t]
return false if size(expected) != size(actual)
for k, v in pairs expected
return false if actual[k] != v
true

say = require 'say'
say\set 'assertion.same_kv.positive', 'Actual result\n%s\ndoes not have the same keys and values as expected\n%s'
say\set 'assertion.same_kv.negative', 'Actual result\n%s\nwas not supposed to be the same as expected\n%s'
assert\register 'assertion', 'same_kv', same_kv, 'assertion.same_kv.positive', 'assertion.same_kv.negative'
-- ----------------------------------------------------------
]]
}
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"
4 changes: 4 additions & 0 deletions exercises/practice/alphametics/alphametics.moon
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
solve: (puzzle) ->
error 'Implement me'
}
134 changes: 134 additions & 0 deletions exercises/practice/alphametics/alphametics_spec.moon
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import solve from require 'alphametics'

describe 'alphametics', ->
-- ----------------------------------------------------------
same_kv = (state, arguments) ->
actual = arguments[1]
return false if type(actual) != 'table'
expected = arguments[2]
size = (t) -> #[k for k, _ in pairs t]
return false if size(expected) != size(actual)
for k, v in pairs expected
return false if actual[k] != v
true

say = require 'say'
say\set 'assertion.same_kv.positive', 'Actual result\n%s\ndoes not have the same keys and values as expected\n%s'
say\set 'assertion.same_kv.negative', 'Actual result\n%s\nwas not supposed to be the same as expected\n%s'
assert\register 'assertion', 'same_kv', same_kv, 'assertion.same_kv.positive', 'assertion.same_kv.negative'
-- ----------------------------------------------------------

it 'puzzle with three letters', ->
puzzle = 'I + BB == ILL'
result = solve puzzle
expected = {
I: 1,
L: 0,
B: 9,
}
assert.is.same_kv result, expected

pending 'solution must have unique value for each letter', ->
puzzle = 'A == B'
assert.is.falsy solve puzzle

pending 'leading zero solution is invalid', ->
puzzle = 'ACA + DD == BD'
assert.is.falsy solve puzzle

pending 'puzzle with two digits final carry', ->
puzzle = 'A + A + A + A + A + A + A + A + A + A + A + B == BCC'
result = solve puzzle
expected = {
A: 9,
C: 0,
B: 1,
}
assert.is.same_kv result, expected

pending 'puzzle with four letters', ->
puzzle = 'AS + A == MOM'
result = solve puzzle
expected = {
A: 9,
O: 0,
S: 2,
M: 1,
}
assert.is.same_kv result, expected

pending 'puzzle with six letters', ->
puzzle = 'NO + NO + TOO == LATE'
result = solve puzzle
expected = {
E: 2,
L: 1,
A: 0,
T: 9,
O: 4,
N: 7,
}
assert.is.same_kv result, expected

pending 'puzzle with seven letters', ->
puzzle = 'HE + SEES + THE == LIGHT'
result = solve puzzle
expected = {
E: 4,
L: 1,
S: 9,
I: 0,
H: 5,
G: 2,
T: 7,
}
assert.is.same_kv result, expected

pending 'puzzle with eight letters', ->
puzzle = 'SEND + MORE == MONEY'
result = solve puzzle
expected = {
E: 5,
D: 7,
S: 9,
R: 8,
Y: 2,
M: 1,
O: 0,
N: 6,
}
assert.is.same_kv result, expected

pending 'puzzle with ten letters', ->
puzzle = 'AND + A + STRONG + OFFENSE + AS + A + GOOD == DEFENSE'
result = solve puzzle
expected = {
A: 5,
O: 2,
N: 0,
E: 4,
D: 3,
S: 6,
R: 1,
T: 9,
G: 8,
F: 7,
}
assert.is.same_kv result, expected

pending 'puzzle with ten letters and 199 addends', ->
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'
result = solve puzzle
expected = {
L: 2,
A: 1,
O: 6,
E: 0,
T: 9,
S: 4,
R: 3,
I: 7,
H: 8,
F: 5,
}
assert.is.same_kv result, expected
Loading