Skip to content

Commit b0cf6e9

Browse files
authored
New practice exercise killer-sudoku-helper (#1116)
1 parent 693ae6d commit b0cf6e9

File tree

11 files changed

+314
-0
lines changed

11 files changed

+314
-0
lines changed

config.json

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1806,6 +1806,22 @@
18061806
],
18071807
"difficulty": 4
18081808
},
1809+
{
1810+
"slug": "killer-sudoku-helper",
1811+
"name": "Killer Sudoku Helper",
1812+
"uuid": "ac7f32a8-920b-4132-b904-76131b344f12",
1813+
"prerequisites": [
1814+
"integers",
1815+
"lists",
1816+
"ranges",
1817+
"enum",
1818+
"maps",
1819+
"pattern-matching",
1820+
"recursion"
1821+
],
1822+
"practices": [],
1823+
"difficulty": 4
1824+
},
18091825
{
18101826
"slug": "largest-series-product",
18111827
"name": "Largest Series Product",
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
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](https://masteringsudoku.com/sudoku-rules-beginners/) 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](https://masteringsudoku.com/killer-sudoku/).
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.](https://media.githubusercontent.com/media/exercism/v3-files/main/julia/killer-sudoku-helper/example1.png)
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.](https://media.githubusercontent.com/media/exercism/v3-files/main/julia/killer-sudoku-helper/example2.png)
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.](https://media.githubusercontent.com/media/exercism/v3-files/main/julia/killer-sudoku-helper/example3.png)
46+
47+
## Trying it yourself
48+
49+
If you want to give an approachable Killer Sudoku a go, you can try out [this puzzle](https://app.crackingthecryptic.com/sudoku/HqTBn3Pr6R) by Clover, featured by [Mark Goodliffe on Cracking The Cryptic on the 21st of June 2021](https://youtu.be/c_NjEbFEeW0?t=1180).
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.
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# Used by "mix format"
2+
[
3+
inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"]
4+
]
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# The directory Mix will write compiled artifacts to.
2+
/_build/
3+
4+
# If you run "mix test --cover", coverage assets end up here.
5+
/cover/
6+
7+
# The directory Mix downloads your dependencies sources to.
8+
/deps/
9+
10+
# Where third-party dependencies like ExDoc output generated docs.
11+
/doc/
12+
13+
# Ignore .fetch files in case you like to edit your project deps locally.
14+
/.fetch
15+
16+
# If the VM crashes, it generates a dump, let's ignore it too.
17+
erl_crash.dump
18+
19+
# Also ignore archive artifacts (built via "mix archive.build").
20+
*.ez
21+
22+
# Ignore package tarball (built via "mix hex.build").
23+
killer_sudoku_helper-*.tar
24+
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"authors": [
3+
"jiegillet"
4+
],
5+
"files": {
6+
"solution": [
7+
"lib/killer_sudoku_helper.ex"
8+
],
9+
"test": [
10+
"test/killer_sudoku_helper_test.exs"
11+
],
12+
"example": [
13+
".meta/example.ex"
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: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
defmodule KillerSudokuHelper do
2+
@doc """
3+
Return the possible combinations of `size` distinct numbers from 1-9 excluding `exclude` that sum up to `sum`.
4+
"""
5+
@spec combinations(cage :: %{exclude: [integer], size: integer, sum: integer}) :: [[integer]]
6+
def combinations(%{exclude: exclude, size: size, sum: sum}) do
7+
numbers = [9, 8, 7, 6, 5, 4, 3, 2, 1] -- exclude
8+
9+
do_combinations(numbers, size, [[]])
10+
|> Enum.filter(&(Enum.sum(&1) == sum))
11+
end
12+
13+
defp do_combinations(_numbers, 0, list), do: list
14+
15+
defp do_combinations(numbers, size, list) do
16+
numbers
17+
|> tails()
18+
|> Enum.map(fn [head | tail] ->
19+
do_combinations(tail, size - 1, Enum.map(list, &[head | &1]))
20+
end)
21+
|> Enum.concat()
22+
end
23+
24+
defp tails([]), do: []
25+
defp tails([_ | tail] = list), do: [list | tails(tail)]
26+
end
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: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
defmodule KillerSudokuHelper do
2+
@doc """
3+
Return the possible combinations of `size` distinct numbers from 1-9 excluding `exclude` that sum up to `sum`.
4+
"""
5+
@spec combinations(cage :: %{exclude: [integer], size: integer, sum: integer}) :: [[integer]]
6+
def combinations(cage) do
7+
end
8+
end
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
defmodule KillerSudokuHelper.MixProject do
2+
use Mix.Project
3+
4+
def project do
5+
[
6+
app: :killer_sudoku_helper,
7+
version: "0.1.0",
8+
# elixir: "~> 1.8",
9+
start_permanent: Mix.env() == :prod,
10+
deps: deps()
11+
]
12+
end
13+
14+
# Run "mix help compile.app" to learn about applications.
15+
def application do
16+
[
17+
extra_applications: [:logger]
18+
]
19+
end
20+
21+
# Run "mix help deps" to learn about dependencies.
22+
defp deps do
23+
[
24+
# {:dep_from_hexpm, "~> 0.3.0"},
25+
# {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"}
26+
]
27+
end
28+
end
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
defmodule KillerSudokuHelperTest do
2+
use ExUnit.Case
3+
4+
describe "Trivial 1-digit cages" do
5+
# @tag :pending
6+
test "1" do
7+
cage = %{exclude: [], size: 1, sum: 1}
8+
assert KillerSudokuHelper.combinations(cage) == [[1]]
9+
end
10+
11+
@tag :pending
12+
test "2" do
13+
cage = %{exclude: [], size: 1, sum: 2}
14+
assert KillerSudokuHelper.combinations(cage) == [[2]]
15+
end
16+
17+
@tag :pending
18+
test "3" do
19+
cage = %{exclude: [], size: 1, sum: 3}
20+
assert KillerSudokuHelper.combinations(cage) == [[3]]
21+
end
22+
23+
@tag :pending
24+
test "4" do
25+
cage = %{exclude: [], size: 1, sum: 4}
26+
assert KillerSudokuHelper.combinations(cage) == [[4]]
27+
end
28+
29+
@tag :pending
30+
test "5" do
31+
cage = %{exclude: [], size: 1, sum: 5}
32+
assert KillerSudokuHelper.combinations(cage) == [[5]]
33+
end
34+
35+
@tag :pending
36+
test "6" do
37+
cage = %{exclude: [], size: 1, sum: 6}
38+
assert KillerSudokuHelper.combinations(cage) == [[6]]
39+
end
40+
41+
@tag :pending
42+
test "7" do
43+
cage = %{exclude: [], size: 1, sum: 7}
44+
assert KillerSudokuHelper.combinations(cage) == [[7]]
45+
end
46+
47+
@tag :pending
48+
test "8" do
49+
cage = %{exclude: [], size: 1, sum: 8}
50+
assert KillerSudokuHelper.combinations(cage) == [[8]]
51+
end
52+
53+
@tag :pending
54+
test "9" do
55+
cage = %{exclude: [], size: 1, sum: 9}
56+
assert KillerSudokuHelper.combinations(cage) == [[9]]
57+
end
58+
end
59+
60+
@tag :pending
61+
test "Cage with sum 45 contains all digits 1:9" do
62+
cage = %{exclude: [], size: 9, sum: 45}
63+
assert KillerSudokuHelper.combinations(cage) == [[1, 2, 3, 4, 5, 6, 7, 8, 9]]
64+
end
65+
66+
@tag :pending
67+
test "Cage with only 1 possible combination" do
68+
cage = %{exclude: [], size: 3, sum: 7}
69+
assert KillerSudokuHelper.combinations(cage) == [[1, 2, 4]]
70+
end
71+
72+
@tag :pending
73+
test "Cage with several combinations" do
74+
cage = %{exclude: [], size: 2, sum: 10}
75+
assert KillerSudokuHelper.combinations(cage) == [[1, 9], [2, 8], [3, 7], [4, 6]]
76+
end
77+
78+
@tag :pending
79+
test "Cage with several combinations that is restricted" do
80+
cage = %{exclude: [1, 4], size: 2, sum: 10}
81+
assert KillerSudokuHelper.combinations(cage) == [[2, 8], [3, 7]]
82+
end
83+
end

0 commit comments

Comments
 (0)