Skip to content

Commit bbce1ca

Browse files
rabestroJegors Cemisovs
andauthored
Killer sudoku helper (#862)
* Add detailed instructions and examples for the Killer Sudoku Helper exercise * Add example implementation for Killer Sudoku Helper exercise * Add configuration and tests for Killer Sudoku Helper exercise --------- Co-authored-by: Jegors Cemisovs <[email protected]>
1 parent 4a55e00 commit bbce1ca

File tree

8 files changed

+296
-0
lines changed

8 files changed

+296
-0
lines changed

config.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1285,6 +1285,14 @@
12851285
"practices": [],
12861286
"prerequisites": [],
12871287
"difficulty": 3
1288+
},
1289+
{
1290+
"slug": "killer-sudoku-helper",
1291+
"name": "Killer Sudoku Helper",
1292+
"uuid": "6c0c1b40-cca6-414b-a3f9-3f0c0ddb2d6d",
1293+
"practices": [],
1294+
"prerequisites": [],
1295+
"difficulty": 4
12881296
}
12891297
]
12901298
},
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
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.
24+
The first killer cage is in the 3×3 box in the top left corner of the grid.
25+
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.
26+
The numbers are highlighted in red to indicate a mistake.
27+
The second killer cage is in the central 3×3 box of the grid.
28+
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.
29+
None of the numbers in this cage are highlighted and therefore don't contain any mistakes.
30+
The third killer cage follows the outside corner of the central 3×3 box of the grid.
31+
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.
32+
The top right cell of the cage contains a 3.
33+
The bottom right cell of the cage contains a 2, highlighted in red. All other cells are empty.][one-solution-img]
34+
35+
## Example 2: Cage with several combinations
36+
37+
In a 2-digit cage with a sum 10, there are 4 possible combinations:
38+
39+
- 19
40+
- 28
41+
- 37
42+
- 46
43+
44+
![Sudoku grid, all squares empty except for the middle column, column 5, which has 8 rows filled.
45+
Each continguous two rows form a killer cage and are marked as grouped together.
46+
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.
47+
Second group is a cell with value 2 and a pencil mark of 10, cell with value 8.
48+
Third group is a cell with value 3 and a pencil mark of 10, cell with value 7.
49+
Fourth group is a cell with value 4 and a pencil mark of 10, cell with value 6.
50+
The last cell in the column is empty.][four-solutions-img]
51+
52+
## Example 3: Cage with several combinations that is restricted
53+
54+
In a 2-digit cage with a sum 10, where the column already contains a 1 and a 4, there are 2 possible combinations:
55+
56+
- 28
57+
- 37
58+
59+
19 and 46 are not possible due to the 1 and 4 in the column according to standard Sudoku rules.
60+
61+
![Sudoku grid, all squares empty except for the middle column, column 5, which has 8 rows filled.
62+
The first row contains a 4, the second is empty, and the third contains a 1.
63+
The 1 is highlighted in red to indicate a mistake.
64+
The last 6 rows in the column form killer cages of two cells each.
65+
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.
66+
Second group is a cell with value 3 and a pencil mark of 10, cell with value 7.
67+
Third group is a cell with value 1, highlighted in red, and a pencil mark of 10, cell with value 9.][not-possible-img]
68+
69+
## Trying it yourself
70+
71+
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].
72+
73+
You can also find Killer Sudokus in varying difficulty in numerous newspapers, as well as Sudoku apps, books and websites.
74+
75+
## Credit
76+
77+
The screenshots above have been generated using [F-Puzzles.com](https://www.f-puzzles.com/), a Puzzle Setting Tool by Eric Fox.
78+
79+
[sudoku-rules]: https://masteringsudoku.com/sudoku-rules-beginners/
80+
[killer-guide]: https://masteringsudoku.com/killer-sudoku/
81+
[one-solution-img]: https://assets.exercism.org/images/exercises/killer-sudoku-helper/example1.png
82+
[four-solutions-img]: https://assets.exercism.org/images/exercises/killer-sudoku-helper/example2.png
83+
[not-possible-img]: https://assets.exercism.org/images/exercises/killer-sudoku-helper/example3.png
84+
[clover-puzzle]: https://app.crackingthecryptic.com/sudoku/HqTBn3Pr6R
85+
[goodliffe-video]: https://youtu.be/c_NjEbFEeW0?t=1180
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import KillerSudokuHelper.MaxDigit
2+
3+
case class KillerSudokuHelper(exclude: Set[Int]):
4+
private def generateRecursive(currentStartDigit: Int, remainingSum: Int, remainingSize: Int): List[List[Int]] =
5+
if remainingSize == 1 then
6+
if currentStartDigit <= remainingSum && remainingSum <= MaxDigit && !exclude.contains(remainingSum) then
7+
List(List(remainingSum))
8+
else List()
9+
else
10+
val maxPossibleFirstDigit = MaxDigit - remainingSize + 1
11+
(currentStartDigit to maxPossibleFirstDigit).toList
12+
.filterNot(digit => exclude.contains(digit))
13+
.flatMap(digit => generateRecursive(digit + 1, remainingSum - digit, remainingSize - 1).map(s => digit :: s))
14+
15+
object KillerSudokuHelper:
16+
val MaxDigit = 9
17+
def combinations(sum: Int, size: Int, exclude: List[Int] = Nil): List[List[Int]] =
18+
KillerSudokuHelper(exclude.toSet).generateRecursive(1, sum, size)
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
{
2+
"authors": [
3+
"rabestro"
4+
],
5+
"files": {
6+
"solution": [
7+
"src/main/scala/KillerSudokuHelper.scala"
8+
],
9+
"test": [
10+
"src/test/scala/KillerSudokuHelperTest.scala"
11+
],
12+
"example": [
13+
".meta/Example.scala"
14+
],
15+
"invalidator": [
16+
"build.sbt"
17+
]
18+
},
19+
"blurb": "Write a tool that makes it easier to solve Killer Sudokus",
20+
"source": "Created by Sascha Mann, Jeremy Walker, and BethanyG for the Julia track on Exercism.",
21+
"source_url": "https://github.com/exercism/julia/pull/413"
22+
}
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: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
scalaVersion := "3.4.2"
2+
3+
libraryDependencies += "org.scalatest" %% "scalatest" % "3.2.19" % Test

exercises/practice/killer-sudoku-helper/src/main/scala/KillerSudokuHelper.scala

Whitespace-only changes.
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
// Generated by AI (Gemini)
2+
import org.scalatest.flatspec.AnyFlatSpec
3+
import org.scalatest.matchers.should.Matchers
4+
5+
class KillerSudokuHelperTest extends AnyFlatSpec with Matchers {
6+
7+
behavior of "KillerSudokuHelper.combinations"
8+
9+
it should "handle trivial 1-digit cage: sum 1, size 1, no exclusions" in {
10+
val sum = 1
11+
val size = 1
12+
val exclude = List()
13+
KillerSudokuHelper.combinations(sum, size, exclude) should be(List(List(1)))
14+
}
15+
16+
it should "handle trivial 1-digit cage: sum 2, size 1, no exclusions" in {
17+
pending
18+
val sum = 2
19+
val size = 1
20+
val exclude = List()
21+
KillerSudokuHelper.combinations(sum, size, exclude) should be(List(List(2)))
22+
}
23+
24+
it should "handle trivial 1-digit cage: sum 3, size 1, no exclusions" in {
25+
pending
26+
val sum = 3
27+
val size = 1
28+
val exclude = List()
29+
KillerSudokuHelper.combinations(sum, size, exclude) should be(List(List(3)))
30+
}
31+
32+
it should "handle trivial 1-digit cage: sum 4, size 1, no exclusions" in {
33+
pending
34+
val sum = 4
35+
val size = 1
36+
val exclude = List()
37+
KillerSudokuHelper.combinations(sum, size, exclude) should be(List(List(4)))
38+
}
39+
40+
it should "handle trivial 1-digit cage: sum 5, size 1, no exclusions" in {
41+
pending
42+
val sum = 5
43+
val size = 1
44+
val exclude = List()
45+
KillerSudokuHelper.combinations(sum, size, exclude) should be(List(List(5)))
46+
}
47+
48+
it should "handle trivial 1-digit cage: sum 6, size 1, no exclusions" in {
49+
pending
50+
val sum = 6
51+
val size = 1
52+
val exclude = List()
53+
KillerSudokuHelper.combinations(sum, size, exclude) should be(List(List(6)))
54+
}
55+
56+
it should "handle trivial 1-digit cage: sum 7, size 1, no exclusions" in {
57+
pending
58+
val sum = 7
59+
val size = 1
60+
val exclude = List()
61+
KillerSudokuHelper.combinations(sum, size, exclude) should be(List(List(7)))
62+
}
63+
64+
it should "handle trivial 1-digit cage: sum 8, size 1, no exclusions" in {
65+
pending
66+
val sum = 8
67+
val size = 1
68+
val exclude = List()
69+
KillerSudokuHelper.combinations(sum, size, exclude) should be(List(List(8)))
70+
}
71+
72+
it should "handle trivial 1-digit cage: sum 9, size 1, no exclusions" in {
73+
pending
74+
val sum = 9
75+
val size = 1
76+
val exclude = List()
77+
KillerSudokuHelper.combinations(sum, size, exclude) should be(List(List(9)))
78+
}
79+
80+
it should "Cage with sum 45 contains all digits 1:9" in {
81+
pending
82+
val sum = 45
83+
val size = 9
84+
val exclude = List()
85+
KillerSudokuHelper.combinations(sum, size, exclude) should be(List(List(1, 2, 3, 4, 5, 6, 7, 8, 9)))
86+
}
87+
88+
it should "Cage with only 1 possible combination" in {
89+
pending
90+
val sum = 7
91+
val size = 3
92+
val exclude = List()
93+
KillerSudokuHelper.combinations(sum, size, exclude) should be(List(List(1, 2, 4)))
94+
}
95+
96+
it should "Cage with several combinations" in {
97+
pending
98+
val sum = 10
99+
val size = 2
100+
val exclude = List()
101+
KillerSudokuHelper.combinations(sum, size, exclude) should be(List(List(1, 9), List(2, 8), List(3, 7), List(4, 6)))
102+
}
103+
104+
it should "Cage with several combinations that is restricted" in {
105+
pending
106+
val sum = 10
107+
val size = 2
108+
val exclude = List(1, 4)
109+
KillerSudokuHelper.combinations(sum, size, exclude) should be(List(List(2, 8), List(3, 7)))
110+
}
111+
}

0 commit comments

Comments
 (0)