diff --git a/config.json b/config.json index 5e1ddf4..0faae62 100644 --- a/config.json +++ b/config.json @@ -685,6 +685,14 @@ "uuid": "9edd0fda-cc73-40a2-a32c-b16918c65167", "practices": [], "prerequisites": [], + "difficulty": 2 + }, + { + "slug": "saddle-points", + "name": "Saddle Points", + "uuid": "5efb23b7-fa38-4667-a344-4455877beb03", + "practices": [], + "prerequisites": [], "difficulty": 4 } ] diff --git a/exercises/practice/saddle-points/.docs/instructions.append.md b/exercises/practice/saddle-points/.docs/instructions.append.md new file mode 100644 index 0000000..56cf001 --- /dev/null +++ b/exercises/practice/saddle-points/.docs/instructions.append.md @@ -0,0 +1,18 @@ +## Input format + +The input will be in the format of a string. To keep this exercise beginner-friendly, you need not expect numbers with multiple digits. + +Bytes 64-191 of the linear memory are reserved for the input string. + +## Output format + +The output is expected in pairs of row and column as u8 directly concatenated. + +For example, if there are three saddle points + +row: 2, column: 1 +row: 2, column: 2 +row: 2, column: 3 + +then the expected output would be the u8 values `2, 1, 2, 2, 2, 3` + diff --git a/exercises/practice/saddle-points/.docs/instructions.md b/exercises/practice/saddle-points/.docs/instructions.md new file mode 100644 index 0000000..f69cdab --- /dev/null +++ b/exercises/practice/saddle-points/.docs/instructions.md @@ -0,0 +1,27 @@ +# Instructions + +Your task is to find the potential trees where you could build your tree house. + +The data company provides the data as grids that show the heights of the trees. +The rows of the grid represent the east-west direction, and the columns represent the north-south direction. + +An acceptable tree will be the largest in its row, while being the smallest in its column. + +A grid might not have any good trees at all. +Or it might have one, or even several. + +Here is a grid that has exactly one candidate tree. + +```text + ↓ + 1 2 3 4 + |----------- + 1 | 9 8 7 8 +→ 2 |[5] 3 2 4 + 3 | 6 6 7 1 +``` + +- Row 2 has values 5, 3, 2, and 4. The largest value is 5. +- Column 1 has values 9, 5, and 6. The smallest value is 5. + +So the point at `[2, 1]` (row: 2, column: 1) is a great spot for a tree house. diff --git a/exercises/practice/saddle-points/.docs/introduction.md b/exercises/practice/saddle-points/.docs/introduction.md new file mode 100644 index 0000000..34b2c77 --- /dev/null +++ b/exercises/practice/saddle-points/.docs/introduction.md @@ -0,0 +1,11 @@ +# Introduction + +You plan to build a tree house in the woods near your house so that you can watch the sun rise and set. + +You've obtained data from a local survey company that show the height of every tree in each rectangular section of the map. +You need to analyze each grid on the map to find good trees for your tree house. + +A good tree is both: + +- taller than every tree to the east and west, so that you have the best possible view of the sunrises and sunsets. +- shorter than every tree to the north and south, to minimize the amount of tree climbing. diff --git a/exercises/practice/saddle-points/.eslintrc b/exercises/practice/saddle-points/.eslintrc new file mode 100644 index 0000000..1dbeac2 --- /dev/null +++ b/exercises/practice/saddle-points/.eslintrc @@ -0,0 +1,18 @@ +{ + "root": true, + "extends": "@exercism/eslint-config-javascript", + "env": { + "jest": true + }, + "overrides": [ + { + "files": [ + "*.spec.js" + ], + "excludedFiles": [ + "custom.spec.js" + ], + "extends": "@exercism/eslint-config-javascript/maintainers" + } + ] +} diff --git a/exercises/practice/saddle-points/.meta/config.json b/exercises/practice/saddle-points/.meta/config.json new file mode 100644 index 0000000..09b867f --- /dev/null +++ b/exercises/practice/saddle-points/.meta/config.json @@ -0,0 +1,29 @@ +{ + "authors": [ + "atk" + ], + "files": { + "solution": [ + "saddle-points.wat" + ], + "test": [ + "saddle-points.spec.js" + ], + "example": [ + ".meta/proof.ci.wat" + ], + "invalidator": [ + "package.json" + ] + + }, + "blurb": "Detect saddle points in a matrix.", + "source": "J Dalbey's Programming Practice problems", + "source_url": "https://users.csc.calpoly.edu/~jdalbey/103/Projects/ProgrammingPractice.html", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": false, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false + } +} diff --git a/exercises/practice/saddle-points/.meta/proof.ci.wat b/exercises/practice/saddle-points/.meta/proof.ci.wat new file mode 100644 index 0000000..91e24d9 --- /dev/null +++ b/exercises/practice/saddle-points/.meta/proof.ci.wat @@ -0,0 +1,71 @@ +(module + (memory (export "mem") 1) + + (global $outputOffset i32 (i32.const 1280)) + (global $parsedOffset i32 (i32.const 512)) + (global $rowMaxOffset i32 (i32.const 768)) + (global $colMinOffset i32 (i32.const 1024)) + (global $zero i32 (i32.const 48)) + (global $br i32 (i32.const 10)) + + ;; + ;; Find the points in the matrix that are the largest in row and smallest in column + ;; + ;; @param {i32} $inputOffset - offset of the matrix in linear memory + ;; @param {i32} $inputLength - length of the matrix in linear memory + ;; + ;; @result {(i32,i32)} - offset and length of row-column-pairs in linear memory + ;; + (func (export "saddlePoints") (param $inputOffset i32) (param $inputLength i32) (result i32 i32) + (local $row i32) + (local $column i32) + (local $lineLength i32) + (local $rowsLength i32) + (local $char i32) + (local $pos i32) + (local $height i32) + (local $outputLength i32) + (if (i32.eqz (local.get $inputLength)) (then (return (global.get $outputOffset) (i32.const 0)))) + (loop $scanInput + (local.set $char (i32.load8_u (i32.add (local.get $inputOffset) (local.get $pos)))) + (if (i32.ge_u (local.get $char) (global.get $zero)) + (then (local.set $height (i32.add (i32.mul (local.get $height) (i32.const 10)) + (i32.sub (local.get $char) (global.get $zero))))) + (else (i32.store8 (i32.add (global.get $parsedOffset) (local.get $outputLength)) (local.get $height)) + (local.set $outputLength (i32.add (local.get $outputLength) (i32.const 1)) + (if (i32.gt_u (local.get $height) (i32.load8_u (i32.add (global.get $rowMaxOffset) (local.get $row)))) + (then (i32.store8 (i32.add (global.get $rowMaxOffset) (local.get $row)) (local.get $height)))) + (if (i32.le_u (local.get $height) (i32.sub (i32.load8_u (i32.add (global.get $colMinOffset) + (local.get $column))) (i32.const 1))) + (then (i32.store8 (i32.add (global.get $colMinOffset) (local.get $column)) (local.get $height)))) + (local.set $column (i32.add (local.get $column) (i32.const 1))) + (local.set $height (i32.const 0))))) + (if (i32.eq (local.get $char) (global.get $br)) (then + (if (i32.eqz (local.get $lineLength)) (then (local.set $lineLength (local.get $pos)))) + (local.set $row (i32.add (local.get $row) (i32.const 1)) + (local.set $column (i32.const 0))))) + (local.set $pos (i32.add (local.get $pos) (i32.const 1))) + (br_if $scanInput (i32.lt_u (local.get $pos) (local.get $inputLength)))) + (local.set $rowsLength (local.get $row)) + (local.set $lineLength (i32.div_u (local.get $outputLength) (local.get $rowsLength))) + (local.set $outputLength (i32.const 0)) + (local.set $row (i32.const 0)) + (loop $rows + (local.set $column (i32.const 0)) + (loop $columns + (local.set $height (i32.load8_u (i32.add (global.get $parsedOffset) + (i32.add (i32.mul (local.get $row) (local.get $lineLength)) (local.get $column))))) + (if (i32.and (i32.eq (local.get $height) + (i32.load8_u (i32.add (global.get $rowMaxOffset) (local.get $row)))) + (i32.eq (local.get $height) (i32.load8_u (i32.add (global.get $colMinOffset) (local.get $column))))) + (then (i32.store16 (i32.add (global.get $outputOffset) (local.get $outputLength)) + (i32.or (i32.shl (i32.add (local.get $column) (i32.const 1)) (i32.const 8)) + (i32.add (local.get $row) (i32.const 1)))) + (local.set $outputLength (i32.add (local.get $outputLength) (i32.const 2))))) + (local.set $column (i32.add (local.get $column) (i32.const 1))) + (br_if $columns (i32.lt_u (local.get $column) (local.get $lineLength)))) + (local.set $row (i32.add (local.get $row) (i32.const 1))) + (br_if $rows (i32.lt_u (local.get $row) (local.get $rowsLength)))) + (global.get $outputOffset) (local.get $outputLength) + ) +) diff --git a/exercises/practice/saddle-points/.meta/tests.toml b/exercises/practice/saddle-points/.meta/tests.toml new file mode 100644 index 0000000..ca00852 --- /dev/null +++ b/exercises/practice/saddle-points/.meta/tests.toml @@ -0,0 +1,37 @@ +# 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. + +[3e374e63-a2e0-4530-a39a-d53c560382bd] +description = "Can identify single saddle point" + +[6b501e2b-6c1f-491f-b1bb-7f278f760534] +description = "Can identify that empty matrix has no saddle points" + +[8c27cc64-e573-4fcb-a099-f0ae863fb02f] +description = "Can identify lack of saddle points when there are none" + +[6d1399bd-e105-40fd-a2c9-c6609507d7a3] +description = "Can identify multiple saddle points in a column" + +[3e81dce9-53b3-44e6-bf26-e328885fd5d1] +description = "Can identify multiple saddle points in a row" + +[88868621-b6f4-4837-bb8b-3fad8b25d46b] +description = "Can identify saddle point in bottom right corner" + +[5b9499ca-fcea-4195-830a-9c4584a0ee79] +description = "Can identify saddle points in a non square matrix" + +[ee99ccd2-a1f1-4283-ad39-f8c70f0cf594] +description = "Can identify that saddle points in a single column matrix are those with the minimum value" + +[63abf709-a84b-407f-a1b3-456638689713] +description = "Can identify that saddle points in a single row matrix are those with the maximum value" diff --git a/exercises/practice/saddle-points/.npmrc b/exercises/practice/saddle-points/.npmrc new file mode 100644 index 0000000..d26df80 --- /dev/null +++ b/exercises/practice/saddle-points/.npmrc @@ -0,0 +1 @@ +audit=false diff --git a/exercises/practice/saddle-points/LICENSE b/exercises/practice/saddle-points/LICENSE new file mode 100644 index 0000000..90e73be --- /dev/null +++ b/exercises/practice/saddle-points/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/saddle-points/babel.config.js b/exercises/practice/saddle-points/babel.config.js new file mode 100644 index 0000000..9c17ba5 --- /dev/null +++ b/exercises/practice/saddle-points/babel.config.js @@ -0,0 +1,4 @@ +export default { + presets: ["@exercism/babel-preset-javascript"], + plugins: [], +}; diff --git a/exercises/practice/saddle-points/package.json b/exercises/practice/saddle-points/package.json new file mode 100644 index 0000000..1af80a8 --- /dev/null +++ b/exercises/practice/saddle-points/package.json @@ -0,0 +1,35 @@ +{ + "name": "@exercism/wasm-saddle-points", + "description": "Exercism exercises in WebAssembly.", + "author": "Alex Lohr", + "type": "module", + "private": true, + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/exercism/wasm", + "directory": "exercises/practice/saddle-points" + }, + "jest": { + "maxWorkers": 1 + }, + "devDependencies": { + "@babel/core": "^7.23.3", + "@exercism/babel-preset-javascript": "^0.4.0", + "@exercism/eslint-config-javascript": "^0.6.0", + "@types/jest": "^29.5.8", + "@types/node": "^20.9.1", + "babel-jest": "^29.7.0", + "core-js": "^3.33.2", + "eslint": "^8.54.0", + "jest": "^29.7.0" + }, + "dependencies": { + "@exercism/wasm-lib": "^0.2.0" + }, + "scripts": { + "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js ./*", + "watch": "node --experimental-vm-modules node_modules/jest/bin/jest.js --watch ./*", + "lint": "eslint ." + } +} diff --git a/exercises/practice/saddle-points/saddle-points.spec.js b/exercises/practice/saddle-points/saddle-points.spec.js new file mode 100644 index 0000000..da8dccb --- /dev/null +++ b/exercises/practice/saddle-points/saddle-points.spec.js @@ -0,0 +1,154 @@ +import { compileWat, WasmRunner } from "@exercism/wasm-lib"; + +let wasmModule; +let currentInstance; + +beforeAll(async () => { + try { + const watPath = new URL("./.meta/proof.ci.wat", import.meta.url); + const { buffer } = await compileWat(watPath); + wasmModule = await WebAssembly.compile(buffer); + } catch (err) { + console.log(`Error compiling *.wat: \n${err}`); + process.exit(1); + } +}); + +function saddlePoints(matrix) { + const inputBufferOffset = 64; + const inputBufferCapacity = 128; + const input = matrix?.[0]?.length ? matrix.map(line => line.join(' ') + '\n').join('') : ''; + + const inputLengthEncoded = new TextEncoder().encode(input).length; + if (inputLengthEncoded > inputBufferCapacity) { + throw new Error( + `String is too large for buffer of size ${inputBufferCapacity} bytes` + ); + } + + currentInstance.set_mem_as_utf8(inputBufferOffset, inputLengthEncoded, input); + + const [outputOffset, outputLength] = currentInstance.exports.saddlePoints( + inputBufferOffset, + inputLengthEncoded + ); + + const output = currentInstance.get_mem_as_u8(outputOffset, outputLength); + const outputIterator = output[Symbol.iterator](); + + return Array.from({ length: Math.ceil(outputLength / 2) }, () => ({ + row: outputIterator.next().value, + column: outputIterator.next().value + })); +} + +describe('Saddle Points', () => { + beforeEach(async () => { + currentInstance = null; + if (!wasmModule) { + return Promise.reject(); + } + try { + currentInstance = await new WasmRunner(wasmModule); + return Promise.resolve(); + } catch (err) { + console.log(`Error instantiating WebAssembly module: ${err}`); + return Promise.reject(); + } + }); + + xtest('Can identify single saddle point', () => { + const expected = [{ row: 2, column: 1 }]; + expect( + saddlePoints([ + [9, 8, 7], + [5, 3, 2], + [6, 6, 7], + ]), + ).toEqual(expected); + }); + + xtest('Can identify that empty matrix has no saddle points', () => { + expect(saddlePoints([[]])).toEqual([]); + }); + + xtest('Can identify lack of saddle points when there are none', () => { + expect( + saddlePoints([ + [1, 2, 3], + [3, 1, 2], + [2, 3, 1], + ]), + ).toEqual([]); + }); + + xtest('Can identify multiple saddle points in a column', () => { + const expected = [ + { row: 1, column: 2 }, + { row: 2, column: 2 }, + { row: 3, column: 2 }, + ]; + expect( + saddlePoints([ + [4, 5, 4], + [3, 5, 5], + [1, 5, 4], + ]), + ).toEqual(expected); + }); + + test('Can identify multiple saddle points in a row', () => { + const expected = [ + { row: 2, column: 1 }, + { row: 2, column: 2 }, + { row: 2, column: 3 }, + ]; + expect( + saddlePoints([ + [6, 7, 8], + [5, 5, 5], + [7, 5, 6], + ]), + ).toEqual(expected); + }); + + xtest('Can identify saddle point in bottom right corner', () => { + const expected = [{ row: 3, column: 3 }]; + expect( + saddlePoints([ + [8, 7, 9], + [6, 7, 6], + [3, 2, 5], + ]), + ).toEqual(expected); + }); + + xtest('Can identify saddle points in a non square matrix', () => { + const expected = [ + { row: 1, column: 1 }, + { row: 1, column: 3 }, + ]; + expect( + saddlePoints([ + [3, 1, 3], + [3, 2, 4], + ]), + ).toEqual(expected); + }); + + xtest('Can identify that saddle points in a single column matrix are those with the minimum value', () => { + const expected = [ + { row: 2, column: 1 }, + { row: 4, column: 1 }, + ]; + expect(saddlePoints([[2], [1], [4], [1]])).toEqual(expected); + }); + + xtest('Can identify that saddle points in a single row matrix are those with the maximum value', () => { + const expected = [ + { row: 1, column: 2 }, + { row: 1, column: 4 }, + ]; + expect(saddlePoints([[2, 5, 3, 5]])).toEqual(expected); + }); +}); diff --git a/exercises/practice/saddle-points/saddle-points.wat b/exercises/practice/saddle-points/saddle-points.wat new file mode 100644 index 0000000..ead976b --- /dev/null +++ b/exercises/practice/saddle-points/saddle-points.wat @@ -0,0 +1,15 @@ +(module + (memory (export "mem") 1) + + ;; + ;; Find the points in the matrix that are the largest in row and smallest in column + ;; + ;; @param {i32} $inputOffset - offset of the matrix in linear memory + ;; @param {i32} $inputLength - length of the matrix in linear memory + ;; + ;; @result {(i32,i32)} - offset and length of row-column-pairs in linear memory + ;; + (func (export "saddlePoints") (param $inputOffset i32) (param $inputLength i32) (result i32 i32) + (i32.const 0) (i32.const 0) + ) +) \ No newline at end of file