diff --git a/config.json b/config.json index 58f525a..674d8f2 100644 --- a/config.json +++ b/config.json @@ -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", diff --git a/exercises/practice/alphametics/.docs/instructions.md b/exercises/practice/alphametics/.docs/instructions.md new file mode 100644 index 0000000..649576e --- /dev/null +++ b/exercises/practice/alphametics/.docs/instructions.md @@ -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 diff --git a/exercises/practice/alphametics/.meta/Alphametics.example.ps1 b/exercises/practice/alphametics/.meta/Alphametics.example.ps1 new file mode 100644 index 0000000..84df058 --- /dev/null +++ b/exercises/practice/alphametics/.meta/Alphametics.example.ps1 @@ -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) + } + } + } +} diff --git a/exercises/practice/alphametics/.meta/config.json b/exercises/practice/alphametics/.meta/config.json new file mode 100644 index 0000000..224793d --- /dev/null +++ b/exercises/practice/alphametics/.meta/config.json @@ -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." +} diff --git a/exercises/practice/alphametics/.meta/tests.toml b/exercises/practice/alphametics/.meta/tests.toml new file mode 100644 index 0000000..f599b3d --- /dev/null +++ b/exercises/practice/alphametics/.meta/tests.toml @@ -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" diff --git a/exercises/practice/alphametics/Alphametics.ps1 b/exercises/practice/alphametics/Alphametics.ps1 new file mode 100644 index 0000000..527fdd6 --- /dev/null +++ b/exercises/practice/alphametics/Alphametics.ps1 @@ -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" +} diff --git a/exercises/practice/alphametics/Alphametics.tests.ps1 b/exercises/practice/alphametics/Alphametics.tests.ps1 new file mode 100644 index 0000000..55ac2eb --- /dev/null +++ b/exercises/practice/alphametics/Alphametics.tests.ps1 @@ -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[$_]} + } +}