Skip to content

Commit 23f5461

Browse files
authored
[New Exercise]: Alphametics (#428)
Add exercise `alphametics`
1 parent f1f6afa commit 23f5461

File tree

7 files changed

+349
-0
lines changed

7 files changed

+349
-0
lines changed

config.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1249,6 +1249,14 @@
12491249
"prerequisites": [],
12501250
"difficulty": 9
12511251
},
1252+
{
1253+
"slug": "alphametics",
1254+
"name": "Alphametics",
1255+
"uuid": "b0fe9c19-f846-4e3d-b36b-6504f1740902",
1256+
"practices": [],
1257+
"prerequisites": [],
1258+
"difficulty": 9
1259+
},
12521260
{
12531261
"slug": "pov",
12541262
"name": "POV",
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# Instructions
2+
3+
Write a function to solve alphametics puzzles.
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+
Write a function to solve alphametics puzzles.
30+
31+
[alphametics]: https://en.wikipedia.org/wiki/Alphametics
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
using namespace System.Collections.Generic
2+
3+
function Get-NewState([int]$col, [int]$carry, [int[]]$assignments, [int]$used) {
4+
[PSCustomObject]@{
5+
ColIdx = $col
6+
Carry = $carry
7+
Assignment = $assignments
8+
Used = $used
9+
}
10+
}
11+
12+
function Invoke-Alphametics {
13+
<#
14+
.SYNOPSIS
15+
Implement a solver for alphametic puzzles.
16+
17+
.PARAMETER Puzzle
18+
The string represent the puzzle.
19+
20+
.NOTES
21+
This solution is not optimized enough for the biggest test.
22+
#>
23+
[CmdletBinding()]
24+
Param(
25+
[string] $Puzzle
26+
)
27+
#Parse the puzzle: addens and sum
28+
$Words = -split $Puzzle -match "\w"
29+
$addens, $sum = $Words[0..($Words.Count-2)], $Words[-1]
30+
$maxLen = $sum.Length
31+
32+
if ($addens | Where-Object { $_.Length -gt $maxLen }) { return $null }
33+
# Extract unique letters and index them
34+
$Letters = $Puzzle -split "" -match "[A-Z]" | Select-Object -Unique
35+
36+
# getting leading letter and they cant be zero
37+
$isLeading = [bool[]]::new($Letters.Count)
38+
foreach ($word in $Words) {
39+
$index = $Letters.IndexOf($word[0])
40+
$isLeading[$index] = $true
41+
}
42+
# build the grid or array of column
43+
$paddedWords = $Words | ForEach-Object {$_.PadLeft($maxLen, ' ')}
44+
$paddedaddens = $paddedWords | Select-Object -First ($Words.Count - 1)
45+
$paddedsum = $paddedWords | Select-Object -Last 1
46+
47+
$columns = @()
48+
for ($c = 0; $c -lt $maxLen; $c++) {
49+
$newColumn = [PSCustomObject]@{
50+
Addens = @()
51+
Result = -1
52+
}
53+
foreach ($word in $paddedaddens) {
54+
$letter = $word[$maxLen - 1 - $c]
55+
$newColumn.Addens += $letter -ne ' ' ? $Letters.IndexOf($letter) : @()
56+
}
57+
$newColumn.Result = $Letters.IndexOf($paddedsum[$maxLen - 1 - $c])
58+
$columns+= $newColumn
59+
}
60+
61+
$Letters | ForEach-Object {$allowed = [ordered]@{}} {$allowed[$_] = $isLeading[$Letters.IndexOf($_)] ? (1..9) : (0..9)}
62+
### SOLVING
63+
[int[]]$assignments = @(1..$Letters.Count).ForEach({-1})
64+
$usedBit = 0x400
65+
$stack = [Stack[PSCustomObject]]::new()
66+
$state = Get-NewState 0 0 $assignments $usedBit
67+
$stack.Push($state)
68+
69+
while ($stack.Count -gt 0) {
70+
$state = $stack.Pop()
71+
# Base case: past last column return result
72+
if ($state.ColIdx -ge $maxLen) {
73+
return 0..($state.Assignment.Count - 1) | ForEach-Object -Begin {$result = [ordered]@{}} -Process {
74+
$result[$Letters[$_]] = $state.Assignment[$_] } -End {$result}
75+
}
76+
$col = $columns[$state.ColIdx]
77+
# Find the first unassigned letter in this column
78+
$unassigned = $col.Addens | Where-Object { $state.Assignment[$_] -eq -1 }
79+
80+
if ($unassigned.Count -eq 0) {
81+
# All addens letters in this column are assigned → calculate sum
82+
$totalSum = ($col.Addens | ForEach-Object {$state.Assignment[$_]} | Measure-Object -Sum).Sum
83+
$resultDigit = ($totalSum + $state.Carry) % 10
84+
$newCarry = [math]::Floor(($totalSum + $state.Carry) / 10)
85+
86+
if ($state.Assignment[$col.Result] -eq -1) { #Sum digit not assigned yet. assign and move to new col
87+
if ( (($state.Used -band (1 -shl $resultDigit)) -ne 0) -or $resultDigit -notin $allowed[$col.Result]) { continue }
88+
$newState = Get-NewState ($state.ColIdx + 1) $newCarry $state.Assignment.Clone() ($state.Used -bor (1 -shl $resultDigit))
89+
$newState.Assignment[$col.Result] = $resultDigit
90+
$stack.Push($newState)
91+
} else { # Sum digit already assigned → check consistency
92+
if ($state.Assignment[$col.Result] -eq $resultDigit) {
93+
$newState = Get-NewState ($state.ColIdx + 1) $newCarry $state.Assignment.Clone() ($state.Used -bor (1 -shl $resultDigit))
94+
$stack.Push($newState)
95+
}
96+
}
97+
} else { #still unassigned letter(s)
98+
$firstUnassigned = $unassigned[0]
99+
$possibleValues = $allowed[$Letters[$firstUnassigned]] | Where-Object {($state.Used -band (1 -shl $_)) -eq 0 }
100+
foreach ($value in $possibleValues) {
101+
$newState = Get-NewState $state.ColIdx $state.Carry $state.Assignment.Clone() ($state.Used -bor (1 -shl $value))
102+
$newState.Assignment[$firstUnassigned] = $value
103+
$stack.Push($newState)
104+
}
105+
}
106+
}
107+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"authors": [
3+
"glaxxie"
4+
],
5+
"files": {
6+
"solution": [
7+
"Alphametics.ps1"
8+
],
9+
"test": [
10+
"Alphametics.tests.ps1"
11+
],
12+
"example": [
13+
".meta/Alphametics.example.ps1"
14+
]
15+
},
16+
"test_runner": false,
17+
"blurb": "Write a function to solve alphametics puzzles."
18+
}
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: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
function Invoke-Alphametics {
2+
<#
3+
.SYNOPSIS
4+
Implement a solver for alphametic puzzles.
5+
6+
.PARAMETER Puzzle
7+
The string represent the puzzle.
8+
#>
9+
[CmdletBinding()]
10+
Param(
11+
[string] $Puzzle
12+
)
13+
Throw "Please implement this function"
14+
}
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
BeforeAll {
2+
. "./Alphametics.ps1"
3+
}
4+
5+
Describe "Alphametics test cases" {
6+
It "puzzle with three letters" {
7+
$got = Invoke-Alphametics -Puzzle "I + BB == ILL"
8+
$want = @{
9+
"I" = 1
10+
"B" = 9
11+
"L" = 0
12+
}
13+
14+
$got.Keys | ForEach-Object {$got[$_] | Should -Be $want[$_]}
15+
}
16+
17+
It "solution must have unique value for each letter" {
18+
$got = Invoke-Alphametics -Puzzle "A == B"
19+
$got | Should -BeNullOrEmpty
20+
}
21+
22+
It "leading zero solution is invalid" {
23+
$got = Invoke-Alphametics -Puzzle "ACA + DD == BD"
24+
$got | Should -BeNullOrEmpty
25+
}
26+
27+
It "puzzle with two digits final carry" {
28+
$got = Invoke-Alphametics -Puzzle "A + A + A + A + A + A + A + A + A + A + A + B == BCC"
29+
$want = @{
30+
"A" = 9
31+
"B" = 1
32+
"C" = 0
33+
}
34+
35+
$got.Keys | ForEach-Object {$got[$_] | Should -Be $want[$_]}
36+
}
37+
38+
It "puzzle with four letters" {
39+
$got = Invoke-Alphametics -Puzzle "AS + A == MOM"
40+
$want = @{
41+
"A" = 9
42+
"S" = 2
43+
"M" = 1
44+
"O" = 0
45+
}
46+
47+
$got.Keys | ForEach-Object {$got[$_] | Should -Be $want[$_]}
48+
}
49+
50+
It "puzzle with six letters" {
51+
$got = Invoke-Alphametics -Puzzle "NO + NO + TOO == LATE"
52+
$want = @{
53+
"N" = 7
54+
"O" = 4
55+
"T" = 9
56+
"L" = 1
57+
"A" = 0
58+
"E" = 2
59+
}
60+
61+
$got.Keys | ForEach-Object {$got[$_] | Should -Be $want[$_]}
62+
}
63+
64+
It "puzzle with seven letters" {
65+
$got = Invoke-Alphametics -Puzzle "HE + SEES + THE == LIGHT"
66+
$want = @{
67+
"E" = 4
68+
"G" = 2
69+
"H" = 5
70+
"I" = 0
71+
"L" = 1
72+
"S" = 9
73+
"T" = 7
74+
}
75+
76+
$got.Keys | ForEach-Object {$got[$_] | Should -Be $want[$_]}
77+
}
78+
79+
It "puzzle with eight letters" {
80+
$got = Invoke-Alphametics -Puzzle "SEND + MORE == MONEY"
81+
$want = @{
82+
"S" = 9
83+
"E" = 5
84+
"N" = 6
85+
"D" = 7
86+
"M" = 1
87+
"O" = 0
88+
"R" = 8
89+
"Y" = 2
90+
}
91+
92+
$got.Keys | ForEach-Object {$got[$_] | Should -Be $want[$_]}
93+
}
94+
95+
It "puzzle with ten letters" {
96+
$got = Invoke-Alphametics -Puzzle "AND + A + STRONG + OFFENSE + AS + A + GOOD == DEFENSE"
97+
$want = @{
98+
"A" = 5
99+
"D" = 3
100+
"E" = 4
101+
"F" = 7
102+
"G" = 8
103+
"N" = 0
104+
"O" = 2
105+
"R" = 1
106+
"S" = 6
107+
"T" = 9
108+
}
109+
110+
$got.Keys | ForEach-Object {$got[$_] | Should -Be $want[$_]}
111+
}
112+
113+
# optional test, remove the Skip flag to run it
114+
It "puzzle with ten letters and 199 addends" -Skip {
115+
$got = Invoke-Alphametics -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"
116+
$want = @{
117+
"A" = 1
118+
"E" = 0
119+
"F" = 5
120+
"H" = 8
121+
"I" = 7
122+
"L" = 2
123+
"O" = 6
124+
"R" = 3
125+
"S" = 4
126+
"T" = 9
127+
}
128+
129+
$got.Keys | ForEach-Object {$got[$_] | Should -Be $want[$_]}
130+
}
131+
}

0 commit comments

Comments
 (0)