diff --git a/config.json b/config.json index 1301a5d9d5..460826f349 100644 --- a/config.json +++ b/config.json @@ -2642,6 +2642,20 @@ "classes", "callbacks" ] + }, + { + "slug": "killer-sudoku-helper", + "name": "Killer Sudoku Helper", + "uuid": "4b6b00cd-62f8-4e9e-b59d-79f153f8efb5", + "practices": [], + "prerequisites": [ + "conditionals", + "recursion", + "loops", + "numbers", + "lists" + ], + "difficulty": 5 } ] }, 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 0000000000..fdafdca8fb --- /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/.eslintrc b/exercises/practice/killer-sudoku-helper/.eslintrc new file mode 100644 index 0000000000..1d4446029c --- /dev/null +++ b/exercises/practice/killer-sudoku-helper/.eslintrc @@ -0,0 +1,14 @@ +{ + "root": true, + "extends": "@exercism/eslint-config-javascript", + "env": { + "jest": true + }, + "overrides": [ + { + "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], + "excludedFiles": ["custom.spec.js"], + "extends": "@exercism/eslint-config-javascript/maintainers" + } + ] +} diff --git a/exercises/practice/killer-sudoku-helper/.gitignore b/exercises/practice/killer-sudoku-helper/.gitignore new file mode 100644 index 0000000000..31c57dd53a --- /dev/null +++ b/exercises/practice/killer-sudoku-helper/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/pnpm-lock.yaml +/yarn.lock 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 0000000000..41bf7f8933 --- /dev/null +++ b/exercises/practice/killer-sudoku-helper/.meta/config.json @@ -0,0 +1,19 @@ +{ + "authors": [ + "Cool-Katt" + ], + "files": { + "solution": [ + "killer-sudoku-helper.js" + ], + "test": [ + "killer-sudoku-helper.spec.js" + ], + "example": [ + ".meta/proof.ci.js" + ] + }, + "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/proof.ci.js b/exercises/practice/killer-sudoku-helper/.meta/proof.ci.js new file mode 100644 index 0000000000..570c87c060 --- /dev/null +++ b/exercises/practice/killer-sudoku-helper/.meta/proof.ci.js @@ -0,0 +1,28 @@ +export const combinations = (cage) => { + const { sum, size, exclude } = cage; + const result = []; + const digits = [...Array(10).keys()] + .slice(1) + .filter((d) => !exclude.includes(d)); + + function findCombinations(remainingSum, index, combination) { + if (remainingSum === 0 && combination.length === size) { + result.push(combination); + return; + } + + for (let i = index; i < digits.length; i++) { + const digit = digits[i]; + if (digit > remainingSum) { + break; + } + if (combination.includes(digit)) { + continue; + } + findCombinations(remainingSum - digit, i + 1, [...combination, digit]); + } + } + + findCombinations(sum, 0, []); + return result; +}; 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 0000000000..19c23e8a92 --- /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/.npmrc b/exercises/practice/killer-sudoku-helper/.npmrc new file mode 100644 index 0000000000..d26df800bb --- /dev/null +++ b/exercises/practice/killer-sudoku-helper/.npmrc @@ -0,0 +1 @@ +audit=false diff --git a/exercises/practice/killer-sudoku-helper/LICENSE b/exercises/practice/killer-sudoku-helper/LICENSE new file mode 100644 index 0000000000..90e73be03b --- /dev/null +++ b/exercises/practice/killer-sudoku-helper/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 Exercism + +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. diff --git a/exercises/practice/killer-sudoku-helper/babel.config.js b/exercises/practice/killer-sudoku-helper/babel.config.js new file mode 100644 index 0000000000..b781d5a667 --- /dev/null +++ b/exercises/practice/killer-sudoku-helper/babel.config.js @@ -0,0 +1,4 @@ +module.exports = { + presets: ['@exercism/babel-preset-javascript'], + plugins: [], +}; diff --git a/exercises/practice/killer-sudoku-helper/killer-sudoku-helper.js b/exercises/practice/killer-sudoku-helper/killer-sudoku-helper.js new file mode 100644 index 0000000000..2e432dd6e1 --- /dev/null +++ b/exercises/practice/killer-sudoku-helper/killer-sudoku-helper.js @@ -0,0 +1,8 @@ +// +// This is only a SKELETON file for the 'Killer Sudoku Helper' exercise. It's been provided as a +// convenience to get you started writing code faster. +// + +export const combinations = (cage) => { + throw new Error('Remove this statement and implement this function'); +}; diff --git a/exercises/practice/killer-sudoku-helper/killer-sudoku-helper.spec.js b/exercises/practice/killer-sudoku-helper/killer-sudoku-helper.spec.js new file mode 100644 index 0000000000..9a1a295b65 --- /dev/null +++ b/exercises/practice/killer-sudoku-helper/killer-sudoku-helper.spec.js @@ -0,0 +1,156 @@ +import { combinations } from './killer-sudoku-helper'; + +describe('Trivial 1-digit cages', () => { + test('1', () => { + const inputCage = { + sum: 1, + size: 1, + exclude: [], + }; + const expected = [[1]]; + const actual = combinations(inputCage); + expect(actual).toEqual(expected); + }); + + xtest('2', () => { + const inputCage = { + sum: 2, + size: 1, + exclude: [], + }; + const expected = [[2]]; + const actual = combinations(inputCage); + expect(actual).toEqual(expected); + }); + + xtest('3', () => { + const inputCage = { + sum: 3, + size: 1, + exclude: [], + }; + const expected = [[3]]; + const actual = combinations(inputCage); + expect(actual).toEqual(expected); + }); + + xtest('4', () => { + const inputCage = { + sum: 4, + size: 1, + exclude: [], + }; + const expected = [[4]]; + const actual = combinations(inputCage); + expect(actual).toEqual(expected); + }); + + xtest('5', () => { + const inputCage = { + sum: 5, + size: 1, + exclude: [], + }; + const expected = [[5]]; + const actual = combinations(inputCage); + expect(actual).toEqual(expected); + }); + + xtest('6', () => { + const inputCage = { + sum: 6, + size: 1, + exclude: [], + }; + const expected = [[6]]; + const actual = combinations(inputCage); + expect(actual).toEqual(expected); + }); + + xtest('7', () => { + const inputCage = { + sum: 7, + size: 1, + exclude: [], + }; + const expected = [[7]]; + const actual = combinations(inputCage); + expect(actual).toEqual(expected); + }); + + xtest('8', () => { + const inputCage = { + sum: 8, + size: 1, + exclude: [], + }; + const expected = [[8]]; + const actual = combinations(inputCage); + expect(actual).toEqual(expected); + }); + + xtest('9', () => { + const inputCage = { + sum: 9, + size: 1, + exclude: [], + }; + const expected = [[9]]; + const actual = combinations(inputCage); + expect(actual).toEqual(expected); + }); +}); + +describe('Other cages', () => { + xtest('Cage with sum 45 contains all digits 1:9', () => { + const inputCage = { + sum: 45, + size: 9, + exclude: [], + }; + const expected = [[1, 2, 3, 4, 5, 6, 7, 8, 9]]; + const actual = combinations(inputCage); + expect(actual).toEqual(expected); + }); + + xtest('Cage with only 1 possible combination', () => { + const inputCage = { + sum: 7, + size: 3, + exclude: [], + }; + const expected = [[1, 2, 4]]; + const actual = combinations(inputCage); + expect(actual).toEqual(expected); + }); + + xtest('Cage with several combinations', () => { + const inputCage = { + sum: 10, + size: 2, + exclude: [], + }; + const expected = [ + [1, 9], + [2, 8], + [3, 7], + [4, 6], + ]; + const actual = combinations(inputCage); + expect(actual).toEqual(expected); + }); + + xtest('Cage with several combinations that is restricted', () => { + const inputCage = { + sum: 10, + size: 2, + exclude: [1, 4], + }; + const expected = [ + [2, 8], + [3, 7], + ]; + const actual = combinations(inputCage); + expect(actual).toEqual(expected); + }); +}); diff --git a/exercises/practice/killer-sudoku-helper/package.json b/exercises/practice/killer-sudoku-helper/package.json new file mode 100644 index 0000000000..682b9f2dbc --- /dev/null +++ b/exercises/practice/killer-sudoku-helper/package.json @@ -0,0 +1,34 @@ +{ + "name": "@exercism/javascript-killer-sudoku-helper", + "description": "Exercism practice exercise on killer-sudoku-helper", + "author": "Katrina Owen", + "contributors": [ + "Derk-Jan Karrenbeld (https://derk-jan.com)", + "Tejas Bubane (https://tejasbubane.github.io/)", + "Cool-Katt (https://github.com/Cool-Katt)" + ], + "private": true, + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/exercism/javascript", + "directory": "exercises/practice/killer-sudoku-helper" + }, + "devDependencies": { + "@babel/core": "^7.25.2", + "@exercism/babel-preset-javascript": "^0.2.1", + "@exercism/eslint-config-javascript": "^0.6.0", + "@types/jest": "^29.5.12", + "@types/node": "^20.12.12", + "babel-jest": "^29.6.4", + "core-js": "~3.37.1", + "eslint": "^8.49.0", + "jest": "^29.7.0" + }, + "dependencies": {}, + "scripts": { + "test": "jest ./*", + "watch": "jest --watch ./*", + "lint": "eslint ." + } +}