From cc6343e5a457fc62fc595cf5a2c0b51fb300b81b Mon Sep 17 00:00:00 2001 From: Sascha Mann Date: Thu, 14 Apr 2022 11:33:01 +0200 Subject: [PATCH] Add practice exercise: killer-sudoku-helper (#413) Co-authored-by: BethanyG Co-authored-by: Jeremy Walker --- config.json | 15 ++++ .../.docs/instructions.md | 55 ++++++++++++++ .../killer-sudoku-helper/.meta/config.json | 21 ++++++ .../killer-sudoku-helper/.meta/example.jl | 74 +++++++++++++++++++ .../practice/killer-sudoku-helper/runtests.jl | 25 +++++++ .../killer-sudoku-helper/sudoku-util.jl | 0 6 files changed, 190 insertions(+) create mode 100644 exercises/practice/killer-sudoku-helper/.docs/instructions.md create mode 100644 exercises/practice/killer-sudoku-helper/.meta/config.json create mode 100644 exercises/practice/killer-sudoku-helper/.meta/example.jl create mode 100644 exercises/practice/killer-sudoku-helper/runtests.jl create mode 100644 exercises/practice/killer-sudoku-helper/sudoku-util.jl diff --git a/config.json b/config.json index 959db347756c3..e2e758b0bb76b 100644 --- a/config.json +++ b/config.json @@ -294,6 +294,21 @@ "strings" ] }, + { + "slug": "killer-sudoku-helper", + "name": "Killer Sudoku Helper", + "uuid": "e0142403-af9e-406e-827a-1fd37187d1ea", + "practices": [ + "iterator-protocol", + "vectors", + "conditionals" + ], + "prerequisites": [ + "vectors", + "conditionals" + ], + "difficulty": 5 + }, { "slug": "leap", "name": "Leap", diff --git a/exercises/practice/killer-sudoku-helper/.docs/instructions.md b/exercises/practice/killer-sudoku-helper/.docs/instructions.md new file mode 100644 index 0000000000000..11d7702371472 --- /dev/null +++ b/exercises/practice/killer-sudoku-helper/.docs/instructions.md @@ -0,0 +1,55 @@ +# Instructions + +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. +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. + +To make the output of your program easy to read, the combinations it returns must be sorted. + +## Killer Sudoku Rules + +- [Standard Sudoku rules](https://masteringsudoku.com/sudoku-rules-beginners/) apply. +- The digits in a cage, usually marked by a dotted line, add up to the small number given in the corner of the cage. +- A digit may only occur once in a cage. + +For a more detailed explanation, check out [this guide](https://masteringsudoku.com/killer-sudoku/). + +## Example 1: Cage with only 1 possible combination + +In a 3-digit cage with a sum of 7, there is only one valid combination: 124. + +- 1 + 2 + 4 = 7 +- Any other combination that adds up to 7, e.g. 232, would violate the rule of not repeating digits within a cage. + +![https://media.githubusercontent.com/media/exercism/v3-files/main/julia/killer-sudoku-helper/example1.png](https://media.githubusercontent.com/media/exercism/v3-files/main/julia/killer-sudoku-helper/example1.png) + +## Example 2: Cage with several combinations + +In a 2-digit cage with a sum 10, there are 4 possible combinations: + +- 19 +- 28 +- 37 +- 46 + +![https://media.githubusercontent.com/media/exercism/v3-files/main/julia/killer-sudoku-helper/example2.png](https://media.githubusercontent.com/media/exercism/v3-files/main/julia/killer-sudoku-helper/example2.png) + +## Example 3: Cage with several combinations that is restricted + +In a 2-digit cage with a sum 10, where the column already contains a 1 and a 4, there are 2 possible combinations: + +- 28 +- 37 + +19 and 46 are not possible due to the 1 and 4 in the column according to standard Sudoku rules. + +![https://media.githubusercontent.com/media/exercism/v3-files/main/julia/killer-sudoku-helper/example3.png](https://media.githubusercontent.com/media/exercism/v3-files/main/julia/killer-sudoku-helper/example3.png) + +## Trying it yourself + +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). + +You can also find Killer Sudokus in varying difficulty in numerous newspapers, as well as Sudoku apps, books and websites. + +## Credit + +The screenshots above have been generated using [F-Puzzles.com](https://www.f-puzzles.com/), a Puzzle Setting Tool by Eric Fox. diff --git a/exercises/practice/killer-sudoku-helper/.meta/config.json b/exercises/practice/killer-sudoku-helper/.meta/config.json new file mode 100644 index 0000000000000..b58afc166811d --- /dev/null +++ b/exercises/practice/killer-sudoku-helper/.meta/config.json @@ -0,0 +1,21 @@ +{ + "blurb": "Write a program to help a friend solve Killer Sudokus.", + "authors": [ + "SaschaMann" + ], + "contributors": [ + "iHiD", + "BethanyG" + ], + "files": { + "solution": [ + "sudoku-util.jl" + ], + "test": [ + "runtests.jl" + ], + "example": [ + ".meta/example.jl" + ] + } +} diff --git a/exercises/practice/killer-sudoku-helper/.meta/example.jl b/exercises/practice/killer-sudoku-helper/.meta/example.jl new file mode 100644 index 0000000000000..7cf78fb6d1d23 --- /dev/null +++ b/exercises/practice/killer-sudoku-helper/.meta/example.jl @@ -0,0 +1,74 @@ +# The following definitions of +# - the Combinations struct, +# - Base.length(c::Combinations), +# - Base.eltype(::Type{Combinations}), and +# - combinations(a, t::Integer) +# are taken from Combinatorics.jl, a combinatorics library for Julia. +# +# https://github.com/JuliaMath/Combinatorics.jl/blob/v1.0.2/src/combinations.jl +# https://github.com/JuliaMath/Combinatorics.jl/blob/v1.0.2/LICENSE.md +# +# It is published under the following license: +# Copyright (c) 2013-2015: Alessandro Andrioni, Jiahao Chen and other contributors. +# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +# The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +struct Combinations + n::Int + t::Int +end + +function Base.iterate(c::Combinations, s = [min(c.t - 1, i) for i in 1:c.t]) + if c.t == 0 # special case to generate 1 result for t==0 + isempty(s) && return (s, [1]) + return + end + for i in c.t:-1:1 + s[i] += 1 + if s[i] > (c.n - (c.t - i)) + continue + end + for j in i+1:c.t + s[j] = s[j-1] + 1 + end + break + end + s[1] > c.n - c.t + 1 && return + (s, s) +end + +Base.length(c::Combinations) = binomial(c.n, c.t) + +Base.eltype(::Type{Combinations}) = Vector{Int} + +""" + combinations(a, n) + +Generate all combinations of `n` elements from an indexable object `a`. Because the number +of combinations can be very large, this function returns an iterator object. +Use `collect(combinations(a, n))` to get an array of all combinations. +""" +function combinations(a, t::Integer) + if t < 0 + # generate 0 combinations for negative argument + t = length(a) + 1 + end + reorder(c) = [a[ci] for ci in c] + (reorder(c) for c in Combinations(length(a), t)) +end + +""" + combinations_in_cage(cagesum, cagesize, exclude=[]) + +Return all valid combinations of digits in a Killer Sudoku cage with the given sum and size, restricted by the excluded numbers. +""" +function combinations_in_cage(cagesum, cagesize, exclude=[]) + all_numbers = collect(1:9) + res = Vector{Int}[] + for c in combinations(all_numbers, cagesize) + isempty(intersect(c, exclude)) || continue + sum(c) == cagesum && push!(res, c) + end + res +end diff --git a/exercises/practice/killer-sudoku-helper/runtests.jl b/exercises/practice/killer-sudoku-helper/runtests.jl new file mode 100644 index 0000000000000..11ccbe5381648 --- /dev/null +++ b/exercises/practice/killer-sudoku-helper/runtests.jl @@ -0,0 +1,25 @@ +using Test + +include("sudoku-util.jl") + +@testset "Trivial 1-digit cages" begin + for n in 1:9 + @test combinations_in_cage(n, 1) == [[n]] + end +end + +@testset "Cage with sum 45 contains all digits 1:9" begin + @test combinations_in_cage(45, 9) == [[1, 2, 3, 4, 5, 6, 7, 8, 9]] +end + +@testset "Cage with only 1 possible combination" begin + @test combinations_in_cage(7, 3) == [[1, 2, 4]] +end + +@testset "Cage with several combinations" begin + @test combinations_in_cage(10, 2) == [[1, 9], [2, 8], [3, 7], [4, 6]] +end + +@testset "Cage with several combinations that is restricted" begin + @test combinations_in_cage(10, 2, [1, 4]) == [[2, 8], [3, 7]] +end diff --git a/exercises/practice/killer-sudoku-helper/sudoku-util.jl b/exercises/practice/killer-sudoku-helper/sudoku-util.jl new file mode 100644 index 0000000000000..e69de29bb2d1d