From 750d811b1d1956e3e7797bbcc7099ccd4619de7b Mon Sep 17 00:00:00 2001 From: Eric Willigers Date: Sat, 13 Jul 2024 13:31:59 +1000 Subject: [PATCH] Add largest-series-product exercise --- config.json | 8 + .../.docs/instructions.append.md | 9 ++ .../.docs/instructions.md | 26 ++++ .../.docs/introduction.md | 5 + .../practice/largest-series-product/.eslintrc | 18 +++ .../largest-series-product/.meta/config.json | 28 ++++ .../largest-series-product/.meta/proof.ci.wat | 77 ++++++++++ .../largest-series-product/.meta/tests.toml | 60 ++++++++ .../practice/largest-series-product/.npmrc | 1 + .../practice/largest-series-product/LICENSE | 21 +++ .../largest-series-product/babel.config.js | 4 + .../largest-series-product.spec.js | 142 ++++++++++++++++++ .../largest-series-product.wat | 15 ++ .../largest-series-product/package.json | 34 +++++ 14 files changed, 448 insertions(+) create mode 100644 exercises/practice/largest-series-product/.docs/instructions.append.md create mode 100644 exercises/practice/largest-series-product/.docs/instructions.md create mode 100644 exercises/practice/largest-series-product/.docs/introduction.md create mode 100644 exercises/practice/largest-series-product/.eslintrc create mode 100644 exercises/practice/largest-series-product/.meta/config.json create mode 100644 exercises/practice/largest-series-product/.meta/proof.ci.wat create mode 100644 exercises/practice/largest-series-product/.meta/tests.toml create mode 100644 exercises/practice/largest-series-product/.npmrc create mode 100644 exercises/practice/largest-series-product/LICENSE create mode 100644 exercises/practice/largest-series-product/babel.config.js create mode 100644 exercises/practice/largest-series-product/largest-series-product.spec.js create mode 100644 exercises/practice/largest-series-product/largest-series-product.wat create mode 100644 exercises/practice/largest-series-product/package.json diff --git a/config.json b/config.json index 8b3a299..53bc7cb 100644 --- a/config.json +++ b/config.json @@ -223,6 +223,14 @@ "prerequisites": [], "difficulty": 4 }, + { + "slug": "largest-series-product", + "name": "Largest Series Product", + "uuid": "2534388c-96ef-45f7-a4e2-eb830dfb9c66", + "practices": [], + "prerequisites": [], + "difficulty": 5 + }, { "slug": "sieve", "name": "Sieve of Eratosthenes", diff --git a/exercises/practice/largest-series-product/.docs/instructions.append.md b/exercises/practice/largest-series-product/.docs/instructions.append.md new file mode 100644 index 0000000..9c9b521 --- /dev/null +++ b/exercises/practice/largest-series-product/.docs/instructions.append.md @@ -0,0 +1,9 @@ +# Instruction append + +If the input is invalid, return `-1`. + +## Reserved Memory + +The buffer for the input string uses bytes 64-319 of linear memory. + +The input string can be modified if desired. diff --git a/exercises/practice/largest-series-product/.docs/instructions.md b/exercises/practice/largest-series-product/.docs/instructions.md new file mode 100644 index 0000000..f297b57 --- /dev/null +++ b/exercises/practice/largest-series-product/.docs/instructions.md @@ -0,0 +1,26 @@ +# Instructions + +Your task is to look for patterns in the long sequence of digits in the encrypted signal. + +The technique you're going to use here is called the largest series product. + +Let's define a few terms, first. + +- **input**: the sequence of digits that you need to analyze +- **series**: a sequence of adjacent digits (those that are next to each other) that is contained within the input +- **span**: how many digits long each series is +- **product**: what you get when you multiply numbers together + +Let's work through an example, with the input `"63915"`. + +- To form a series, take adjacent digits in the original input. +- If you are working with a span of `3`, there will be three possible series: + - `"639"` + - `"391"` + - `"915"` +- Then we need to calculate the product of each series: + - The product of the series `"639"` is 162 (`6 × 3 × 9 = 162`) + - The product of the series `"391"` is 27 (`3 × 9 × 1 = 27`) + - The product of the series `"915"` is 45 (`9 × 1 × 5 = 45`) +- 162 is bigger than both 27 and 45, so the largest series product of `"63915"` is from the series `"639"`. + So the answer is **162**. diff --git a/exercises/practice/largest-series-product/.docs/introduction.md b/exercises/practice/largest-series-product/.docs/introduction.md new file mode 100644 index 0000000..597bb5f --- /dev/null +++ b/exercises/practice/largest-series-product/.docs/introduction.md @@ -0,0 +1,5 @@ +# Introduction + +You work for a government agency that has intercepted a series of encrypted communication signals from a group of bank robbers. +The signals contain a long sequence of digits. +Your team needs to use various digital signal processing techniques to analyze the signals and identify any patterns that may indicate the planning of a heist. diff --git a/exercises/practice/largest-series-product/.eslintrc b/exercises/practice/largest-series-product/.eslintrc new file mode 100644 index 0000000..1dbeac2 --- /dev/null +++ b/exercises/practice/largest-series-product/.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/largest-series-product/.meta/config.json b/exercises/practice/largest-series-product/.meta/config.json new file mode 100644 index 0000000..45fc387 --- /dev/null +++ b/exercises/practice/largest-series-product/.meta/config.json @@ -0,0 +1,28 @@ +{ + "authors": [ + "keiravillekode" + ], + "files": { + "solution": [ + "largest-series-product.wat" + ], + "test": [ + "largest-series-product.spec.js" + ], + "example": [ + ".meta/proof.ci.wat" + ], + "invalidator": [ + "package.json" + ] + }, + "blurb": "Given a string of digits, calculate the largest product for a contiguous substring of digits of length n.", + "source": "A variation on Problem 8 at Project Euler", + "source_url": "https://projecteuler.net/problem=8", + "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/largest-series-product/.meta/proof.ci.wat b/exercises/practice/largest-series-product/.meta/proof.ci.wat new file mode 100644 index 0000000..07525fc --- /dev/null +++ b/exercises/practice/largest-series-product/.meta/proof.ci.wat @@ -0,0 +1,77 @@ +(module + (memory (export "mem") 1) + + (global $ZERO i32 (i32.const 48)) + + ;; + ;; Calculate the largest product for a contiguous substring of digits of length n. + ;; + ;; @param {i32} offset - offset of string in linear memory + ;; @param {i32} length - length of string in linear memory + ;; + ;; @returns {i32} largest digit product + ;; + (func (export "largestProduct") (param $offset i32) (param $length i32) (param $span i32) (result i32) + (local $product i32) + (local $stop i32) + (local $start i32) + (local $result i32) + (local $index i32) + (local $digit i32) + + (if (i32.or (i32.lt_s (local.get $span) (i32.const 0)) + (i32.gt_s (local.get $span) (local.get $length))) (then + (return (i32.const -1)) + )) + + (if (i32.eqz (local.get $length)) (then + (return (i32.const 1)) + )) + + (local.set $stop (i32.add (local.get $offset) (local.get $length))) + + (local.set $index (local.get $offset)) + (loop + (local.set $digit (i32.sub (i32.load8_u (local.get $index)) + (global.get $ZERO))) + (if (i32.gt_u (local.get $digit) (i32.const 9)) (then + ;; $digit is not a valid digit. + (return (i32.const -1)) + )) + (i32.store8 (local.get $index) (local.get $digit)) + + (local.set $index (i32.add (local.get $index) (i32.const 1))) + (br_if 0 (i32.lt_u (local.get $index) (local.get $stop))) + ) + + (if (i32.eqz (local.get $span)) (then + (return (i32.const 1)) + )) + + (local.set $result (i32.const 0)) + + (local.set $start (i32.sub (local.get $stop) (local.get $span))) + (loop $outer + (local.set $product (i32.const 1)) + (local.set $index (local.get $start)) + (loop $inner + (local.set $product (i32.mul (local.get $product) + (i32.load8_u (local.get $index)))) + (local.set $index (i32.add (local.get $index) (i32.const 1))) + (br_if $inner (i32.lt_u (local.get $index) (local.get $stop))) + ) + (if (i32.lt_u (local.get $result) (local.get $product)) (then + (local.set $result (local.get $product)) + )) + + (if (i32.eq (local.get $start) (local.get $offset)) (then + (return (local.get $result)) + )) + + (local.set $start (i32.sub (local.get $start) (i32.const 1))) + (local.set $stop (i32.sub (local.get $stop) (i32.const 1))) + (br $outer) + ) + (unreachable) + ) +) diff --git a/exercises/practice/largest-series-product/.meta/tests.toml b/exercises/practice/largest-series-product/.meta/tests.toml new file mode 100644 index 0000000..6c111ad --- /dev/null +++ b/exercises/practice/largest-series-product/.meta/tests.toml @@ -0,0 +1,60 @@ +# 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. + +[7c82f8b7-e347-48ee-8a22-f672323324d4] +description = "finds the largest product if span equals length" + +[88523f65-21ba-4458-a76a-b4aaf6e4cb5e] +description = "can find the largest product of 2 with numbers in order" + +[f1376b48-1157-419d-92c2-1d7e36a70b8a] +description = "can find the largest product of 2" + +[46356a67-7e02-489e-8fea-321c2fa7b4a4] +description = "can find the largest product of 3 with numbers in order" + +[a2dcb54b-2b8f-4993-92dd-5ce56dece64a] +description = "can find the largest product of 3" + +[673210a3-33cd-4708-940b-c482d7a88f9d] +description = "can find the largest product of 5 with numbers in order" + +[02acd5a6-3bbf-46df-8282-8b313a80a7c9] +description = "can get the largest product of a big number" + +[76dcc407-21e9-424c-a98e-609f269622b5] +description = "reports zero if the only digits are zero" + +[6ef0df9f-52d4-4a5d-b210-f6fae5f20e19] +description = "reports zero if all spans include zero" + +[5d81aaf7-4f67-4125-bf33-11493cc7eab7] +description = "rejects span longer than string length" + +[06bc8b90-0c51-4c54-ac22-3ec3893a079e] +description = "reports 1 for empty string and empty product (0 span)" + +[3ec0d92e-f2e2-4090-a380-70afee02f4c0] +description = "reports 1 for nonempty string and empty product (0 span)" + +[6d96c691-4374-4404-80ee-2ea8f3613dd4] +description = "rejects empty string and nonzero span" + +[7a38f2d6-3c35-45f6-8d6f-12e6e32d4d74] +description = "rejects invalid character in digits" + +[5fe3c0e5-a945-49f2-b584-f0814b4dd1ef] +description = "rejects negative span" +include = false + +[c859f34a-9bfe-4897-9c2f-6d7f8598e7f0] +description = "rejects negative span" +reimplements = "5fe3c0e5-a945-49f2-b584-f0814b4dd1ef" diff --git a/exercises/practice/largest-series-product/.npmrc b/exercises/practice/largest-series-product/.npmrc new file mode 100644 index 0000000..d26df80 --- /dev/null +++ b/exercises/practice/largest-series-product/.npmrc @@ -0,0 +1 @@ +audit=false diff --git a/exercises/practice/largest-series-product/LICENSE b/exercises/practice/largest-series-product/LICENSE new file mode 100644 index 0000000..90e73be --- /dev/null +++ b/exercises/practice/largest-series-product/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/largest-series-product/babel.config.js b/exercises/practice/largest-series-product/babel.config.js new file mode 100644 index 0000000..9c17ba5 --- /dev/null +++ b/exercises/practice/largest-series-product/babel.config.js @@ -0,0 +1,4 @@ +export default { + presets: ["@exercism/babel-preset-javascript"], + plugins: [], +}; diff --git a/exercises/practice/largest-series-product/largest-series-product.spec.js b/exercises/practice/largest-series-product/largest-series-product.spec.js new file mode 100644 index 0000000..74aa882 --- /dev/null +++ b/exercises/practice/largest-series-product/largest-series-product.spec.js @@ -0,0 +1,142 @@ +import { compileWat, WasmRunner } from "@exercism/wasm-lib"; + +let wasmModule; +let currentInstance; + +function largestProduct(input = "", span) { + const inputBufferOffset = 64; + const inputBufferCapacity = 256; + + 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); + + // Pass offset and length to WebAssembly function + return currentInstance.exports.largestProduct( + inputBufferOffset, + inputLengthEncoded, + span + ); +} + +beforeAll(async () => { + try { + const watPath = new URL("./largest-series-product.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); + } +}); + +describe("largestProduct()", () => { + 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(); + } + }); + + test("finds the largest product if span equals length", () => { + const expected = 18; + const actual = largestProduct("29", 2); + expect(actual).toEqual(expected); + }); + + xtest("can find the largest product of 2 with numbers in order", () => { + const expected = 72; + const actual = largestProduct("0123456789", 2); + expect(actual).toEqual(expected); + }); + + xtest("can find the largest product of 2", () => { + const expected = 48; + const actual = largestProduct("576802143", 2); + expect(actual).toEqual(expected); + }); + + xtest("can find the largest product of 3 with numbers in order", () => { + const expected = 504; + const actual = largestProduct("0123456789", 3); + expect(actual).toEqual(expected); + }); + + xtest("can find the largest product of 3", () => { + const expected = 270; + const actual = largestProduct("1027839564", 3); + expect(actual).toEqual(expected); + }); + + xtest("can find the largest product of 5 with numbers in order", () => { + const expected = 15120; + const actual = largestProduct("0123456789", 5); + expect(actual).toEqual(expected); + }); + + xtest("can get the largest product of a big number", () => { + const expected = 23520; + const actual = largestProduct("73167176531330624919225119674426574742355349194934", 6); + expect(actual).toEqual(expected); + }); + + xtest("reports zero if the only digits are zero", () => { + const expected = 0; + const actual = largestProduct("0000", 2); + expect(actual).toEqual(expected); + }); + + xtest("reports zero if all spans include zero", () => { + const expected = 0; + const actual = largestProduct("99099", 3); + expect(actual).toEqual(expected); + }); + + xtest("rejects span longer than string length", () => { + const expected = -1; + const actual = largestProduct("123", 4); + expect(actual).toEqual(expected); + }); + + xtest("reports 1 for empty string and empty product (0 span)", () => { + const expected = 1; + const actual = largestProduct("", 0); + expect(actual).toEqual(expected); + }); + + xtest("reports 1 for nonempty string and empty product (0 span)", () => { + const expected = 1; + const actual = largestProduct("123", 0); + expect(actual).toEqual(expected); + }); + + xtest("rejects empty string and nonzero span", () => { + const expected = -1; + const actual = largestProduct("", 1); + expect(actual).toEqual(expected); + }); + + xtest("rejects invalid character in digits", () => { + const expected = -1; + const actual = largestProduct("1234a5", 2); + expect(actual).toEqual(expected); + }); + + xtest("rejects negative span", () => { + const expected = -1; + const actual = largestProduct("12345", -1); + expect(actual).toEqual(expected); + }); +}); diff --git a/exercises/practice/largest-series-product/largest-series-product.wat b/exercises/practice/largest-series-product/largest-series-product.wat new file mode 100644 index 0000000..5cbdfc6 --- /dev/null +++ b/exercises/practice/largest-series-product/largest-series-product.wat @@ -0,0 +1,15 @@ +(module + (memory (export "mem") 1) + + ;; + ;; Calculate the largest product for a contiguous substring of digits of length n. + ;; + ;; @param {i32} offset - offset of string in linear memory + ;; @param {i32} length - length of string in linear memory + ;; + ;; @returns {i32} largest digit product + ;; + (func (export "largestProduct") (param $offset i32) (param $length i32) (param $span i32) (result i32) + (return (i32.const 0)) + ) +) diff --git a/exercises/practice/largest-series-product/package.json b/exercises/practice/largest-series-product/package.json new file mode 100644 index 0000000..5a523ba --- /dev/null +++ b/exercises/practice/largest-series-product/package.json @@ -0,0 +1,34 @@ +{ + "name": "@exercism/wasm-largest-series-product", + "description": "Exercism exercises in WebAssembly.", + "type": "module", + "private": true, + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/exercism/wasm", + "directory": "exercises/practice/largest-series-product" + }, + "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 ." + } +}