-
-
Notifications
You must be signed in to change notification settings - Fork 137
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add alphametics exercise and update TwelveDays stub. (#547)
- Loading branch information
1 parent
4510137
commit 60336f6
Showing
7 changed files
with
345 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
{ | ||
"blurb": "Write a function to solve alphametics puzzles.", | ||
"authors": ["MichaelBunker"], | ||
"contributors": [], | ||
"files": { | ||
"solution": ["Alphametics.php"], | ||
"test": ["AlphameticsTest.php"], | ||
"example": [".meta/example.php"] | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
<?php | ||
|
||
/* | ||
* By adding type hints and enabling strict type checking, code can become | ||
* easier to read, self-documenting and reduce the number of potential bugs. | ||
* By default, type declarations are non-strict, which means they will attempt | ||
* to change the original type to match the type specified by the | ||
* type-declaration. | ||
* | ||
* In other words, if you pass a string to a function requiring a float, | ||
* it will attempt to convert the string value to a float. | ||
* | ||
* To enable strict mode, a single declare directive must be placed at the top | ||
* of the file. | ||
* This means that the strictness of typing is configured on a per-file basis. | ||
* This directive not only affects the type declarations of parameters, but also | ||
* a function's return type. | ||
* | ||
* For more info review the Concept on strict type checking in the PHP track | ||
* <link>. | ||
* | ||
* To disable strict typing, comment out the directive below. | ||
*/ | ||
|
||
declare(strict_types=1); | ||
|
||
class Alphametics | ||
{ | ||
public function solve(string $puzzle): ?array | ||
{ | ||
//Remove operators and filter down to just the letters | ||
$parts = preg_split('/[+|==]/', $puzzle); | ||
$parts = array_map('trim', $parts); | ||
$parts = array_filter($parts); | ||
|
||
$firstLetters = []; | ||
|
||
foreach ($parts as $part) { | ||
$firstLetters[] = substr($part, 0, 1); | ||
} | ||
$firstLetters = array_unique($firstLetters); | ||
|
||
$sum = array_pop($parts); | ||
$counts = $this->getLetterCounts($parts, $sum); | ||
|
||
return $this->runPermutations($counts, $firstLetters); | ||
} | ||
|
||
//Run through each permutation of values. | ||
private function runPermutations(array $letterCounts, array $firstLetters, array $numbers = []): ?array | ||
{ | ||
$letters = array_keys($letterCounts); | ||
|
||
//If the permutation has a value for each letter, test the permutation to see if it is a solution | ||
if (count($letters) === count($numbers)) { | ||
return $this->testPermutation($letterCounts, $numbers); | ||
} | ||
$possibleValues = [0,1,2,3,4,5,6,7,8,9]; | ||
|
||
foreach ($possibleValues as $value) { | ||
//Setup possible values without duplicates and not using 0 as first letter value | ||
if (in_array($value, $numbers) || ($value === 0 && in_array($letters[count($numbers)], $firstLetters))) { | ||
continue; | ||
} | ||
|
||
//Add the number to the possible permutation and run function again. | ||
//Ex. with 3 unique letters | ||
// 1st run: $numbers = [] | ||
// 2nd run: $numbers = [1] | ||
// 3rd run: $numbers = [1,2] | ||
// 4th run: $numbers = [1,2,3] -> testPermutation | ||
$result = $this->runPermutations($letterCounts, $firstLetters, [...$numbers, $value]); | ||
|
||
if ($result) { | ||
return $result; | ||
} | ||
} | ||
|
||
return null; | ||
} | ||
|
||
//Test the permutation when each letter has a potential value | ||
private function testPermutation(array $letterCounts, array $numbers): ?array | ||
{ | ||
$letters = array_keys($letterCounts); | ||
$counts = array_values($letterCounts); | ||
|
||
$i = 0; | ||
|
||
$isSolved = array_reduce($counts, function ($sum, $count) use ($numbers, &$i) { | ||
$return = $sum + $count * $numbers[$i]; | ||
$i++; | ||
return $return; | ||
}, 0) == 0; | ||
|
||
if (!$isSolved) { | ||
return null; | ||
} | ||
|
||
$result = []; | ||
|
||
foreach ($letters as $key => $letter) { | ||
$result[$letter] = $numbers[$key]; | ||
} | ||
|
||
return $result; | ||
} | ||
|
||
private function getLetterCounts(array $addends, string $sum): array | ||
{ | ||
$counts = []; | ||
|
||
foreach ($addends as $addend) { | ||
$addendParts = str_split($addend); | ||
|
||
foreach ($addendParts as $i => $letter) { | ||
$counts[$letter] = ($counts[$letter] ?? 0) + 10 ** (count($addendParts) - 1 - $i); | ||
} | ||
} | ||
|
||
$sumParts = str_split($sum); | ||
foreach ($sumParts as $i => $letter) { | ||
$counts[$letter] = ($counts[$letter] ?? 0) - 10 ** (count($sumParts) - 1 - $i); | ||
} | ||
|
||
return $counts; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
<?php | ||
|
||
/* | ||
* By adding type hints and enabling strict type checking, code can become | ||
* easier to read, self-documenting and reduce the number of potential bugs. | ||
* By default, type declarations are non-strict, which means they will attempt | ||
* to change the original type to match the type specified by the | ||
* type-declaration. | ||
* | ||
* In other words, if you pass a string to a function requiring a float, | ||
* it will attempt to convert the string value to a float. | ||
* | ||
* To enable strict mode, a single declare directive must be placed at the top | ||
* of the file. | ||
* This means that the strictness of typing is configured on a per-file basis. | ||
* This directive not only affects the type declarations of parameters, but also | ||
* a function's return type. | ||
* | ||
* For more info review the Concept on strict type checking in the PHP track | ||
* <link>. | ||
* | ||
* To disable strict typing, comment out the directive below. | ||
*/ | ||
|
||
declare(strict_types=1); | ||
|
||
class Alphametics | ||
{ | ||
public function solve(string $puzzle): ?array | ||
{ | ||
throw new \BadMethodCallException(sprintf('Implement the %s method', __FUNCTION__)); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,134 @@ | ||
<?php | ||
|
||
/* | ||
* By adding type hints and enabling strict type checking, code can become | ||
* easier to read, self-documenting and reduce the number of potential bugs. | ||
* By default, type declarations are non-strict, which means they will attempt | ||
* to change the original type to match the type specified by the | ||
* type-declaration. | ||
* | ||
* In other words, if you pass a string to a function requiring a float, | ||
* it will attempt to convert the string value to a float. | ||
* | ||
* To enable strict mode, a single declare directive must be placed at the top | ||
* of the file. | ||
* This means that the strictness of typing is configured on a per-file basis. | ||
* This directive not only affects the type declarations of parameters, but also | ||
* a function's return type. | ||
* | ||
* For more info review the Concept on strict type checking in the PHP track | ||
* <link>. | ||
* | ||
* To disable strict typing, comment out the directive below. | ||
*/ | ||
|
||
declare(strict_types=1); | ||
|
||
class AlphameticsTest extends PHPUnit\Framework\TestCase | ||
{ | ||
private Alphametics $alphametics; | ||
|
||
public static function setUpBeforeClass(): void | ||
{ | ||
require_once 'Alphametics.php'; | ||
} | ||
|
||
public function setUp(): void | ||
{ | ||
$this->alphametics = new Alphametics(); | ||
} | ||
|
||
public function testSolveThreeLetterPuzzle(): void | ||
{ | ||
$this->assertEquals(['I' => 1, 'B' => 9, 'L' => 0], $this->alphametics->solve('I + BB == ILL')); | ||
} | ||
|
||
public function testSolutionsMustHaveUniqueValuesForLetters(): void | ||
{ | ||
$this->assertEquals(null, $this->alphametics->solve('A == B')); | ||
} | ||
|
||
public function testLeadingZerosAreInvalid(): void | ||
{ | ||
$this->assertEquals(null, $this->alphametics->solve('ACA + DD == BD')); | ||
} | ||
|
||
public function testPuzzleWithTwoDigitsFinalCarry(): void | ||
{ | ||
$result = $this->alphametics->solve('A + A + A + A + A + A + A + A + A + A + A + B == BCC'); | ||
$this->assertEquals(['A' => 9, 'B' => 1, 'C' => 0], $result); | ||
} | ||
|
||
public function testPuzzleWithFourLetters(): void | ||
{ | ||
$result = $this->alphametics->solve('AS + A == MOM'); | ||
$this->assertEquals(['A' => 9, 'S' => 2, 'M' => 1, 'O' => 0], $result); | ||
} | ||
|
||
public function testPuzzleWithSixLetters(): void | ||
{ | ||
$result = $this->alphametics->solve('NO + NO + TOO == LATE'); | ||
$this->assertEquals(['N' => 7, 'O' => 4, 'T' => 9, 'L' => 1, 'A' => 0, 'E' => 2], $result); | ||
} | ||
|
||
public function testPuzzleWithSevenLetter(): void | ||
{ | ||
$result = $this->alphametics->solve('HE + SEES + THE == LIGHT'); | ||
$this->assertEquals(['E' => 4, 'G' => 2, 'H' => 5, 'I' => 0, 'L' => 1, 'S' => 9, 'T' => 7], $result); | ||
} | ||
|
||
public function testPuzzleWithEightLetters(): void | ||
{ | ||
$result = $this->alphametics->solve('SEND + MORE == MONEY'); | ||
$this->assertEquals(['S' => 9, 'E' => 5, 'N' => 6, 'D' => 7, 'M' => 1, 'O' => 0, 'R' => 8, 'Y' => 2], $result); | ||
} | ||
|
||
public function testPuzzleWithTenLetters(): void | ||
{ | ||
$result = $this->alphametics->solve('AND + A + STRONG + OFFENSE + AS + A + GOOD == DEFENSE'); | ||
$this->assertEquals([ | ||
'A' => 5, | ||
'D' => 3, | ||
'E' => 4, | ||
'F' => 7, | ||
'G' => 8, | ||
'N' => 0, | ||
'O' => 2, | ||
'R' => 1, | ||
'S' => 6, | ||
'T' => 9 | ||
], $result); | ||
} | ||
|
||
public function testPuzzleWithTenLettersAnd199Addends(): void | ||
{ | ||
$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 = $this->alphametics->solve($puzzle); | ||
$this->assertEquals([ | ||
'A' => 1, | ||
'E' => 0, | ||
'F' => 5, | ||
'H' => 8, | ||
'I' => 7, | ||
'L' => 2, | ||
'O' => 6, | ||
'R' => 3, | ||
'S' => 4, | ||
'T' => 9 | ||
], $result); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters