Skip to content

Commit e72a35b

Browse files
authored
Add Killer Sudoku Helper Exercise (#648)
1 parent d39da9b commit e72a35b

File tree

7 files changed

+373
-0
lines changed

7 files changed

+373
-0
lines changed

config.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1220,6 +1220,14 @@
12201220
"prerequisites": [],
12211221
"difficulty": 4
12221222
},
1223+
{
1224+
"slug": "killer-sudoku-helper",
1225+
"name": "Killer Sudoku Helper",
1226+
"uuid": "1ccf740f-9c81-4742-8e00-1ecc5c52d7a3",
1227+
"practices": [],
1228+
"prerequisites": [],
1229+
"difficulty": 5
1230+
},
12231231
{
12241232
"slug": "circular-buffer",
12251233
"name": "Circular Buffer",
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
# Instructions
2+
3+
A friend of yours is learning how to solve Killer Sudokus (rules below) but struggling to figure out which digits can go in a cage.
4+
They ask you to help them out by writing a small program that lists all valid combinations for a given cage, and any constraints that affect the cage.
5+
6+
To make the output of your program easy to read, the combinations it returns must be sorted.
7+
8+
## Killer Sudoku Rules
9+
10+
- [Standard Sudoku rules][sudoku-rules] apply.
11+
- The digits in a cage, usually marked by a dotted line, add up to the small number given in the corner of the cage.
12+
- A digit may only occur once in a cage.
13+
14+
For a more detailed explanation, check out [this guide][killer-guide].
15+
16+
## Example 1: Cage with only 1 possible combination
17+
18+
In a 3-digit cage with a sum of 7, there is only one valid combination: 124.
19+
20+
- 1 + 2 + 4 = 7
21+
- Any other combination that adds up to 7, e.g. 232, would violate the rule of not repeating digits within a cage.
22+
23+
![Sudoku grid, with three killer cages that are marked as grouped together. The first killer cage is in the 3×3 box in the top left corner of the grid. The middle column of that box forms the cage, with the followings cells from top to bottom: first cell contains a 1 and a pencil mark of 7, indicating a cage sum of 7, second cell contains a 2, third cell contains a 5. The numbers are highlighted in red to indicate a mistake. The second killer cage is in the central 3×3 box of the grid. The middle column of that box forms the cage, with the followings cells from top to bottom: first cell contains a 1 and a pencil mark of 7, indicating a cage sum of 7, second cell contains a 2, third cell contains a 4. None of the numbers in this cage are highlighted and therefore don't contain any mistakes. The third killer cage follows the outside corner of the central 3×3 box of the grid. It is made up of the following three cells: the top left cell of the cage contains a 2, highlighted in red, and a cage sum of 7. The top right cell of the cage contains a 3. The bottom right cell of the cage contains a 2, highlighted in red. All other cells are empty.][one-solution-img]
24+
25+
## Example 2: Cage with several combinations
26+
27+
In a 2-digit cage with a sum 10, there are 4 possible combinations:
28+
29+
- 19
30+
- 28
31+
- 37
32+
- 46
33+
34+
![Sudoku grid, all squares empty except for the middle column, column 5, which has 8 rows filled. Each continguous two rows form a killer cage and are marked as grouped together. From top to bottom: first group is a cell with value 1 and a pencil mark indicating a cage sum of 10, cell with value 9. Second group is a cell with value 2 and a pencil mark of 10, cell with value 8. Third group is a cell with value 3 and a pencil mark of 10, cell with value 7. Fourth group is a cell with value 4 and a pencil mark of 10, cell with value 6. The last cell in the column is empty.][four-solutions-img]
35+
36+
## Example 3: Cage with several combinations that is restricted
37+
38+
In a 2-digit cage with a sum 10, where the column already contains a 1 and a 4, there are 2 possible combinations:
39+
40+
- 28
41+
- 37
42+
43+
19 and 46 are not possible due to the 1 and 4 in the column according to standard Sudoku rules.
44+
45+
![Sudoku grid, all squares empty except for the middle column, column 5, which has 8 rows filled. The first row contains a 4, the second is empty, and the third contains a 1. The 1 is highlighted in red to indicate a mistake. The last 6 rows in the column form killer cages of two cells each. From top to bottom: first group is a cell with value 2 and a pencil mark indicating a cage sum of 10, cell with value 8. Second group is a cell with value 3 and a pencil mark of 10, cell with value 7. Third group is a cell with value 1, highlighted in red, and a pencil mark of 10, cell with value 9.][not-possible-img]
46+
47+
## Trying it yourself
48+
49+
If you want to give an approachable Killer Sudoku a go, you can try out [this puzzle][clover-puzzle] by Clover, featured by [Mark Goodliffe on Cracking The Cryptic on the 21st of June 2021][goodliffe-video].
50+
51+
You can also find Killer Sudokus in varying difficulty in numerous newspapers, as well as Sudoku apps, books and websites.
52+
53+
## Credit
54+
55+
The screenshots above have been generated using [F-Puzzles.com](https://www.f-puzzles.com/), a Puzzle Setting Tool by Eric Fox.
56+
57+
[sudoku-rules]: https://masteringsudoku.com/sudoku-rules-beginners/
58+
[killer-guide]: https://masteringsudoku.com/killer-sudoku/
59+
[one-solution-img]: https://assets.exercism.org/images/exercises/killer-sudoku-helper/example1.png
60+
[four-solutions-img]: https://assets.exercism.org/images/exercises/killer-sudoku-helper/example2.png
61+
[not-possible-img]: https://assets.exercism.org/images/exercises/killer-sudoku-helper/example3.png
62+
[clover-puzzle]: https://app.crackingthecryptic.com/sudoku/HqTBn3Pr6R
63+
[goodliffe-video]: https://youtu.be/c_NjEbFEeW0?t=1180
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"authors": [
3+
"tomasnorre"
4+
],
5+
"files": {
6+
"solution": [
7+
"KillerSudokuHelper.php"
8+
],
9+
"test": [
10+
"KillerSudokuHelperTest.php"
11+
],
12+
"example": [
13+
".meta/example.php"
14+
]
15+
},
16+
"blurb": "Write a tool that makes it easier to solve Killer Sudokus",
17+
"source": "Created by Sascha Mann, Jeremy Walker, and BethanyG for the Julia track on Exercism.",
18+
"source_url": "https://github.com/exercism/julia/pull/413"
19+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
class KillerSudokuHelper
6+
{
7+
public function combinations(int $sum, int $size, array $exclude): array
8+
{
9+
$candidates = array_filter(range(1, min($sum, 9)), static function ($i) use ($exclude) {
10+
return !in_array($i, $exclude, true);
11+
});
12+
13+
$result = [];
14+
foreach ($this->combinationsHelper([], $candidates, $sum, $size) as $combo) {
15+
$result[] = $combo;
16+
}
17+
18+
return $result;
19+
}
20+
21+
private function combinationsHelper(array $prefix, array $candidates, int $target, int $size): array
22+
{
23+
if ($size === 0) {
24+
return $target === 0 ? [$prefix] : [];
25+
}
26+
27+
$result = [];
28+
foreach ($candidates as $index => $candidate) {
29+
if ($candidate > $target) {
30+
break;
31+
}
32+
$remaining = array_slice($candidates, $index + 1);
33+
foreach ($this->combinationsHelper(array_merge($prefix, [$candidate]), $remaining, $target - $candidate, $size - 1) as $combo) {
34+
$result[] = $combo;
35+
}
36+
}
37+
38+
return $result;
39+
}
40+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
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+
[2aaa8f13-11b5-4054-b95c-a906e4d79fb6]
13+
description = "Trivial 1-digit cages -> 1"
14+
15+
[4645da19-9fdd-4087-a910-a6ed66823563]
16+
description = "Trivial 1-digit cages -> 2"
17+
18+
[07cfc704-f8aa-41b2-8f9a-cbefb674cb48]
19+
description = "Trivial 1-digit cages -> 3"
20+
21+
[22b8b2ba-c4fd-40b3-b1bf-40aa5e7b5f24]
22+
description = "Trivial 1-digit cages -> 4"
23+
24+
[b75d16e2-ff9b-464d-8578-71f73094cea7]
25+
description = "Trivial 1-digit cages -> 5"
26+
27+
[bcbf5afc-4c89-4ff6-9357-07ab4d42788f]
28+
description = "Trivial 1-digit cages -> 6"
29+
30+
[511b3bf8-186f-4e35-844f-c804d86f4a7a]
31+
description = "Trivial 1-digit cages -> 7"
32+
33+
[bd09a60d-3aca-43bd-b6aa-6ccad01bedda]
34+
description = "Trivial 1-digit cages -> 8"
35+
36+
[9b539f27-44ea-4ff8-bd3d-c7e136bee677]
37+
description = "Trivial 1-digit cages -> 9"
38+
39+
[0a8b2078-b3a4-4dbd-be0d-b180f503d5c3]
40+
description = "Cage with sum 45 contains all digits 1:9"
41+
42+
[2635d7c9-c716-4da1-84f1-c96e03900142]
43+
description = "Cage with only 1 possible combination"
44+
45+
[a5bde743-e3a2-4a0c-8aac-e64fceea4228]
46+
description = "Cage with several combinations"
47+
48+
[dfbf411c-737d-465a-a873-ca556360c274]
49+
description = "Cage with several combinations that is restricted"
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?php
2+
3+
/*
4+
* By adding type hints and enabling strict type checking, code can become
5+
* easier to read, self-documenting and reduce the number of potential bugs.
6+
* By default, type declarations are non-strict, which means they will attempt
7+
* to change the original type to match the type specified by the
8+
* type-declaration.
9+
*
10+
* In other words, if you pass a string to a function requiring a float,
11+
* it will attempt to convert the string value to a float.
12+
*
13+
* To enable strict mode, a single declare directive must be placed at the top
14+
* of the file.
15+
* This means that the strictness of typing is configured on a per-file basis.
16+
* This directive not only affects the type declarations of parameters, but also
17+
* a function's return type.
18+
*
19+
* For more info review the Concept on strict type checking in the PHP track
20+
* <link>.
21+
*
22+
* To disable strict typing, comment out the directive below.
23+
*/
24+
25+
declare(strict_types=1);
26+
27+
class KillerSudokuHelper
28+
{
29+
public function combinations(int $sum, int $size, array $exclude): array
30+
{
31+
throw new \BadMethodCallException(sprintf('Implement the %s method', __FUNCTION__));
32+
}
33+
}
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
class KillerSudokuHelperTest extends PHPUnit\Framework\TestCase
6+
{
7+
private KillerSudokuHelper $killerSudokuHelper;
8+
9+
public static function setUpBeforeClass(): void
10+
{
11+
require_once 'KillerSudokuHelper.php';
12+
}
13+
14+
protected function setUp(): void
15+
{
16+
$this->killerSudokuHelper = new KillerSudokuHelper();
17+
}
18+
19+
/**
20+
* uuid: 2aaa8f13-11b5-4054-b95c-a906e4d79fb6
21+
*/
22+
public function testTrivialOneDigitCages1(): void
23+
{
24+
$expected = [
25+
[1]
26+
];
27+
$this->assertEquals($expected, $this->killerSudokuHelper->combinations(1, 1, []));
28+
}
29+
30+
/**
31+
* uuid: 4645da19-9fdd-4087-a910-a6ed66823563
32+
*/
33+
public function testTrivialOneDigitCages2(): void
34+
{
35+
$expected = [
36+
[2]
37+
];
38+
$this->assertEquals($expected, $this->killerSudokuHelper->combinations(2, 1, []));
39+
}
40+
41+
/**
42+
* uuid: 07cfc704-f8aa-41b2-8f9a-cbefb674cb48
43+
*/
44+
public function testTrivialOneDigitCages3(): void
45+
{
46+
$expected = [
47+
[3]
48+
];
49+
$this->assertEquals($expected, $this->killerSudokuHelper->combinations(3, 1, []));
50+
}
51+
52+
/**
53+
* uuid: 22b8b2ba-c4fd-40b3-b1bf-40aa5e7b5f24
54+
*/
55+
public function testTrivialOneDigitCages4(): void
56+
{
57+
$expected = [
58+
[4]
59+
];
60+
$this->assertEquals($expected, $this->killerSudokuHelper->combinations(4, 1, []));
61+
}
62+
63+
/**
64+
* uuid: b75d16e2-ff9b-464d-8578-71f73094cea7
65+
*/
66+
public function testTrivialOneDigitCages5(): void
67+
{
68+
$expected = [
69+
[5]
70+
];
71+
$this->assertEquals($expected, $this->killerSudokuHelper->combinations(5, 1, []));
72+
}
73+
74+
/**
75+
* uuid: bcbf5afc-4c89-4ff6-9357-07ab4d42788f
76+
*/
77+
public function testTrivialOneDigitCages6(): void
78+
{
79+
$expected = [
80+
[6]
81+
];
82+
$this->assertEquals($expected, $this->killerSudokuHelper->combinations(6, 1, []));
83+
}
84+
85+
/**
86+
* uuid: 511b3bf8-186f-4e35-844f-c804d86f4a7a
87+
*/
88+
public function testTrivialOneDigitCages7(): void
89+
{
90+
$expected = [
91+
[7]
92+
];
93+
$this->assertEquals($expected, $this->killerSudokuHelper->combinations(7, 1, []));
94+
}
95+
96+
/**
97+
* uuid: bd09a60d-3aca-43bd-b6aa-6ccad01bedda
98+
*/
99+
public function testTrivialOneDigitCages8(): void
100+
{
101+
$expected = [
102+
[8]
103+
];
104+
$this->assertEquals($expected, $this->killerSudokuHelper->combinations(8, 1, []));
105+
}
106+
107+
/**
108+
* uuid: 9b539f27-44ea-4ff8-bd3d-c7e136bee677
109+
*/
110+
public function testTrivialOneDigitCages9(): void
111+
{
112+
$expected = [
113+
[9]
114+
];
115+
$this->assertEquals($expected, $this->killerSudokuHelper->combinations(9, 1, []));
116+
}
117+
118+
/**
119+
* uuid: 0a8b2078-b3a4-4dbd-be0d-b180f503d5c3
120+
*/
121+
public function testCageWithSum45ContainsAllDigits1To9(): void
122+
{
123+
$expected = [
124+
[1, 2, 3, 4, 5, 6, 7, 8, 9]
125+
];
126+
$this->assertEquals($expected, $this->killerSudokuHelper->combinations(45, 9, []));
127+
}
128+
129+
/**
130+
* uuid: 2635d7c9-c716-4da1-84f1-c96e03900142
131+
*/
132+
public function testCageWithOnlyOnePossibleCombination(): void
133+
{
134+
$expected = [
135+
[1, 2, 4]
136+
];
137+
$this->assertEquals($expected, $this->killerSudokuHelper->combinations(7, 3, []));
138+
}
139+
140+
/**
141+
* uuid: a5bde743-e3a2-4a0c-8aac-e64fceea4228
142+
*/
143+
public function testCageWithSeveralCombinations(): void
144+
{
145+
$expected = [
146+
[1, 9], [2, 8], [3, 7], [4, 6]
147+
];
148+
$this->assertEquals($expected, $this->killerSudokuHelper->combinations(10, 2, []));
149+
}
150+
151+
/**
152+
* uuid: dfbf411c-737d-465a-a873-ca556360c274
153+
*/
154+
public function testCageWithSeveralCombinationsThatIsRestricted(): void
155+
{
156+
$expected = [
157+
[2, 8], [3, 7]
158+
];
159+
$this->assertEquals($expected, $this->killerSudokuHelper->combinations(10, 2, [1, 4]));
160+
}
161+
}

0 commit comments

Comments
 (0)