From e32544752a8ec32ceff360759c6759bcaec2a5b6 Mon Sep 17 00:00:00 2001 From: Erik Schierboom Date: Mon, 21 Oct 2024 14:20:34 +0200 Subject: [PATCH] Add `killer-sudoku-helper` exercise --- config.json | 13 +++ exercises/Exercises.sln | 7 ++ .../.config/dotnet-tools.json | 12 +++ .../.docs/instructions.md | 85 +++++++++++++++++++ .../killer-sudoku-helper/.meta/Example.fs | 16 ++++ .../killer-sudoku-helper/.meta/config.json | 19 +++++ .../killer-sudoku-helper/.meta/tests.toml | 49 +++++++++++ .../KillerSudokuHelper.fs | 4 + .../KillerSudokuHelper.fsproj | 22 +++++ .../KillerSudokuHelperTests.fs | 67 +++++++++++++++ generators/Generators.fs | 3 + 11 files changed, 297 insertions(+) create mode 100644 exercises/practice/killer-sudoku-helper/.config/dotnet-tools.json create mode 100644 exercises/practice/killer-sudoku-helper/.docs/instructions.md create mode 100644 exercises/practice/killer-sudoku-helper/.meta/Example.fs create mode 100644 exercises/practice/killer-sudoku-helper/.meta/config.json create mode 100644 exercises/practice/killer-sudoku-helper/.meta/tests.toml create mode 100644 exercises/practice/killer-sudoku-helper/KillerSudokuHelper.fs create mode 100644 exercises/practice/killer-sudoku-helper/KillerSudokuHelper.fsproj create mode 100644 exercises/practice/killer-sudoku-helper/KillerSudokuHelperTests.fs diff --git a/config.json b/config.json index 9455b508e..d2a270f9e 100644 --- a/config.json +++ b/config.json @@ -2205,6 +2205,19 @@ "strings" ], "difficulty": 3 + }, + { + "slug": "killer-sudoku-helper", + "name": "Killer Sudoku Helper", + "uuid": "beb3f222-54b2-459c-9704-f2e6516542bb", + "practices": [ + "lists" + ], + "prerequisites": [ + "lists", + "recursion" + ], + "difficulty": 5 } ], "foregone": [ diff --git a/exercises/Exercises.sln b/exercises/Exercises.sln index 78358a3d0..b676be98e 100644 --- a/exercises/Exercises.sln +++ b/exercises/Exercises.sln @@ -305,6 +305,8 @@ Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Zipper", "practice\zipper\Z EndProject Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "ResistorColorTrio", "practice\resistor-color-trio\ResistorColorTrio.fsproj", "{1850FAE9-5ACB-41D0-91BB-AD17A1021248}" EndProject +Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "KillerSudokuHelper", "practice\killer-sudoku-helper\KillerSudokuHelper.fsproj", "{FCE9E627-CFF9-4EF3-84BE-D42B354825AA}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -898,6 +900,10 @@ Global {1850FAE9-5ACB-41D0-91BB-AD17A1021248}.Debug|Any CPU.Build.0 = Debug|Any CPU {1850FAE9-5ACB-41D0-91BB-AD17A1021248}.Release|Any CPU.ActiveCfg = Release|Any CPU {1850FAE9-5ACB-41D0-91BB-AD17A1021248}.Release|Any CPU.Build.0 = Release|Any CPU + {FCE9E627-CFF9-4EF3-84BE-D42B354825AA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FCE9E627-CFF9-4EF3-84BE-D42B354825AA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FCE9E627-CFF9-4EF3-84BE-D42B354825AA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FCE9E627-CFF9-4EF3-84BE-D42B354825AA}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {B404AA3C-A226-409A-A035-6C1DC66940DD} = {B7E719DB-FB8D-43B4-B529-55FCF6E3DC3F} @@ -1049,5 +1055,6 @@ Global {A6E25412-34F6-49ED-834B-8A551CF3F2D3} = {391BEEC4-91A8-43F3-AE94-D5CB9A8FA611} {32F8738C-2782-4881-95C0-C621DC0D7ED9} = {391BEEC4-91A8-43F3-AE94-D5CB9A8FA611} {1850FAE9-5ACB-41D0-91BB-AD17A1021248} = {391BEEC4-91A8-43F3-AE94-D5CB9A8FA611} + {FCE9E627-CFF9-4EF3-84BE-D42B354825AA} = {391BEEC4-91A8-43F3-AE94-D5CB9A8FA611} EndGlobalSection EndGlobal diff --git a/exercises/practice/killer-sudoku-helper/.config/dotnet-tools.json b/exercises/practice/killer-sudoku-helper/.config/dotnet-tools.json new file mode 100644 index 000000000..0f7926bad --- /dev/null +++ b/exercises/practice/killer-sudoku-helper/.config/dotnet-tools.json @@ -0,0 +1,12 @@ +{ + "version": 1, + "isRoot": true, + "tools": { + "fantomas-tool": { + "version": "4.7.9", + "commands": [ + "fantomas" + ] + } + } +} \ No newline at end of file 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 000000000..fdafdca8f --- /dev/null +++ b/exercises/practice/killer-sudoku-helper/.docs/instructions.md @@ -0,0 +1,85 @@ +# 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][sudoku-rules] 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][killer-guide]. + +## 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. + +![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] + +## Example 2: Cage with several combinations + +In a 2-digit cage with a sum 10, there are 4 possible combinations: + +- 19 +- 28 +- 37 +- 46 + +![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] + +## 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. + +![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] + +## Trying it yourself + +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]. + +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. + +[sudoku-rules]: https://masteringsudoku.com/sudoku-rules-beginners/ +[killer-guide]: https://masteringsudoku.com/killer-sudoku/ +[one-solution-img]: https://assets.exercism.org/images/exercises/killer-sudoku-helper/example1.png +[four-solutions-img]: https://assets.exercism.org/images/exercises/killer-sudoku-helper/example2.png +[not-possible-img]: https://assets.exercism.org/images/exercises/killer-sudoku-helper/example3.png +[clover-puzzle]: https://app.crackingthecryptic.com/sudoku/HqTBn3Pr6R +[goodliffe-video]: https://youtu.be/c_NjEbFEeW0?t=1180 diff --git a/exercises/practice/killer-sudoku-helper/.meta/Example.fs b/exercises/practice/killer-sudoku-helper/.meta/Example.fs new file mode 100644 index 000000000..b4caf1f3a --- /dev/null +++ b/exercises/practice/killer-sudoku-helper/.meta/Example.fs @@ -0,0 +1,16 @@ +module KillerSudokuHelper + +module List = + let rec combinations n l = + match n, l with + | 0, _ -> [ [] ] + | _, [] -> [] + | k, (x :: xs) -> + List.map ((@) [ x ]) (combinations (k - 1) xs) + @ combinations k xs + +let combinations sum size exclude = + [ 1..9 ] + |> List.except exclude + |> List.combinations size + |> List.filter (fun combination -> List.sum combination = sum) 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 000000000..a35696faa --- /dev/null +++ b/exercises/practice/killer-sudoku-helper/.meta/config.json @@ -0,0 +1,19 @@ +{ + "authors": [ + "erikschierboom" + ], + "files": { + "solution": [ + "KillerSudokuHelper.fs" + ], + "test": [ + "KillerSudokuHelperTests.fs" + ], + "example": [ + ".meta/Example.fs" + ] + }, + "blurb": "Write a tool that makes it easier to solve Killer Sudokus", + "source": "Created by Sascha Mann, Jeremy Walker, and BethanyG for the Julia track on Exercism.", + "source_url": "https://github.com/exercism/julia/pull/413" +} diff --git a/exercises/practice/killer-sudoku-helper/.meta/tests.toml b/exercises/practice/killer-sudoku-helper/.meta/tests.toml new file mode 100644 index 000000000..19c23e8a9 --- /dev/null +++ b/exercises/practice/killer-sudoku-helper/.meta/tests.toml @@ -0,0 +1,49 @@ +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[2aaa8f13-11b5-4054-b95c-a906e4d79fb6] +description = "Trivial 1-digit cages -> 1" + +[4645da19-9fdd-4087-a910-a6ed66823563] +description = "Trivial 1-digit cages -> 2" + +[07cfc704-f8aa-41b2-8f9a-cbefb674cb48] +description = "Trivial 1-digit cages -> 3" + +[22b8b2ba-c4fd-40b3-b1bf-40aa5e7b5f24] +description = "Trivial 1-digit cages -> 4" + +[b75d16e2-ff9b-464d-8578-71f73094cea7] +description = "Trivial 1-digit cages -> 5" + +[bcbf5afc-4c89-4ff6-9357-07ab4d42788f] +description = "Trivial 1-digit cages -> 6" + +[511b3bf8-186f-4e35-844f-c804d86f4a7a] +description = "Trivial 1-digit cages -> 7" + +[bd09a60d-3aca-43bd-b6aa-6ccad01bedda] +description = "Trivial 1-digit cages -> 8" + +[9b539f27-44ea-4ff8-bd3d-c7e136bee677] +description = "Trivial 1-digit cages -> 9" + +[0a8b2078-b3a4-4dbd-be0d-b180f503d5c3] +description = "Cage with sum 45 contains all digits 1:9" + +[2635d7c9-c716-4da1-84f1-c96e03900142] +description = "Cage with only 1 possible combination" + +[a5bde743-e3a2-4a0c-8aac-e64fceea4228] +description = "Cage with several combinations" + +[dfbf411c-737d-465a-a873-ca556360c274] +description = "Cage with several combinations that is restricted" diff --git a/exercises/practice/killer-sudoku-helper/KillerSudokuHelper.fs b/exercises/practice/killer-sudoku-helper/KillerSudokuHelper.fs new file mode 100644 index 000000000..da79e195f --- /dev/null +++ b/exercises/practice/killer-sudoku-helper/KillerSudokuHelper.fs @@ -0,0 +1,4 @@ +module KillerSudokuHelper + +let combinations sum size exclude = + failwith "Please implement the 'combinations' function" diff --git a/exercises/practice/killer-sudoku-helper/KillerSudokuHelper.fsproj b/exercises/practice/killer-sudoku-helper/KillerSudokuHelper.fsproj new file mode 100644 index 000000000..24cf39ce6 --- /dev/null +++ b/exercises/practice/killer-sudoku-helper/KillerSudokuHelper.fsproj @@ -0,0 +1,22 @@ + + + net8.0 + false + false + true + + + + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + \ No newline at end of file diff --git a/exercises/practice/killer-sudoku-helper/KillerSudokuHelperTests.fs b/exercises/practice/killer-sudoku-helper/KillerSudokuHelperTests.fs new file mode 100644 index 000000000..33727c111 --- /dev/null +++ b/exercises/practice/killer-sudoku-helper/KillerSudokuHelperTests.fs @@ -0,0 +1,67 @@ +module KillerSudokuHelperTests + +open FsUnit.Xunit +open Xunit + +open KillerSudokuHelper + +[] +let ``1`` () = + combinations 1 1 [] |> should equal [ [ 1 ] ] + +[] +let ``2`` () = + combinations 2 1 [] |> should equal [ [ 2 ] ] + +[] +let ``3`` () = + combinations 3 1 [] |> should equal [ [ 3 ] ] + +[] +let ``4`` () = + combinations 4 1 [] |> should equal [ [ 4 ] ] + +[] +let ``5`` () = + combinations 5 1 [] |> should equal [ [ 5 ] ] + +[] +let ``6`` () = + combinations 6 1 [] |> should equal [ [ 6 ] ] + +[] +let ``7`` () = + combinations 7 1 [] |> should equal [ [ 7 ] ] + +[] +let ``8`` () = + combinations 8 1 [] |> should equal [ [ 8 ] ] + +[] +let ``9`` () = + combinations 9 1 [] |> should equal [ [ 9 ] ] + +[] +let ``Cage with sum 45 contains all digits 1:9`` () = + combinations 45 9 [] + |> should equal [ [ 1; 2; 3; 4; 5; 6; 7; 8; 9 ] ] + +[] +let ``Cage with only 1 possible combination`` () = + combinations 7 3 [] + |> should equal [ [ 1; 2; 4 ] ] + +[] +let ``Cage with several combinations`` () = + combinations 10 2 [] + |> should + equal + [ [ 1; 9 ] + [ 2; 8 ] + [ 3; 7 ] + [ 4; 6 ] ] + +[] +let ``Cage with several combinations that is restricted`` () = + combinations 10 2 [ 1; 4 ] + |> should equal [ [ 2; 8 ]; [ 3; 7 ] ] diff --git a/generators/Generators.fs b/generators/Generators.fs index c814e814e..23fe473dd 100644 --- a/generators/Generators.fs +++ b/generators/Generators.fs @@ -2050,3 +2050,6 @@ type BottleSong() = type ResistorColorTrio() = inherit ExerciseGenerator() + +type KillerSudokuHelper() = + inherit ExerciseGenerator()