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
8 changes: 8 additions & 0 deletions config.json
Original file line number Diff line number Diff line change
Expand Up @@ -1249,6 +1249,14 @@
"prerequisites": [],
"difficulty": 9
},
{
"slug": "alphametics",
"name": "Alphametics",
"uuid": "b0fe9c19-f846-4e3d-b36b-6504f1740902",
"practices": [],
"prerequisites": [],
"difficulty": 9
},
{
"slug": "pov",
"name": "POV",
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
107 changes: 107 additions & 0 deletions exercises/practice/alphametics/.meta/Alphametics.example.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
using namespace System.Collections.Generic

function Get-NewState([int]$col, [int]$carry, [int[]]$assignments, [int]$used) {
[PSCustomObject]@{
ColIdx = $col
Carry = $carry
Assignment = $assignments
Used = $used
}
}

function Invoke-Alphametics {
<#
.SYNOPSIS
Implement a solver for alphametic puzzles.

.PARAMETER Puzzle
The string represent the puzzle.

.NOTES
This solution is not optimized enough for the biggest test.
#>
[CmdletBinding()]
Param(
[string] $Puzzle
)
#Parse the puzzle: addens and sum
$Words = -split $Puzzle -match "\w"
$addens, $sum = $Words[0..($Words.Count-2)], $Words[-1]
$maxLen = $sum.Length

if ($addens | Where-Object { $_.Length -gt $maxLen }) { return $null }
# Extract unique letters and index them
$Letters = $Puzzle -split "" -match "[A-Z]" | Select-Object -Unique

# getting leading letter and they cant be zero
$isLeading = [bool[]]::new($Letters.Count)
foreach ($word in $Words) {
$index = $Letters.IndexOf($word[0])
$isLeading[$index] = $true
}
# build the grid or array of column
$paddedWords = $Words | ForEach-Object {$_.PadLeft($maxLen, ' ')}
$paddedaddens = $paddedWords | Select-Object -First ($Words.Count - 1)
$paddedsum = $paddedWords | Select-Object -Last 1

$columns = @()
for ($c = 0; $c -lt $maxLen; $c++) {
$newColumn = [PSCustomObject]@{
Addens = @()
Result = -1
}
foreach ($word in $paddedaddens) {
$letter = $word[$maxLen - 1 - $c]
$newColumn.Addens += $letter -ne ' ' ? $Letters.IndexOf($letter) : @()
}
$newColumn.Result = $Letters.IndexOf($paddedsum[$maxLen - 1 - $c])
$columns+= $newColumn
}

$Letters | ForEach-Object {$allowed = [ordered]@{}} {$allowed[$_] = $isLeading[$Letters.IndexOf($_)] ? (1..9) : (0..9)}
### SOLVING
[int[]]$assignments = @(1..$Letters.Count).ForEach({-1})
$usedBit = 0x400
$stack = [Stack[PSCustomObject]]::new()
$state = Get-NewState 0 0 $assignments $usedBit
$stack.Push($state)

while ($stack.Count -gt 0) {
$state = $stack.Pop()
# Base case: past last column return result
if ($state.ColIdx -ge $maxLen) {
return 0..($state.Assignment.Count - 1) | ForEach-Object -Begin {$result = [ordered]@{}} -Process {
$result[$Letters[$_]] = $state.Assignment[$_] } -End {$result}
}
$col = $columns[$state.ColIdx]
# Find the first unassigned letter in this column
$unassigned = $col.Addens | Where-Object { $state.Assignment[$_] -eq -1 }

if ($unassigned.Count -eq 0) {
# All addens letters in this column are assigned → calculate sum
$totalSum = ($col.Addens | ForEach-Object {$state.Assignment[$_]} | Measure-Object -Sum).Sum
$resultDigit = ($totalSum + $state.Carry) % 10
$newCarry = [math]::Floor(($totalSum + $state.Carry) / 10)

if ($state.Assignment[$col.Result] -eq -1) { #Sum digit not assigned yet. assign and move to new col
if ( (($state.Used -band (1 -shl $resultDigit)) -ne 0) -or $resultDigit -notin $allowed[$col.Result]) { continue }
$newState = Get-NewState ($state.ColIdx + 1) $newCarry $state.Assignment.Clone() ($state.Used -bor (1 -shl $resultDigit))
$newState.Assignment[$col.Result] = $resultDigit
$stack.Push($newState)
} else { # Sum digit already assigned → check consistency
if ($state.Assignment[$col.Result] -eq $resultDigit) {
$newState = Get-NewState ($state.ColIdx + 1) $newCarry $state.Assignment.Clone() ($state.Used -bor (1 -shl $resultDigit))
$stack.Push($newState)
}
}
} else { #still unassigned letter(s)
$firstUnassigned = $unassigned[0]
$possibleValues = $allowed[$Letters[$firstUnassigned]] | Where-Object {($state.Used -band (1 -shl $_)) -eq 0 }
foreach ($value in $possibleValues) {
$newState = Get-NewState $state.ColIdx $state.Carry $state.Assignment.Clone() ($state.Used -bor (1 -shl $value))
$newState.Assignment[$firstUnassigned] = $value
$stack.Push($newState)
}
}
}
}
18 changes: 18 additions & 0 deletions exercises/practice/alphametics/.meta/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"authors": [
"glaxxie"
],
"files": {
"solution": [
"Alphametics.ps1"
],
"test": [
"Alphametics.tests.ps1"
],
"example": [
".meta/Alphametics.example.ps1"
]
},
"test_runner": false,
"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"
14 changes: 14 additions & 0 deletions exercises/practice/alphametics/Alphametics.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
function Invoke-Alphametics {
<#
.SYNOPSIS
Implement a solver for alphametic puzzles.

.PARAMETER Puzzle
The string represent the puzzle.
#>
[CmdletBinding()]
Param(
[string] $Puzzle
)
Throw "Please implement this function"
}
131 changes: 131 additions & 0 deletions exercises/practice/alphametics/Alphametics.tests.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
BeforeAll {
. "./Alphametics.ps1"
}

Describe "Alphametics test cases" {
It "puzzle with three letters" {
$got = Invoke-Alphametics -Puzzle "I + BB == ILL"
$want = @{
"I" = 1
"B" = 9
"L" = 0
}

$got.Keys | ForEach-Object {$got[$_] | Should -Be $want[$_]}
}

It "solution must have unique value for each letter" {
$got = Invoke-Alphametics -Puzzle "A == B"
$got | Should -BeNullOrEmpty
}

It "leading zero solution is invalid" {
$got = Invoke-Alphametics -Puzzle "ACA + DD == BD"
$got | Should -BeNullOrEmpty
}

It "puzzle with two digits final carry" {
$got = Invoke-Alphametics -Puzzle "A + A + A + A + A + A + A + A + A + A + A + B == BCC"
$want = @{
"A" = 9
"B" = 1
"C" = 0
}

$got.Keys | ForEach-Object {$got[$_] | Should -Be $want[$_]}
}

It "puzzle with four letters" {
$got = Invoke-Alphametics -Puzzle "AS + A == MOM"
$want = @{
"A" = 9
"S" = 2
"M" = 1
"O" = 0
}

$got.Keys | ForEach-Object {$got[$_] | Should -Be $want[$_]}
}

It "puzzle with six letters" {
$got = Invoke-Alphametics -Puzzle "NO + NO + TOO == LATE"
$want = @{
"N" = 7
"O" = 4
"T" = 9
"L" = 1
"A" = 0
"E" = 2
}

$got.Keys | ForEach-Object {$got[$_] | Should -Be $want[$_]}
}

It "puzzle with seven letters" {
$got = Invoke-Alphametics -Puzzle "HE + SEES + THE == LIGHT"
$want = @{
"E" = 4
"G" = 2
"H" = 5
"I" = 0
"L" = 1
"S" = 9
"T" = 7
}

$got.Keys | ForEach-Object {$got[$_] | Should -Be $want[$_]}
}

It "puzzle with eight letters" {
$got = Invoke-Alphametics -Puzzle "SEND + MORE == MONEY"
$want = @{
"S" = 9
"E" = 5
"N" = 6
"D" = 7
"M" = 1
"O" = 0
"R" = 8
"Y" = 2
}

$got.Keys | ForEach-Object {$got[$_] | Should -Be $want[$_]}
}

It "puzzle with ten letters" {
$got = Invoke-Alphametics -Puzzle "AND + A + STRONG + OFFENSE + AS + A + GOOD == DEFENSE"
$want = @{
"A" = 5
"D" = 3
"E" = 4
"F" = 7
"G" = 8
"N" = 0
"O" = 2
"R" = 1
"S" = 6
"T" = 9
}

$got.Keys | ForEach-Object {$got[$_] | Should -Be $want[$_]}
}

# optional test, remove the Skip flag to run it
It "puzzle with ten letters and 199 addends" -Skip {
$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"
$want = @{
"A" = 1
"E" = 0
"F" = 5
"H" = 8
"I" = 7
"L" = 2
"O" = 6
"R" = 3
"S" = 4
"T" = 9
}

$got.Keys | ForEach-Object {$got[$_] | Should -Be $want[$_]}
}
}
Loading