From c1ad1f8ff480238f28b3c773acf7923659782297 Mon Sep 17 00:00:00 2001 From: benjides Date: Sun, 16 Nov 2025 17:15:13 +0100 Subject: [PATCH 001/118] Modern scafold project TypeScript, eslint, vitest, prettier --- .babelrc | 8 - .eslintrc.json | 15 - .prettierrc | 5 + .travis.yml | 6 +- README.md | 167 ++++---- bun.lock | 616 +++++++++++++++++++++++++++ eslint.config.js | 21 + package.json | 75 +--- rollup.config.js | 12 - src/PriorityQueue.ts | 19 + src/core/Maizal.js | 23 - src/core/init/init.js | 99 ----- src/core/node.js | 20 - src/core/solver/rebuild.js | 26 -- src/core/solver/solver.js | 29 -- src/core/solver/stats.js | 16 - src/engine/engines.js | 14 - src/engine/informed/astar.js | 14 - src/engine/informed/bestfs.js | 15 - src/engine/informed/weightedastar.js | 15 - src/engine/uninformed/bfs.js | 11 - src/engine/uninformed/dfs.js | 11 - src/engine/uninformed/dijkstra.js | 11 - src/engine/uninformed/random.js | 11 - src/maizal.js | 20 - src/util/HashSet.js | 133 ------ src/util/PriorityQueue.js | 87 ---- test/corridor.js | 26 -- test/months.js | 50 --- test/unit/core/core.js | 284 ------------ test/unit/informed/astar.js | 69 --- test/unit/informed/bestfs.js | 50 --- test/unit/informed/weightedastar.js | 65 --- test/unit/uninformed/bfs.js | 50 --- test/unit/uninformed/dfs.js | 33 -- test/unit/uninformed/dijkstra.js | 59 --- test/unit/uninformed/random.js | 23 - test/unit/util/HashSet.js | 150 ------- test/unit/util/PriorityQueue.js | 77 ---- tests/PriorityQueue.test.ts | 8 + tsconfig.json | 44 ++ 41 files changed, 823 insertions(+), 1664 deletions(-) delete mode 100644 .babelrc delete mode 100644 .eslintrc.json create mode 100644 .prettierrc create mode 100644 bun.lock create mode 100644 eslint.config.js delete mode 100644 rollup.config.js create mode 100644 src/PriorityQueue.ts delete mode 100644 src/core/Maizal.js delete mode 100644 src/core/init/init.js delete mode 100644 src/core/node.js delete mode 100644 src/core/solver/rebuild.js delete mode 100644 src/core/solver/solver.js delete mode 100644 src/core/solver/stats.js delete mode 100644 src/engine/engines.js delete mode 100644 src/engine/informed/astar.js delete mode 100644 src/engine/informed/bestfs.js delete mode 100644 src/engine/informed/weightedastar.js delete mode 100644 src/engine/uninformed/bfs.js delete mode 100644 src/engine/uninformed/dfs.js delete mode 100644 src/engine/uninformed/dijkstra.js delete mode 100644 src/engine/uninformed/random.js delete mode 100644 src/maizal.js delete mode 100644 src/util/HashSet.js delete mode 100644 src/util/PriorityQueue.js delete mode 100644 test/corridor.js delete mode 100644 test/months.js delete mode 100644 test/unit/core/core.js delete mode 100644 test/unit/informed/astar.js delete mode 100644 test/unit/informed/bestfs.js delete mode 100644 test/unit/informed/weightedastar.js delete mode 100644 test/unit/uninformed/bfs.js delete mode 100644 test/unit/uninformed/dfs.js delete mode 100644 test/unit/uninformed/dijkstra.js delete mode 100644 test/unit/uninformed/random.js delete mode 100644 test/unit/util/HashSet.js delete mode 100644 test/unit/util/PriorityQueue.js create mode 100644 tests/PriorityQueue.test.ts create mode 100644 tsconfig.json diff --git a/.babelrc b/.babelrc deleted file mode 100644 index 746b6ab..0000000 --- a/.babelrc +++ /dev/null @@ -1,8 +0,0 @@ -{ - "presets": ["env"], - "env": { - "production": { - "presets": ["minify"] - } - } -} diff --git a/.eslintrc.json b/.eslintrc.json deleted file mode 100644 index 2a1dce4..0000000 --- a/.eslintrc.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "env": { - "browser": true, - "es6": true, - "node": true, - "mocha": true - }, - "extends": "airbnb-base", - "rules": { - "func-names": ["error", "never"], - "no-param-reassign": [2, { "props": false }], - "max-len": "off", - "valid-typeof": ["error", { "requireStringLiterals": false }] - } -} diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..aaf3357 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,5 @@ +{ + "singleQuote": true, + "semi": false, + "trailingComma": "all" +} diff --git a/.travis.yml b/.travis.yml index 2430d05..1b22c1b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,3 @@ - language: node_js node_js: - node @@ -7,10 +6,9 @@ before_deploy: after_success: - 'bash <(curl -s https://codecov.io/bash)' deploy: - skip_cleanup: true provider: npm - email: "$NPM_EMAIL" - api_key: "$NPM_TOKEN" + email: '$NPM_EMAIL' + api_key: '$NPM_TOKEN' skip_cleanup: true on: tags: true diff --git a/README.md b/README.md index 6504d0f..965d525 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ # Maizal +

> Pronounced like /'māisɔːl/ @@ -19,6 +20,7 @@ $ npm install maizal ``` In the browser + ``` ``` @@ -28,40 +30,42 @@ In the browser Perform a `Breadth-first` search ```js -const maizal = require('maizal'); +const maizal = require('maizal') -maizal.bfs({ - initial: { - position: 1, - }, - goals: { - position: 4, - }, - actions: [ - { - name: 'right', - expand: (state) => { - if (state.position + 1 > 5) return undefined; - return { position: state.position + 1 }; - }, +maizal + .bfs({ + initial: { + position: 1, }, - { - name: 'left', - expand: (state) => { - if (state.position - 1 < 0) return undefined; - return { position: state.position - 1 }; - }, + goals: { + position: 4, }, - ], - hash: 'position', -}) -.then(results => console.log(results)) -.catch(error => console.log(error)); + actions: [ + { + name: 'right', + expand: (state) => { + if (state.position + 1 > 5) return undefined + return { position: state.position + 1 } + }, + }, + { + name: 'left', + expand: (state) => { + if (state.position - 1 < 0) return undefined + return { position: state.position - 1 } + }, + }, + ], + hash: 'position', + }) + .then((results) => console.log(results)) + .catch((error) => console.log(error)) ``` Perform a `Dijkstra` search + ```js -const maizal = require('maizal'); +const maizal = require('maizal') const config = { initial: { @@ -75,15 +79,15 @@ const config = { name: 'right', cost: 50, expand: (state) => { - if (state.position + 1 > 5) return undefined; - return { position: state.position + 1 }; + if (state.position + 1 > 5) return undefined + return { position: state.position + 1 } }, }, { name: 'left', expand: (state) => { - if (state.position - 1 < 0) return undefined; - return { position: state.position - 1 }; + if (state.position - 1 < 0) return undefined + return { position: state.position - 1 } }, }, ], @@ -92,10 +96,10 @@ const config = { async function solveCorridor() { try { - const results = await maizal.dijkstra(config); - console.log(results); + const results = await maizal.dijkstra(config) + console.log(results) } catch (error) { - console.error(error); + console.error(error) } } ``` @@ -103,7 +107,6 @@ async function solveCorridor() { > **NOTE:** `async/await` is part of ECMAScript 2017 and is not supported in Internet > Explorer and older browsers, so use with caution. - ## Documentation The config object @@ -111,27 +114,29 @@ The config object ### Initial state | Type | Defaults | Optional | Description | -|--------|----------|----------|-----------------------------| +| ------ | -------- | -------- | --------------------------- | | Object | | false | Initial state of the search | Examples + ```js const initial = { position: [4, 5], -}; +} const initial = { name: 'myFancyName', -}; +} ``` ### Goals -| Type | Defaults | Optional | Description | -|--------|----------|----------|-----------------------------| +| Type | Defaults | Optional | Description | +| --------------- | -------- | -------- | ------------------- | | Object \| Array | | false | Set of goals states | Examples + ```js const goals = [ { @@ -139,70 +144,71 @@ const goals = [ }, { position: [4, 2], - } -]; + }, +] const goals = { height: 200, -}; +} ``` ### Actions -| Type | Defaults | Optional | Description | -|--------|----------|----------|-----------------------------| +| Type | Defaults | Optional | Description | +| --------------- | -------- | -------- | ----------------------------------------- | | Object \| Array | | false | Set of actions to take for each new state | -|Key | Type | Defaults | Optional | Description | -|-------|-----------|----------|----------|-----------------------------| -|name | String | 'expand' | true | Action name | -|cost | Int | 1 | true | Action cost | -|expand | Function | | false | Function that takes a state as argument and returns the data for the following states | +| Key | Type | Defaults | Optional | Description | +| ------ | -------- | -------- | -------- | ------------------------------------------------------------------------------------- | +| name | String | 'expand' | true | Action name | +| cost | Int | 1 | true | Action cost | +| expand | Function | | false | Function that takes a state as argument and returns the data for the following states | Examples + ```js const actions = [ { expand: (state) => { - return { position: state.position + 1 }; - } + return { position: state.position + 1 } + }, }, { expand: (state) => { - return Promise.resolve({ position: state.position - 1}); - } + return Promise.resolve({ position: state.position - 1 }) + }, }, { name: 'myFancyAction', cost: 80, expand: (state) => { - if(state.height > 90) { - return; + if (state.height > 90) { + return } - return { height: state.height + 10 }; - } + return { height: state.height + 10 } + }, }, -]; - +] ``` + > **NOTE:** Remember to return `undefined` or simply `return` on forbidden actions to avoid generating infinite states ### Hash Used to establish when two newly generated states are essentially the same , for example, in a maze going to the left one cell and the returning to the same represents essentially the same state and we do not want that -| Type | Defaults | Optional | Description | -|--------|----------|----------|-----------------------------| -| String\|Function| | false | Field or function to determine the equality of two states | +| Type | Defaults | Optional | Description | +| ---------------- | -------- | -------- | --------------------------------------------------------- | +| String\|Function | | false | Field or function to determine the equality of two states | Examples ```js -const hash = 'position'; +const hash = 'position' -const hash = 'height'; +const hash = 'height' -const hash = (state) => `${state.x},${state.y}`; +const hash = (state) => `${state.x},${state.y}` ``` ### Heuristics @@ -211,24 +217,24 @@ Used to give a state a 'sense of approaching to the goal'. It is a function that returns decreasing values as the closer we get to the goal. It depends purely on your search state representation and you must ensure to provide logical and decreasing values the closer the solution is -| Type | Defaults | Optional | Description | -|--------|----------|----------|-----------------------------| -|Function| | true | Function to determine the 'proximity' to a goal | +| Type | Defaults | Optional | Description | +| -------- | -------- | -------- | ----------------------------------------------- | +| Function | | true | Function to determine the 'proximity' to a goal | Examples ```js const heuristics = ({ x, y }) => { // Euclidean distance - return Math.sqrt(((x - goal.x) ** 2) + ((y - goal.y) ** 2)); -}; + return Math.sqrt((x - goal.x) ** 2 + (y - goal.y) ** 2) +} const heuristics = ({ x, y }) => { // Manhattan distance - return Math.abs((x - goal.x) + (y - goal.y)); -}; + return Math.abs(x - goal.x + (y - goal.y)) +} -const heuristics = state => 90 - state.position; +const heuristics = (state) => 90 - state.position ``` A better documentation its on the way. @@ -238,20 +244,21 @@ A better documentation its on the way. Available engines ### Uninformed + | Engine | API | -|---------------|----------| +| ------------- | -------- | | Breadth-first | bfs | | Dijkstra | dijkstra | | Random-search | random | | Depth-first | dfs | ### Informed -| Engine | API | -|-------------------|----------| -| Best-first search | bestfs | -| A* | astar | -| Weighted-A* | weightedastar | +| Engine | API | +| ----------------- | ------------- | +| Best-first search | bestfs | +| A\* | astar | +| Weighted-A\* | weightedastar | ## Promises diff --git a/bun.lock b/bun.lock new file mode 100644 index 0000000..7286422 --- /dev/null +++ b/bun.lock @@ -0,0 +1,616 @@ +{ + "lockfileVersion": 1, + "configVersion": 1, + "workspaces": { + "": { + "name": "maizal", + "devDependencies": { + "@eslint/js": "^9.39.1", + "@eslint/markdown": "^7.5.1", + "@tsconfig/strictest": "^2.0.8", + "@typescript-eslint/eslint-plugin": "^8.46.4", + "@typescript-eslint/parser": "^8.46.4", + "@vitest/coverage-v8": "^4.0.9", + "eslint": "^9.39.1", + "globals": "^16.5.0", + "prettier": "^3.6.2", + "typescript": "^5.9.3", + "typescript-eslint": "^8.46.4", + "vitest": "^4.0.9", + }, + }, + }, + "packages": { + "@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="], + + "@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="], + + "@babel/parser": ["@babel/parser@7.28.5", "", { "dependencies": { "@babel/types": "^7.28.5" }, "bin": "./bin/babel-parser.js" }, "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ=="], + + "@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="], + + "@bcoe/v8-coverage": ["@bcoe/v8-coverage@1.0.2", "", {}, "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA=="], + + "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.12", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA=="], + + "@esbuild/android-arm": ["@esbuild/android-arm@0.25.12", "", { "os": "android", "cpu": "arm" }, "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg=="], + + "@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.12", "", { "os": "android", "cpu": "arm64" }, "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg=="], + + "@esbuild/android-x64": ["@esbuild/android-x64@0.25.12", "", { "os": "android", "cpu": "x64" }, "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg=="], + + "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.12", "", { "os": "darwin", "cpu": "arm64" }, "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg=="], + + "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.12", "", { "os": "darwin", "cpu": "x64" }, "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA=="], + + "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.12", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg=="], + + "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.12", "", { "os": "freebsd", "cpu": "x64" }, "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ=="], + + "@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.12", "", { "os": "linux", "cpu": "arm" }, "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw=="], + + "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.12", "", { "os": "linux", "cpu": "arm64" }, "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ=="], + + "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.12", "", { "os": "linux", "cpu": "ia32" }, "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA=="], + + "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng=="], + + "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw=="], + + "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.12", "", { "os": "linux", "cpu": "ppc64" }, "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA=="], + + "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w=="], + + "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.12", "", { "os": "linux", "cpu": "s390x" }, "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg=="], + + "@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.12", "", { "os": "linux", "cpu": "x64" }, "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw=="], + + "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.12", "", { "os": "none", "cpu": "arm64" }, "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg=="], + + "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.12", "", { "os": "none", "cpu": "x64" }, "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ=="], + + "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.12", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A=="], + + "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.12", "", { "os": "openbsd", "cpu": "x64" }, "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw=="], + + "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.25.12", "", { "os": "none", "cpu": "arm64" }, "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg=="], + + "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.12", "", { "os": "sunos", "cpu": "x64" }, "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w=="], + + "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.12", "", { "os": "win32", "cpu": "arm64" }, "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg=="], + + "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.12", "", { "os": "win32", "cpu": "ia32" }, "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ=="], + + "@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.12", "", { "os": "win32", "cpu": "x64" }, "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA=="], + + "@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.9.0", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g=="], + + "@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.2", "", {}, "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew=="], + + "@eslint/config-array": ["@eslint/config-array@0.21.1", "", { "dependencies": { "@eslint/object-schema": "^2.1.7", "debug": "^4.3.1", "minimatch": "^3.1.2" } }, "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA=="], + + "@eslint/config-helpers": ["@eslint/config-helpers@0.4.2", "", { "dependencies": { "@eslint/core": "^0.17.0" } }, "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw=="], + + "@eslint/core": ["@eslint/core@0.17.0", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ=="], + + "@eslint/eslintrc": ["@eslint/eslintrc@3.3.1", "", { "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", "espree": "^10.0.1", "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.0", "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" } }, "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ=="], + + "@eslint/js": ["@eslint/js@9.39.1", "", {}, "sha512-S26Stp4zCy88tH94QbBv3XCuzRQiZ9yXofEILmglYTh/Ug/a9/umqvgFtYBAo3Lp0nsI/5/qH1CCrbdK3AP1Tw=="], + + "@eslint/markdown": ["@eslint/markdown@7.5.1", "", { "dependencies": { "@eslint/core": "^0.17.0", "@eslint/plugin-kit": "^0.4.1", "github-slugger": "^2.0.0", "mdast-util-from-markdown": "^2.0.2", "mdast-util-frontmatter": "^2.0.1", "mdast-util-gfm": "^3.1.0", "micromark-extension-frontmatter": "^2.0.0", "micromark-extension-gfm": "^3.0.0", "micromark-util-normalize-identifier": "^2.0.1" } }, "sha512-R8uZemG9dKTbru/DQRPblbJyXpObwKzo8rv1KYGGuPUPtjM4LXBYM9q5CIZAComzZupws3tWbDwam5AFpPLyJQ=="], + + "@eslint/object-schema": ["@eslint/object-schema@2.1.7", "", {}, "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA=="], + + "@eslint/plugin-kit": ["@eslint/plugin-kit@0.4.1", "", { "dependencies": { "@eslint/core": "^0.17.0", "levn": "^0.4.1" } }, "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA=="], + + "@humanfs/core": ["@humanfs/core@0.19.1", "", {}, "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA=="], + + "@humanfs/node": ["@humanfs/node@0.16.7", "", { "dependencies": { "@humanfs/core": "^0.19.1", "@humanwhocodes/retry": "^0.4.0" } }, "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ=="], + + "@humanwhocodes/module-importer": ["@humanwhocodes/module-importer@1.0.1", "", {}, "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA=="], + + "@humanwhocodes/retry": ["@humanwhocodes/retry@0.4.3", "", {}, "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ=="], + + "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="], + + "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="], + + "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="], + + "@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="], + + "@nodelib/fs.stat": ["@nodelib/fs.stat@2.0.5", "", {}, "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="], + + "@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="], + + "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.53.2", "", { "os": "android", "cpu": "arm" }, "sha512-yDPzwsgiFO26RJA4nZo8I+xqzh7sJTZIWQOxn+/XOdPE31lAvLIYCKqjV+lNH/vxE2L2iH3plKxDCRK6i+CwhA=="], + + "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.53.2", "", { "os": "android", "cpu": "arm64" }, "sha512-k8FontTxIE7b0/OGKeSN5B6j25EuppBcWM33Z19JoVT7UTXFSo3D9CdU39wGTeb29NO3XxpMNauh09B+Ibw+9g=="], + + "@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.53.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-A6s4gJpomNBtJ2yioj8bflM2oogDwzUiMl2yNJ2v9E7++sHrSrsQ29fOfn5DM/iCzpWcebNYEdXpaK4tr2RhfQ=="], + + "@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.53.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-e6XqVmXlHrBlG56obu9gDRPW3O3hLxpwHpLsBJvuI8qqnsrtSZ9ERoWUXtPOkY8c78WghyPHZdmPhHLWNdAGEw=="], + + "@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.53.2", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-v0E9lJW8VsrwPux5Qe5CwmH/CF/2mQs6xU1MF3nmUxmZUCHazCjLgYvToOk+YuuUqLQBio1qkkREhxhc656ViA=="], + + "@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.53.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-ClAmAPx3ZCHtp6ysl4XEhWU69GUB1D+s7G9YjHGhIGCSrsg00nEGRRZHmINYxkdoJehde8VIsDC5t9C0gb6yqA=="], + + "@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.53.2", "", { "os": "linux", "cpu": "arm" }, "sha512-EPlb95nUsz6Dd9Qy13fI5kUPXNSljaG9FiJ4YUGU1O/Q77i5DYFW5KR8g1OzTcdZUqQQ1KdDqsTohdFVwCwjqg=="], + + "@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.53.2", "", { "os": "linux", "cpu": "arm" }, "sha512-BOmnVW+khAUX+YZvNfa0tGTEMVVEerOxN0pDk2E6N6DsEIa2Ctj48FOMfNDdrwinocKaC7YXUZ1pHlKpnkja/Q=="], + + "@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.53.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-Xt2byDZ+6OVNuREgBXr4+CZDJtrVso5woFtpKdGPhpTPHcNG7D8YXeQzpNbFRxzTVqJf7kvPMCub/pcGUWgBjA=="], + + "@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.53.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-+LdZSldy/I9N8+klim/Y1HsKbJ3BbInHav5qE9Iy77dtHC/pibw1SR/fXlWyAk0ThnpRKoODwnAuSjqxFRDHUQ=="], + + "@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.53.2", "", { "os": "linux", "cpu": "none" }, "sha512-8ms8sjmyc1jWJS6WdNSA23rEfdjWB30LH8Wqj0Cqvv7qSHnvw6kgMMXRdop6hkmGPlyYBdRPkjJnj3KCUHV/uQ=="], + + "@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.53.2", "", { "os": "linux", "cpu": "ppc64" }, "sha512-3HRQLUQbpBDMmzoxPJYd3W6vrVHOo2cVW8RUo87Xz0JPJcBLBr5kZ1pGcQAhdZgX9VV7NbGNipah1omKKe23/g=="], + + "@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.53.2", "", { "os": "linux", "cpu": "none" }, "sha512-fMjKi+ojnmIvhk34gZP94vjogXNNUKMEYs+EDaB/5TG/wUkoeua7p7VCHnE6T2Tx+iaghAqQX8teQzcvrYpaQA=="], + + "@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.53.2", "", { "os": "linux", "cpu": "none" }, "sha512-XuGFGU+VwUUV5kLvoAdi0Wz5Xbh2SrjIxCtZj6Wq8MDp4bflb/+ThZsVxokM7n0pcbkEr2h5/pzqzDYI7cCgLQ=="], + + "@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.53.2", "", { "os": "linux", "cpu": "s390x" }, "sha512-w6yjZF0P+NGzWR3AXWX9zc0DNEGdtvykB03uhonSHMRa+oWA6novflo2WaJr6JZakG2ucsyb+rvhrKac6NIy+w=="], + + "@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.53.2", "", { "os": "linux", "cpu": "x64" }, "sha512-yo8d6tdfdeBArzC7T/PnHd7OypfI9cbuZzPnzLJIyKYFhAQ8SvlkKtKBMbXDxe1h03Rcr7u++nFS7tqXz87Gtw=="], + + "@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.53.2", "", { "os": "linux", "cpu": "x64" }, "sha512-ah59c1YkCxKExPP8O9PwOvs+XRLKwh/mV+3YdKqQ5AMQ0r4M4ZDuOrpWkUaqO7fzAHdINzV9tEVu8vNw48z0lA=="], + + "@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.53.2", "", { "os": "none", "cpu": "arm64" }, "sha512-4VEd19Wmhr+Zy7hbUsFZ6YXEiP48hE//KPLCSVNY5RMGX2/7HZ+QkN55a3atM1C/BZCGIgqN+xrVgtdak2S9+A=="], + + "@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.53.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-IlbHFYc/pQCgew/d5fslcy1KEaYVCJ44G8pajugd8VoOEI8ODhtb/j8XMhLpwHCMB3yk2J07ctup10gpw2nyMA=="], + + "@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.53.2", "", { "os": "win32", "cpu": "ia32" }, "sha512-lNlPEGgdUfSzdCWU176ku/dQRnA7W+Gp8d+cWv73jYrb8uT7HTVVxq62DUYxjbaByuf1Yk0RIIAbDzp+CnOTFg=="], + + "@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.53.2", "", { "os": "win32", "cpu": "x64" }, "sha512-S6YojNVrHybQis2lYov1sd+uj7K0Q05NxHcGktuMMdIQ2VixGwAfbJ23NnlvvVV1bdpR2m5MsNBViHJKcA4ADw=="], + + "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.53.2", "", { "os": "win32", "cpu": "x64" }, "sha512-k+/Rkcyx//P6fetPoLMb8pBeqJBNGx81uuf7iljX9++yNBVRDQgD04L+SVXmXmh5ZP4/WOp4mWF0kmi06PW2tA=="], + + "@standard-schema/spec": ["@standard-schema/spec@1.0.0", "", {}, "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA=="], + + "@tsconfig/strictest": ["@tsconfig/strictest@2.0.8", "", {}, "sha512-XnQ7vNz5HRN0r88GYf1J9JJjqtZPiHt2woGJOo2dYqyHGGcd6OLGqSlBB6p1j9mpzja6Oe5BoPqWmeDx6X9rLw=="], + + "@types/chai": ["@types/chai@5.2.3", "", { "dependencies": { "@types/deep-eql": "*", "assertion-error": "^2.0.1" } }, "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA=="], + + "@types/debug": ["@types/debug@4.1.12", "", { "dependencies": { "@types/ms": "*" } }, "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ=="], + + "@types/deep-eql": ["@types/deep-eql@4.0.2", "", {}, "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw=="], + + "@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="], + + "@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="], + + "@types/mdast": ["@types/mdast@4.0.4", "", { "dependencies": { "@types/unist": "*" } }, "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA=="], + + "@types/ms": ["@types/ms@2.1.0", "", {}, "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA=="], + + "@types/node": ["@types/node@24.10.1", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ=="], + + "@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="], + + "@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.46.4", "", { "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "8.46.4", "@typescript-eslint/type-utils": "8.46.4", "@typescript-eslint/utils": "8.46.4", "@typescript-eslint/visitor-keys": "8.46.4", "graphemer": "^1.4.0", "ignore": "^7.0.0", "natural-compare": "^1.4.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.46.4", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-R48VhmTJqplNyDxCyqqVkFSZIx1qX6PzwqgcXn1olLrzxcSBDlOsbtcnQuQhNtnNiJ4Xe5gREI1foajYaYU2Vg=="], + + "@typescript-eslint/parser": ["@typescript-eslint/parser@8.46.4", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.46.4", "@typescript-eslint/types": "8.46.4", "@typescript-eslint/typescript-estree": "8.46.4", "@typescript-eslint/visitor-keys": "8.46.4", "debug": "^4.3.4" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-tK3GPFWbirvNgsNKto+UmB/cRtn6TZfyw0D6IKrW55n6Vbs7KJoZtI//kpTKzE/DUmmnAFD8/Ca46s7Obs92/w=="], + + "@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.46.4", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.46.4", "@typescript-eslint/types": "^8.46.4", "debug": "^4.3.4" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-nPiRSKuvtTN+no/2N1kt2tUh/HoFzeEgOm9fQ6XQk4/ApGqjx0zFIIaLJ6wooR1HIoozvj2j6vTi/1fgAz7UYQ=="], + + "@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.46.4", "", { "dependencies": { "@typescript-eslint/types": "8.46.4", "@typescript-eslint/visitor-keys": "8.46.4" } }, "sha512-tMDbLGXb1wC+McN1M6QeDx7P7c0UWO5z9CXqp7J8E+xGcJuUuevWKxuG8j41FoweS3+L41SkyKKkia16jpX7CA=="], + + "@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.46.4", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-+/XqaZPIAk6Cjg7NWgSGe27X4zMGqrFqZ8atJsX3CWxH/jACqWnrWI68h7nHQld0y+k9eTTjb9r+KU4twLoo9A=="], + + "@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.46.4", "", { "dependencies": { "@typescript-eslint/types": "8.46.4", "@typescript-eslint/typescript-estree": "8.46.4", "@typescript-eslint/utils": "8.46.4", "debug": "^4.3.4", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-V4QC8h3fdT5Wro6vANk6eojqfbv5bpwHuMsBcJUJkqs2z5XnYhJzyz9Y02eUmF9u3PgXEUiOt4w4KHR3P+z0PQ=="], + + "@typescript-eslint/types": ["@typescript-eslint/types@8.46.4", "", {}, "sha512-USjyxm3gQEePdUwJBFjjGNG18xY9A2grDVGuk7/9AkjIF1L+ZrVnwR5VAU5JXtUnBL/Nwt3H31KlRDaksnM7/w=="], + + "@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.46.4", "", { "dependencies": { "@typescript-eslint/project-service": "8.46.4", "@typescript-eslint/tsconfig-utils": "8.46.4", "@typescript-eslint/types": "8.46.4", "@typescript-eslint/visitor-keys": "8.46.4", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-7oV2qEOr1d4NWNmpXLR35LvCfOkTNymY9oyW+lUHkmCno7aOmIf/hMaydnJBUTBMRCOGZh8YjkFOc8dadEoNGA=="], + + "@typescript-eslint/utils": ["@typescript-eslint/utils@8.46.4", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", "@typescript-eslint/scope-manager": "8.46.4", "@typescript-eslint/types": "8.46.4", "@typescript-eslint/typescript-estree": "8.46.4" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-AbSv11fklGXV6T28dp2Me04Uw90R2iJ30g2bgLz529Koehrmkbs1r7paFqr1vPCZi7hHwYxYtxfyQMRC8QaVSg=="], + + "@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.46.4", "", { "dependencies": { "@typescript-eslint/types": "8.46.4", "eslint-visitor-keys": "^4.2.1" } }, "sha512-/++5CYLQqsO9HFGLI7APrxBJYo+5OCMpViuhV8q5/Qa3o5mMrF//eQHks+PXcsAVaLdn817fMuS7zqoXNNZGaw=="], + + "@vitest/coverage-v8": ["@vitest/coverage-v8@4.0.9", "", { "dependencies": { "@bcoe/v8-coverage": "^1.0.2", "@vitest/utils": "4.0.9", "ast-v8-to-istanbul": "^0.3.8", "debug": "^4.4.3", "istanbul-lib-coverage": "^3.2.2", "istanbul-lib-report": "^3.0.1", "istanbul-lib-source-maps": "^5.0.6", "istanbul-reports": "^3.2.0", "magicast": "^0.5.1", "std-env": "^3.10.0", "tinyrainbow": "^3.0.3" }, "peerDependencies": { "@vitest/browser": "4.0.9", "vitest": "4.0.9" }, "optionalPeers": ["@vitest/browser"] }, "sha512-70oyhP+Q0HlWBIeGSP74YBw5KSjYhNgSCQjvmuQFciMqnyF36WL2cIkcT7XD85G4JPmBQitEMUsx+XMFv2AzQA=="], + + "@vitest/expect": ["@vitest/expect@4.0.9", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "@types/chai": "^5.2.2", "@vitest/spy": "4.0.9", "@vitest/utils": "4.0.9", "chai": "^6.2.0", "tinyrainbow": "^3.0.3" } }, "sha512-C2vyXf5/Jfj1vl4DQYxjib3jzyuswMi/KHHVN2z+H4v16hdJ7jMZ0OGe3uOVIt6LyJsAofDdaJNIFEpQcrSTFw=="], + + "@vitest/mocker": ["@vitest/mocker@4.0.9", "", { "dependencies": { "@vitest/spy": "4.0.9", "estree-walker": "^3.0.3", "magic-string": "^0.30.21" }, "peerDependencies": { "msw": "^2.4.9", "vite": "^6.0.0 || ^7.0.0-0" }, "optionalPeers": ["msw", "vite"] }, "sha512-PUyaowQFHW+9FKb4dsvvBM4o025rWMlEDXdWRxIOilGaHREYTi5Q2Rt9VCgXgPy/hHZu1LeuXtrA/GdzOatP2g=="], + + "@vitest/pretty-format": ["@vitest/pretty-format@4.0.9", "", { "dependencies": { "tinyrainbow": "^3.0.3" } }, "sha512-Hor0IBTwEi/uZqB7pvGepyElaM8J75pYjrrqbC8ZYMB9/4n5QA63KC15xhT+sqHpdGWfdnPo96E8lQUxs2YzSQ=="], + + "@vitest/runner": ["@vitest/runner@4.0.9", "", { "dependencies": { "@vitest/utils": "4.0.9", "pathe": "^2.0.3" } }, "sha512-aF77tsXdEvIJRkj9uJZnHtovsVIx22Ambft9HudC+XuG/on1NY/bf5dlDti1N35eJT+QZLb4RF/5dTIG18s98w=="], + + "@vitest/snapshot": ["@vitest/snapshot@4.0.9", "", { "dependencies": { "@vitest/pretty-format": "4.0.9", "magic-string": "^0.30.21", "pathe": "^2.0.3" } }, "sha512-r1qR4oYstPbnOjg0Vgd3E8ADJbi4ditCzqr+Z9foUrRhIy778BleNyZMeAJ2EjV+r4ASAaDsdciC9ryMy8xMMg=="], + + "@vitest/spy": ["@vitest/spy@4.0.9", "", {}, "sha512-J9Ttsq0hDXmxmT8CUOWUr1cqqAj2FJRGTdyEjSR+NjoOGKEqkEWj+09yC0HhI8t1W6t4Ctqawl1onHgipJve1A=="], + + "@vitest/utils": ["@vitest/utils@4.0.9", "", { "dependencies": { "@vitest/pretty-format": "4.0.9", "tinyrainbow": "^3.0.3" } }, "sha512-cEol6ygTzY4rUPvNZM19sDf7zGa35IYTm9wfzkHoT/f5jX10IOY7QleWSOh5T0e3I3WVozwK5Asom79qW8DiuQ=="], + + "acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="], + + "acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="], + + "ajv": ["ajv@6.12.6", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g=="], + + "ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], + + "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], + + "assertion-error": ["assertion-error@2.0.1", "", {}, "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA=="], + + "ast-v8-to-istanbul": ["ast-v8-to-istanbul@0.3.8", "", { "dependencies": { "@jridgewell/trace-mapping": "^0.3.31", "estree-walker": "^3.0.3", "js-tokens": "^9.0.1" } }, "sha512-szgSZqUxI5T8mLKvS7WTjF9is+MVbOeLADU73IseOcrqhxr/VAvy6wfoVE39KnKzA7JRhjF5eUagNlHwvZPlKQ=="], + + "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + + "brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="], + + "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="], + + "buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="], + + "callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="], + + "ccount": ["ccount@2.0.1", "", {}, "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg=="], + + "chai": ["chai@6.2.1", "", {}, "sha512-p4Z49OGG5W/WBCPSS/dH3jQ73kD6tiMmUM+bckNK6Jr5JHMG3k9bg/BvKR8lKmtVBKmOiuVaV2ws8s9oSbwysg=="], + + "chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], + + "character-entities": ["character-entities@2.0.2", "", {}, "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ=="], + + "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], + + "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], + + "commander": ["commander@2.20.3", "", {}, "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="], + + "concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="], + + "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], + + "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + + "decode-named-character-reference": ["decode-named-character-reference@1.2.0", "", { "dependencies": { "character-entities": "^2.0.0" } }, "sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q=="], + + "deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="], + + "dequal": ["dequal@2.0.3", "", {}, "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA=="], + + "devlop": ["devlop@1.1.0", "", { "dependencies": { "dequal": "^2.0.0" } }, "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA=="], + + "es-module-lexer": ["es-module-lexer@1.7.0", "", {}, "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA=="], + + "esbuild": ["esbuild@0.25.12", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.12", "@esbuild/android-arm": "0.25.12", "@esbuild/android-arm64": "0.25.12", "@esbuild/android-x64": "0.25.12", "@esbuild/darwin-arm64": "0.25.12", "@esbuild/darwin-x64": "0.25.12", "@esbuild/freebsd-arm64": "0.25.12", "@esbuild/freebsd-x64": "0.25.12", "@esbuild/linux-arm": "0.25.12", "@esbuild/linux-arm64": "0.25.12", "@esbuild/linux-ia32": "0.25.12", "@esbuild/linux-loong64": "0.25.12", "@esbuild/linux-mips64el": "0.25.12", "@esbuild/linux-ppc64": "0.25.12", "@esbuild/linux-riscv64": "0.25.12", "@esbuild/linux-s390x": "0.25.12", "@esbuild/linux-x64": "0.25.12", "@esbuild/netbsd-arm64": "0.25.12", "@esbuild/netbsd-x64": "0.25.12", "@esbuild/openbsd-arm64": "0.25.12", "@esbuild/openbsd-x64": "0.25.12", "@esbuild/openharmony-arm64": "0.25.12", "@esbuild/sunos-x64": "0.25.12", "@esbuild/win32-arm64": "0.25.12", "@esbuild/win32-ia32": "0.25.12", "@esbuild/win32-x64": "0.25.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg=="], + + "escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="], + + "eslint": ["eslint@9.39.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.21.1", "@eslint/config-helpers": "^0.4.2", "@eslint/core": "^0.17.0", "@eslint/eslintrc": "^3.3.1", "@eslint/js": "9.39.1", "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.4.0", "eslint-visitor-keys": "^4.2.1", "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g=="], + + "eslint-scope": ["eslint-scope@8.4.0", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg=="], + + "eslint-visitor-keys": ["eslint-visitor-keys@4.2.1", "", {}, "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ=="], + + "espree": ["espree@10.4.0", "", { "dependencies": { "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^4.2.1" } }, "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ=="], + + "esquery": ["esquery@1.6.0", "", { "dependencies": { "estraverse": "^5.1.0" } }, "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg=="], + + "esrecurse": ["esrecurse@4.3.0", "", { "dependencies": { "estraverse": "^5.2.0" } }, "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag=="], + + "estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="], + + "estree-walker": ["estree-walker@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="], + + "esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="], + + "expect-type": ["expect-type@1.2.2", "", {}, "sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA=="], + + "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], + + "fast-glob": ["fast-glob@3.3.3", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="], + + "fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="], + + "fast-levenshtein": ["fast-levenshtein@2.0.6", "", {}, "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw=="], + + "fastq": ["fastq@1.19.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ=="], + + "fault": ["fault@2.0.1", "", { "dependencies": { "format": "^0.2.0" } }, "sha512-WtySTkS4OKev5JtpHXnib4Gxiurzh5NCGvWrFaZ34m6JehfTUhKZvn9njTfw48t6JumVQOmrKqpmGcdwxnhqBQ=="], + + "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], + + "file-entry-cache": ["file-entry-cache@8.0.0", "", { "dependencies": { "flat-cache": "^4.0.0" } }, "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ=="], + + "fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="], + + "find-up": ["find-up@5.0.0", "", { "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" } }, "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng=="], + + "flat-cache": ["flat-cache@4.0.1", "", { "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.4" } }, "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw=="], + + "flatted": ["flatted@3.3.3", "", {}, "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg=="], + + "format": ["format@0.2.2", "", {}, "sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww=="], + + "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], + + "github-slugger": ["github-slugger@2.0.0", "", {}, "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw=="], + + "glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="], + + "globals": ["globals@16.5.0", "", {}, "sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ=="], + + "graphemer": ["graphemer@1.4.0", "", {}, "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag=="], + + "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], + + "html-escaper": ["html-escaper@2.0.2", "", {}, "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg=="], + + "ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="], + + "import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="], + + "imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="], + + "is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="], + + "is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="], + + "is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="], + + "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], + + "istanbul-lib-coverage": ["istanbul-lib-coverage@3.2.2", "", {}, "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg=="], + + "istanbul-lib-report": ["istanbul-lib-report@3.0.1", "", { "dependencies": { "istanbul-lib-coverage": "^3.0.0", "make-dir": "^4.0.0", "supports-color": "^7.1.0" } }, "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw=="], + + "istanbul-lib-source-maps": ["istanbul-lib-source-maps@5.0.6", "", { "dependencies": { "@jridgewell/trace-mapping": "^0.3.23", "debug": "^4.1.1", "istanbul-lib-coverage": "^3.0.0" } }, "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A=="], + + "istanbul-reports": ["istanbul-reports@3.2.0", "", { "dependencies": { "html-escaper": "^2.0.0", "istanbul-lib-report": "^3.0.0" } }, "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA=="], + + "jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="], + + "js-tokens": ["js-tokens@9.0.1", "", {}, "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ=="], + + "js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="], + + "json-buffer": ["json-buffer@3.0.1", "", {}, "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="], + + "json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="], + + "json-stable-stringify-without-jsonify": ["json-stable-stringify-without-jsonify@1.0.1", "", {}, "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw=="], + + "keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="], + + "levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="], + + "locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="], + + "lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="], + + "longest-streak": ["longest-streak@3.1.0", "", {}, "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g=="], + + "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], + + "magicast": ["magicast@0.5.1", "", { "dependencies": { "@babel/parser": "^7.28.5", "@babel/types": "^7.28.5", "source-map-js": "^1.2.1" } }, "sha512-xrHS24IxaLrvuo613F719wvOIv9xPHFWQHuvGUBmPnCA/3MQxKI3b+r7n1jAoDHmsbC5bRhTZYR77invLAxVnw=="], + + "make-dir": ["make-dir@4.0.0", "", { "dependencies": { "semver": "^7.5.3" } }, "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw=="], + + "markdown-table": ["markdown-table@3.0.4", "", {}, "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw=="], + + "mdast-util-find-and-replace": ["mdast-util-find-and-replace@3.0.2", "", { "dependencies": { "@types/mdast": "^4.0.0", "escape-string-regexp": "^5.0.0", "unist-util-is": "^6.0.0", "unist-util-visit-parents": "^6.0.0" } }, "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg=="], + + "mdast-util-from-markdown": ["mdast-util-from-markdown@2.0.2", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "mdast-util-to-string": "^4.0.0", "micromark": "^4.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-decode-string": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA=="], + + "mdast-util-frontmatter": ["mdast-util-frontmatter@2.0.1", "", { "dependencies": { "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "escape-string-regexp": "^5.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0", "micromark-extension-frontmatter": "^2.0.0" } }, "sha512-LRqI9+wdgC25P0URIJY9vwocIzCcksduHQ9OF2joxQoyTNVduwLAFUzjoopuRJbJAReaKrNQKAZKL3uCMugWJA=="], + + "mdast-util-gfm": ["mdast-util-gfm@3.1.0", "", { "dependencies": { "mdast-util-from-markdown": "^2.0.0", "mdast-util-gfm-autolink-literal": "^2.0.0", "mdast-util-gfm-footnote": "^2.0.0", "mdast-util-gfm-strikethrough": "^2.0.0", "mdast-util-gfm-table": "^2.0.0", "mdast-util-gfm-task-list-item": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ=="], + + "mdast-util-gfm-autolink-literal": ["mdast-util-gfm-autolink-literal@2.0.1", "", { "dependencies": { "@types/mdast": "^4.0.0", "ccount": "^2.0.0", "devlop": "^1.0.0", "mdast-util-find-and-replace": "^3.0.0", "micromark-util-character": "^2.0.0" } }, "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ=="], + + "mdast-util-gfm-footnote": ["mdast-util-gfm-footnote@2.1.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "devlop": "^1.1.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0" } }, "sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ=="], + + "mdast-util-gfm-strikethrough": ["mdast-util-gfm-strikethrough@2.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg=="], + + "mdast-util-gfm-table": ["mdast-util-gfm-table@2.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "markdown-table": "^3.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg=="], + + "mdast-util-gfm-task-list-item": ["mdast-util-gfm-task-list-item@2.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ=="], + + "mdast-util-phrasing": ["mdast-util-phrasing@4.1.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "unist-util-is": "^6.0.0" } }, "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w=="], + + "mdast-util-to-markdown": ["mdast-util-to-markdown@2.1.2", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "longest-streak": "^3.0.0", "mdast-util-phrasing": "^4.0.0", "mdast-util-to-string": "^4.0.0", "micromark-util-classify-character": "^2.0.0", "micromark-util-decode-string": "^2.0.0", "unist-util-visit": "^5.0.0", "zwitch": "^2.0.0" } }, "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA=="], + + "mdast-util-to-string": ["mdast-util-to-string@4.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0" } }, "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg=="], + + "merge2": ["merge2@1.4.1", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="], + + "micromark": ["micromark@4.0.2", "", { "dependencies": { "@types/debug": "^4.0.0", "debug": "^4.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "micromark-core-commonmark": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-combine-extensions": "^2.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-encode": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-sanitize-uri": "^2.0.0", "micromark-util-subtokenize": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA=="], + + "micromark-core-commonmark": ["micromark-core-commonmark@2.0.3", "", { "dependencies": { "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "micromark-factory-destination": "^2.0.0", "micromark-factory-label": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-factory-title": "^2.0.0", "micromark-factory-whitespace": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-classify-character": "^2.0.0", "micromark-util-html-tag-name": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-subtokenize": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg=="], + + "micromark-extension-frontmatter": ["micromark-extension-frontmatter@2.0.0", "", { "dependencies": { "fault": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-C4AkuM3dA58cgZha7zVnuVxBhDsbttIMiytjgsM2XbHAB2faRVaHRle40558FBN+DJcrLNCoqG5mlrpdU4cRtg=="], + + "micromark-extension-gfm": ["micromark-extension-gfm@3.0.0", "", { "dependencies": { "micromark-extension-gfm-autolink-literal": "^2.0.0", "micromark-extension-gfm-footnote": "^2.0.0", "micromark-extension-gfm-strikethrough": "^2.0.0", "micromark-extension-gfm-table": "^2.0.0", "micromark-extension-gfm-tagfilter": "^2.0.0", "micromark-extension-gfm-task-list-item": "^2.0.0", "micromark-util-combine-extensions": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w=="], + + "micromark-extension-gfm-autolink-literal": ["micromark-extension-gfm-autolink-literal@2.1.0", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-sanitize-uri": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw=="], + + "micromark-extension-gfm-footnote": ["micromark-extension-gfm-footnote@2.1.0", "", { "dependencies": { "devlop": "^1.0.0", "micromark-core-commonmark": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-sanitize-uri": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw=="], + + "micromark-extension-gfm-strikethrough": ["micromark-extension-gfm-strikethrough@2.1.0", "", { "dependencies": { "devlop": "^1.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-classify-character": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw=="], + + "micromark-extension-gfm-table": ["micromark-extension-gfm-table@2.1.1", "", { "dependencies": { "devlop": "^1.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg=="], + + "micromark-extension-gfm-tagfilter": ["micromark-extension-gfm-tagfilter@2.0.0", "", { "dependencies": { "micromark-util-types": "^2.0.0" } }, "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg=="], + + "micromark-extension-gfm-task-list-item": ["micromark-extension-gfm-task-list-item@2.1.0", "", { "dependencies": { "devlop": "^1.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw=="], + + "micromark-factory-destination": ["micromark-factory-destination@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA=="], + + "micromark-factory-label": ["micromark-factory-label@2.0.1", "", { "dependencies": { "devlop": "^1.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg=="], + + "micromark-factory-space": ["micromark-factory-space@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg=="], + + "micromark-factory-title": ["micromark-factory-title@2.0.1", "", { "dependencies": { "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw=="], + + "micromark-factory-whitespace": ["micromark-factory-whitespace@2.0.1", "", { "dependencies": { "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ=="], + + "micromark-util-character": ["micromark-util-character@2.1.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q=="], + + "micromark-util-chunked": ["micromark-util-chunked@2.0.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA=="], + + "micromark-util-classify-character": ["micromark-util-classify-character@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q=="], + + "micromark-util-combine-extensions": ["micromark-util-combine-extensions@2.0.1", "", { "dependencies": { "micromark-util-chunked": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg=="], + + "micromark-util-decode-numeric-character-reference": ["micromark-util-decode-numeric-character-reference@2.0.2", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw=="], + + "micromark-util-decode-string": ["micromark-util-decode-string@2.0.1", "", { "dependencies": { "decode-named-character-reference": "^1.0.0", "micromark-util-character": "^2.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-symbol": "^2.0.0" } }, "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ=="], + + "micromark-util-encode": ["micromark-util-encode@2.0.1", "", {}, "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw=="], + + "micromark-util-html-tag-name": ["micromark-util-html-tag-name@2.0.1", "", {}, "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA=="], + + "micromark-util-normalize-identifier": ["micromark-util-normalize-identifier@2.0.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q=="], + + "micromark-util-resolve-all": ["micromark-util-resolve-all@2.0.1", "", { "dependencies": { "micromark-util-types": "^2.0.0" } }, "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg=="], + + "micromark-util-sanitize-uri": ["micromark-util-sanitize-uri@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-encode": "^2.0.0", "micromark-util-symbol": "^2.0.0" } }, "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ=="], + + "micromark-util-subtokenize": ["micromark-util-subtokenize@2.1.0", "", { "dependencies": { "devlop": "^1.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA=="], + + "micromark-util-symbol": ["micromark-util-symbol@2.0.1", "", {}, "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q=="], + + "micromark-util-types": ["micromark-util-types@2.0.2", "", {}, "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA=="], + + "micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="], + + "minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="], + + "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + + "nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], + + "natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="], + + "optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="], + + "p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="], + + "p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="], + + "parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="], + + "path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="], + + "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], + + "pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], + + "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], + + "picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="], + + "postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="], + + "prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="], + + "prettier": ["prettier@3.6.2", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ=="], + + "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="], + + "queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="], + + "resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="], + + "reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="], + + "rollup": ["rollup@4.53.2", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.53.2", "@rollup/rollup-android-arm64": "4.53.2", "@rollup/rollup-darwin-arm64": "4.53.2", "@rollup/rollup-darwin-x64": "4.53.2", "@rollup/rollup-freebsd-arm64": "4.53.2", "@rollup/rollup-freebsd-x64": "4.53.2", "@rollup/rollup-linux-arm-gnueabihf": "4.53.2", "@rollup/rollup-linux-arm-musleabihf": "4.53.2", "@rollup/rollup-linux-arm64-gnu": "4.53.2", "@rollup/rollup-linux-arm64-musl": "4.53.2", "@rollup/rollup-linux-loong64-gnu": "4.53.2", "@rollup/rollup-linux-ppc64-gnu": "4.53.2", "@rollup/rollup-linux-riscv64-gnu": "4.53.2", "@rollup/rollup-linux-riscv64-musl": "4.53.2", "@rollup/rollup-linux-s390x-gnu": "4.53.2", "@rollup/rollup-linux-x64-gnu": "4.53.2", "@rollup/rollup-linux-x64-musl": "4.53.2", "@rollup/rollup-openharmony-arm64": "4.53.2", "@rollup/rollup-win32-arm64-msvc": "4.53.2", "@rollup/rollup-win32-ia32-msvc": "4.53.2", "@rollup/rollup-win32-x64-gnu": "4.53.2", "@rollup/rollup-win32-x64-msvc": "4.53.2", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-MHngMYwGJVi6Fmnk6ISmnk7JAHRNF0UkuucA0CUW3N3a4KnONPEZz+vUanQP/ZC/iY1Qkf3bwPWzyY84wEks1g=="], + + "run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="], + + "semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], + + "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], + + "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], + + "siginfo": ["siginfo@2.0.0", "", {}, "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g=="], + + "source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], + + "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], + + "source-map-support": ["source-map-support@0.5.21", "", { "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" } }, "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w=="], + + "stackback": ["stackback@0.0.2", "", {}, "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw=="], + + "std-env": ["std-env@3.10.0", "", {}, "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg=="], + + "strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="], + + "supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], + + "terser": ["terser@3.17.0", "", { "dependencies": { "commander": "^2.19.0", "source-map": "~0.6.1", "source-map-support": "~0.5.10" }, "bin": { "terser": "bin/uglifyjs" } }, "sha512-/FQzzPJmCpjAH9Xvk2paiWrFq+5M6aVOf+2KRbwhByISDX/EujxsK+BAvrhb6H+2rtrLCHK9N01wO014vrIwVQ=="], + + "tinybench": ["tinybench@2.9.0", "", {}, "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg=="], + + "tinyexec": ["tinyexec@0.3.2", "", {}, "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA=="], + + "tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="], + + "tinyrainbow": ["tinyrainbow@3.0.3", "", {}, "sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q=="], + + "to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="], + + "ts-api-utils": ["ts-api-utils@2.1.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ=="], + + "type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="], + + "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], + + "typescript-eslint": ["typescript-eslint@8.46.4", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.46.4", "@typescript-eslint/parser": "8.46.4", "@typescript-eslint/typescript-estree": "8.46.4", "@typescript-eslint/utils": "8.46.4" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-KALyxkpYV5Ix7UhvjTwJXZv76VWsHG+NjNlt/z+a17SOQSiOcBdUXdbJdyXi7RPxrBFECtFOiPwUJQusJuCqrg=="], + + "undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="], + + "unist-util-is": ["unist-util-is@6.0.1", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g=="], + + "unist-util-stringify-position": ["unist-util-stringify-position@4.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ=="], + + "unist-util-visit": ["unist-util-visit@5.0.0", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0", "unist-util-visit-parents": "^6.0.0" } }, "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg=="], + + "unist-util-visit-parents": ["unist-util-visit-parents@6.0.2", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0" } }, "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ=="], + + "uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="], + + "vite": ["vite@7.2.2", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-BxAKBWmIbrDgrokdGZH1IgkIk/5mMHDreLDmCJ0qpyJaAteP8NvMhkwr/ZCQNqNH97bw/dANTE9PDzqwJghfMQ=="], + + "vitest": ["vitest@4.0.9", "", { "dependencies": { "@vitest/expect": "4.0.9", "@vitest/mocker": "4.0.9", "@vitest/pretty-format": "4.0.9", "@vitest/runner": "4.0.9", "@vitest/snapshot": "4.0.9", "@vitest/spy": "4.0.9", "@vitest/utils": "4.0.9", "debug": "^4.4.3", "es-module-lexer": "^1.7.0", "expect-type": "^1.2.2", "magic-string": "^0.30.21", "pathe": "^2.0.3", "picomatch": "^4.0.3", "std-env": "^3.10.0", "tinybench": "^2.9.0", "tinyexec": "^0.3.2", "tinyglobby": "^0.2.15", "tinyrainbow": "^3.0.3", "vite": "^6.0.0 || ^7.0.0", "why-is-node-running": "^2.3.0" }, "peerDependencies": { "@edge-runtime/vm": "*", "@types/debug": "^4.1.12", "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", "@vitest/browser-playwright": "4.0.9", "@vitest/browser-preview": "4.0.9", "@vitest/browser-webdriverio": "4.0.9", "@vitest/ui": "4.0.9", "happy-dom": "*", "jsdom": "*" }, "optionalPeers": ["@edge-runtime/vm", "@types/debug", "@types/node", "@vitest/browser-playwright", "@vitest/browser-preview", "@vitest/browser-webdriverio", "@vitest/ui", "happy-dom", "jsdom"], "bin": { "vitest": "vitest.mjs" } }, "sha512-E0Ja2AX4th+CG33yAFRC+d1wFx2pzU5r6HtG6LiPSE04flaE0qB6YyjSw9ZcpJAtVPfsvZGtJlKWZpuW7EHRxg=="], + + "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], + + "why-is-node-running": ["why-is-node-running@2.3.0", "", { "dependencies": { "siginfo": "^2.0.0", "stackback": "0.0.2" }, "bin": { "why-is-node-running": "cli.js" } }, "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w=="], + + "word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="], + + "yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="], + + "zwitch": ["zwitch@2.0.4", "", {}, "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A=="], + + "@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="], + + "@eslint/eslintrc/globals": ["globals@14.0.0", "", {}, "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ=="], + + "@eslint/eslintrc/ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], + + "@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], + + "eslint/ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], + + "fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], + + "mdast-util-find-and-replace/escape-string-regexp": ["escape-string-regexp@5.0.0", "", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="], + + "mdast-util-frontmatter/escape-string-regexp": ["escape-string-regexp@5.0.0", "", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="], + + "micromatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], + + "@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], + } +} diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 0000000..ce75430 --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,21 @@ +import js from '@eslint/js' +import globals from 'globals' +import tseslint from 'typescript-eslint' +import markdown from '@eslint/markdown' +import { defineConfig } from 'eslint/config' + +export default defineConfig([ + { + files: ['**/*.{js,mjs,cjs,ts,mts,cts}'], + plugins: { js }, + extends: ['js/recommended'], + languageOptions: { globals: { ...globals.browser, ...globals.node } }, + }, + tseslint.configs.recommended, + { + files: ['**/*.md'], + plugins: { markdown }, + language: 'markdown/gfm', + extends: ['markdown/recommended'], + }, +]) diff --git a/package.json b/package.json index cd44c58..d13bb2b 100644 --- a/package.json +++ b/package.json @@ -2,70 +2,37 @@ "name": "maizal", "version": "0.4.0", "description": "Search algorithm framework to find and expand trees.", - "main": "lib/maizal.js", "scripts": { - "build": "npm run lint && npm run transpile && npm run bundle && size-limit", - "transpile": "babel --delete-dir-on-start -d lib/ src/ ", - "bundle": "rollup -c", - "preversion": "npm test", - "postversion": "git push && git push --tags", - "prepublishOnly": "npm run build", - "lint": "eslint ./src", - "test": "nyc mocha test/unit/*/*.js --reporter spec", - "clean": "rm -r ./lib && rm -r ./dist" + "lint": "eslint ./src ./tests", + "lint:fix": "eslint ./src ./tests", + "format": "prettier --check .", + "format:fix": "prettier --write .", + "test": "vitest --run", + "test:watch": "vitest --watch" }, - "files": [ - "src", - "lib", - "dist/*.js" - ], "repository": { "type": "git", "url": "https://github.com/benjides/Maizal" }, "keywords": [ - "js" + "js", + "ts" ], "author": "Benjamin Hernandez", "license": "MIT", - "dependencies": {}, "devDependencies": { - "await-to-js": "^2.1.1", - "babel-cli": "^6.26.0", - "babel-preset-env": "^1.7.0", - "babel-preset-minify": "^0.5.0", - "chai": "^4.2.0", - "eslint": "^5.8.0", - "eslint-config-airbnb-base": "^13.1.0", - "eslint-plugin-import": "^2.14.0", - "husky": "^1.3.1", - "mocha": "^5.2.0", - "nyc": "^13.1.0", - "rollup": "^0.67.3", - "rollup-plugin-commonjs": "^9.2.0", - "rollup-plugin-terser": "^3.0.0", - "sinon": "^7.1.1", - "size-limit": "^0.21.0" - }, - "nyc": { - "reporter": [ - "lcov", - "text" - ] + "@eslint/js": "^9.39.1", + "@eslint/markdown": "^7.5.1", + "@tsconfig/strictest": "^2.0.8", + "@typescript-eslint/eslint-plugin": "^8.46.4", + "@typescript-eslint/parser": "^8.46.4", + "@vitest/coverage-v8": "^4.0.9", + "eslint": "^9.39.1", + "globals": "^16.5.0", + "prettier": "^3.6.2", + "typescript": "^5.9.3", + "typescript-eslint": "^8.46.4", + "vitest": "^4.0.9" }, - "size-limit": [ - { - "path": "lib/maizal.js", - "limit": "2 KB" - }, - { - "path": "dist/maizal.min.js", - "limit": "2 KB" - } - ], - "husky": { - "hooks": { - "pre-commit": "npm test" - } - } + "type": "module" } diff --git a/rollup.config.js b/rollup.config.js deleted file mode 100644 index a52d164..0000000 --- a/rollup.config.js +++ /dev/null @@ -1,12 +0,0 @@ -const commonjs = require('rollup-plugin-commonjs'); -const { terser } = require('rollup-plugin-terser'); - -module.exports = { - input: 'lib/maizal.js', - output: { - file: './dist/maizal.min.js', - name: 'maizal', - format: 'umd', - }, - plugins: [commonjs(), terser()], -}; diff --git a/src/PriorityQueue.ts b/src/PriorityQueue.ts new file mode 100644 index 0000000..dfa8692 --- /dev/null +++ b/src/PriorityQueue.ts @@ -0,0 +1,19 @@ +export type PriorityQueue = Node[] + +export type Node = { + priority: number + data: T +} + +export const priorityQueue = + (priority: (node: T) => number) => + (data: T[]) => + data + .map((node: T) => ({ + priority: priority(node), + data: node, + })) + .sort((a: Node, b: Node) => a.priority - b.priority) + +export const values = (priorityQueue: PriorityQueue) => + priorityQueue.map((node: Node) => node.data) diff --git a/src/core/Maizal.js b/src/core/Maizal.js deleted file mode 100644 index f3238ee..0000000 --- a/src/core/Maizal.js +++ /dev/null @@ -1,23 +0,0 @@ -const init = require('./init/init'); -const solve = require('./solver/solver'); - -/** - * Creates a Maizal instance - * - * @constructor - * @param {Object} config object containing the necesarry configuation to start a search - */ -function Maizal(config) { - init(this, config); -} - -/** - * Solves a search problem - * - * @returns {Promise} Promise including the search results - */ -Maizal.prototype.solve = function () { - return solve(this); -}; - -module.exports = Maizal; diff --git a/src/core/init/init.js b/src/core/init/init.js deleted file mode 100644 index 2690596..0000000 --- a/src/core/init/init.js +++ /dev/null @@ -1,99 +0,0 @@ -const PriorityQueue = require('../../util/PriorityQueue'); -const HashSet = require('../../util/HashSet'); - -/** - * Defines the poll of yet unexplored states - * - * @param {HashSet} goals HashSet containing the user defined goals - * @param {Object} config User defined search configuration - * @returns {PriorityQueue} - */ -function initPoll(goals, config) { - if (!config.initial) { - throw new Error('Missing initial state'); - } - - const evaluationFn = state => config.engine.evaluationFn(state, config); - - const poll = new PriorityQueue(evaluationFn); - poll.enqueue({ - final: goals.has(config.initial), - data: config.initial, - depth: 0, - cost: 0, - }); - return poll; -} - -/** - * Defines a closed set to introduce already visited states - * - * @param {String|Int|Function} hash HashFn to determine whether two states are essentially the same - * @returns {HashSet} - */ -function initClosed({ hash }) { - return new HashSet(hash); -} - -/** - * Defines the user created actions - * - * @param {Array|Object} actions Array or Object containing the user defined actions - * @returns {Array} Actions array - */ -function initActions({ actions }) { - return (Array.isArray(actions) ? actions : [actions]).map((action) => { - if (!action.expand) throw new Error(`Every action needs to have its expand functionno on action ${JSON.stringify(action)}`); - return { - name: action.name || 'expand', - cost: action.cost || 1, - expand: action.expand, - }; - }); -} - -/** - * Defines the user created goal states - * - * @param {Array|Object} goals Array containing the goal states - * @param {String|Int|Function} hash HashFn to quickly recognize a goal state when is created - * @returns {HashSet} - */ -function initGoals({ goals, hash }) { - if (!hash) { - throw new Error('Missing Hash function'); - } - if (!goals) { - throw new Error('Missing Goals states'); - } - const g = new HashSet(hash); - g.add(goals); - return g; -} - -function setup(config) { - if (!config.engine.requires) { - return; - } - Object.keys(config.engine.requires).forEach((requirement) => { - if (typeof config[requirement] !== config.engine.requires[requirement]) { - throw new Error(`The search requires the field ${requirement} to be a ${config.engine.requires[requirement]}`); - } - }); -} - -/** - * Sets the configuartion for the Maizal search - * - * @param {Maizal} maizal Maizal instance - * @param {Object} config - */ -function init(maizal, config) { - setup(config); - maizal.goals = initGoals(config); - maizal.poll = initPoll(maizal.goals, config); - maizal.closed = initClosed(config); - maizal.actions = initActions(config); -} - -module.exports = init; diff --git a/src/core/node.js b/src/core/node.js deleted file mode 100644 index 98f0317..0000000 --- a/src/core/node.js +++ /dev/null @@ -1,20 +0,0 @@ -/** - * Creates a new node - * - * @param {Object} parent state - * @param {Object} data information about the state - * @param {boolean} final - * @param {Object} action taken action - */ -const node = ({ - parent, data, final, action, -}) => ({ - parent, - data, - final, - action: action.name, - cost: parent.cost + action.cost, - depth: parent.depth + 1, -}); - -module.exports = node; diff --git a/src/core/solver/rebuild.js b/src/core/solver/rebuild.js deleted file mode 100644 index 217f453..0000000 --- a/src/core/solver/rebuild.js +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Rebuild the tree once the solution is found - * - * @param {Object} goal state of the search - * @returns {Array} ordered array containing the path from the initial state to the goal one. - */ -function rebuild(goal) { - const tree = []; - let state = goal; - while (state) { - const { - parent, data, action, cost, depth, - } = state; - const s = { - data, cost, depth, - }; - if (action) { - s.action = action; - } - tree.unshift(s); - state = parent; - } - return tree; -} - -module.exports = rebuild; diff --git a/src/core/solver/solver.js b/src/core/solver/solver.js deleted file mode 100644 index d8b0da7..0000000 --- a/src/core/solver/solver.js +++ /dev/null @@ -1,29 +0,0 @@ -const node = require('../node'); -const rebuild = require('./rebuild'); -const stats = require('./stats'); - -/** - * Expands recursively the most promising node acording to the priorityQueue - * - * @param {Maizal} maizal instance - * @returns {Promise} the results of the search - */ -function expand(maizal) { - const state = maizal.poll.dequeue(); - if (!state) { - return Promise.reject(new Error('The goal states could not be reached. Are you sure they are accesible?')); - } - if (state.final) { - return Promise.resolve().then(() => ({ stats: stats(maizal, state), solution: rebuild(state) })); - } - const promises = maizal.actions.map(action => new Promise(resolve => resolve(action.expand(state.data))) - .then(states => (Array.isArray(states) ? states : [states])) - .then(states => states.filter(data => (data && !maizal.closed.has(data)))) - .then(states => states.map(data => node({ - parent: state, data, final: maizal.goals.has(data), action, - }))) - .then(states => maizal.poll.enqueue(states))); - return Promise.all(promises).then(() => maizal.closed.add(state.data)).then(() => expand(maizal)); -} - -module.exports = maizal => expand(maizal); diff --git a/src/core/solver/stats.js b/src/core/solver/stats.js deleted file mode 100644 index be82365..0000000 --- a/src/core/solver/stats.js +++ /dev/null @@ -1,16 +0,0 @@ -/** - * Get statistics from the search - * - * @param {Maizal} maizal solved instance - * @param {State} state reached goal state - * @returns {Object} - */ -function stats(maizal, state) { - return { - cost: state.cost, - depth: state.depth, - nodes: maizal.closed.size(), - }; -} - -module.exports = stats; diff --git a/src/engine/engines.js b/src/engine/engines.js deleted file mode 100644 index cbfdb86..0000000 --- a/src/engine/engines.js +++ /dev/null @@ -1,14 +0,0 @@ -// Uninformed -const bfs = require('./uninformed/bfs'); -const dijkstra = require('./uninformed/dijkstra'); -const random = require('./uninformed/random'); -const dfs = require('./uninformed/dfs'); - -// Informed -const bestfs = require('./informed/bestfs'); -const astar = require('./informed/astar'); -const weightedastar = require('./informed/weightedastar'); - -module.exports = { - bfs, dijkstra, random, dfs, bestfs, astar, weightedastar, -}; diff --git a/src/engine/informed/astar.js b/src/engine/informed/astar.js deleted file mode 100644 index b9285e3..0000000 --- a/src/engine/informed/astar.js +++ /dev/null @@ -1,14 +0,0 @@ -/** - * A* search - * - * Expands based on cost and heuristics - * - * completness: (depending on the heuristics) - * admissability: (depending on the heuristics) - */ -module.exports = { - requires: { - heuristics: 'function', - }, - evaluationFn: ({ cost, data }, { heuristics }) => cost + heuristics(data), -}; diff --git a/src/engine/informed/bestfs.js b/src/engine/informed/bestfs.js deleted file mode 100644 index d23db8d..0000000 --- a/src/engine/informed/bestfs.js +++ /dev/null @@ -1,15 +0,0 @@ -/** - * Best-first search - * - * Expands based purely on heuristics - * note that the 1 is added to ensure we arte not getting 0 values on reaching goal states. - * - * completness: (depending on the heuristics) - * admissability: (depending on the heuristics) - */ -module.exports = { - requires: { - heuristics: 'function', - }, - evaluationFn: ({ data }, { heuristics }) => 1 + heuristics(data), -}; diff --git a/src/engine/informed/weightedastar.js b/src/engine/informed/weightedastar.js deleted file mode 100644 index e6bdaf4..0000000 --- a/src/engine/informed/weightedastar.js +++ /dev/null @@ -1,15 +0,0 @@ -/** - * Weighted A* search - * - * Expands based on cost and heuristics giving a weight value to the heuristics - * - * completness: (depending on the heuristics) - * admissability: (depending on the heuristics) - */ -module.exports = { - requires: { - heuristics: 'function', - epsilon: 'number', - }, - evaluationFn: ({ cost, data }, { heuristics, epsilon }) => cost + epsilon * heuristics(data), -}; diff --git a/src/engine/uninformed/bfs.js b/src/engine/uninformed/bfs.js deleted file mode 100644 index f65194c..0000000 --- a/src/engine/uninformed/bfs.js +++ /dev/null @@ -1,11 +0,0 @@ -/** - * Breadth-first search - * - * Expands nodes at depth "d" before going to the next level - * - * completness: true - * admissability: true - */ -module.exports = { - evaluationFn: ({ depth }) => depth, -}; diff --git a/src/engine/uninformed/dfs.js b/src/engine/uninformed/dfs.js deleted file mode 100644 index 82db494..0000000 --- a/src/engine/uninformed/dfs.js +++ /dev/null @@ -1,11 +0,0 @@ -/** - * Depth-first Search - * - * Expands nodes on depth - * - * completness: true - * admissability: false - */ -module.exports = { - evaluationFn: ({ depth }) => -depth, -}; diff --git a/src/engine/uninformed/dijkstra.js b/src/engine/uninformed/dijkstra.js deleted file mode 100644 index 5142f82..0000000 --- a/src/engine/uninformed/dijkstra.js +++ /dev/null @@ -1,11 +0,0 @@ -/** - * Dijkstra search - * - * Espands nodes with least cost - * - * completness: true - * admissability: false - */ -module.exports = { - evaluationFn: ({ cost }) => cost, -}; diff --git a/src/engine/uninformed/random.js b/src/engine/uninformed/random.js deleted file mode 100644 index c34d81e..0000000 --- a/src/engine/uninformed/random.js +++ /dev/null @@ -1,11 +0,0 @@ -/** - * Random search - * - * Expands nodes randomly - * - * completness: true - * admissability: false - */ -module.exports = { - evaluationFn: () => Math.round(Math.random()) + 1, -}; diff --git a/src/maizal.js b/src/maizal.js deleted file mode 100644 index 48cf074..0000000 --- a/src/maizal.js +++ /dev/null @@ -1,20 +0,0 @@ -const Maizal = require('./core/Maizal'); -const engines = require('./engine/engines'); - -const maizal = {}; - -/** - * Expose maizal.engine - */ -Object.keys(engines).forEach((engine) => { - maizal[engine] = (config) => { - config.engine = engines[engine]; - try { - return new Maizal(config).solve(); - } catch (error) { - return Promise.reject(error); - } - }; -}); - -module.exports = maizal; diff --git a/src/util/HashSet.js b/src/util/HashSet.js deleted file mode 100644 index ced8ac5..0000000 --- a/src/util/HashSet.js +++ /dev/null @@ -1,133 +0,0 @@ -/** - * Creates a new instance of a HashSet - * - * @param {Function|String} hashFunction to check whether two elements are essentially the same - * @constructor - */ -function HashSet(hashFunction) { - if (hashFunction === undefined) { - throw new TypeError('hashFunction is required argument'); - } - if (typeof hashFunction === 'string') { - this.hashFn = function (item) { - if (item[hashFunction] !== undefined) { - return item[hashFunction]; - } - throw new Error(`Undefined was returned by hash function on object ${JSON.stringify(item)}`); - }; - } else { - this.hashFn = hashFunction; - } - this.hashObject = {}; - this.length = 0; -} - -/** - * Inserts the element into the set if it is not already present. - * - * @param {*} value Value to add - * @returns {boolean} True if the item was added - */ -HashSet.prototype.add = function (value) { - if (Array.isArray(value)) { - return this.addArray(value); - } - const r = !this.has(value); - if (r) { - this.hashObject[this.hashFn(value)] = value; - this.length += 1; - } - return r; -}; - -/** - * Adds an Array of elements - * - * @param {array} values Array of values to be added - * @return {array} Boolean array specifying if the value was added - */ -HashSet.prototype.addArray = function (values) { - return values.map(value => this.add(value)); -}; - -/** - * Get the key/hashes of the set. - * - * @returns {Array} Array of keys - */ -HashSet.prototype.keys = function () { - return Object.keys(this.hashObject); -}; - -/** - * Clear the set and keep the Hash Function - */ -HashSet.prototype.clear = function () { - this.hashObject = {}; - this.length = 0; -}; - -/** - * Get the value given a hash - * - * @param {String} hash - * @returns {*} stored value or undefined if the hash did not found anything - */ -HashSet.prototype.getValue = function (hash) { - return this.hashObject[hash]; -}; - -/** - * Get the hash given a value - * - * @param {*} value - * @returns {string} stored hash or undefined if the hash did not found anything - */ -HashSet.prototype.getHash = function (value) { - return this.hashFn(value); -}; - -/** - * Removes a value from the set given its hash or value - * - * @param {*} valueOrHash either hash or an object - * @returns {boolean} whether it removed the item - */ -HashSet.prototype.remove = function (valueOrHash) { - if (this.has(valueOrHash)) { - if (typeof valueOrHash === 'object') { - delete this.hashObject[this.hashFn(valueOrHash)]; - } else { - delete this.hashObject[valueOrHash]; - } - this.length -= 1; - return true; - } - return false; -}; - -/** - * Check if the set contains the given value or hash - * - * @param {*} valueOrHash either hash or an object - * @returns {boolean} - */ -HashSet.prototype.has = function (valueOrHash) { - if (typeof valueOrHash === 'object') { - return this.hashObject[this.hashFn(valueOrHash)] !== undefined; - } - return this.hashObject[valueOrHash] !== undefined; -}; - -/** - * Size of the Set - * - * @returns {number} - */ -HashSet.prototype.size = function () { - return this.length; -}; - -HashSet.prototype.hashes = HashSet.prototype.keys; - -module.exports = HashSet; diff --git a/src/util/PriorityQueue.js b/src/util/PriorityQueue.js deleted file mode 100644 index 38a680b..0000000 --- a/src/util/PriorityQueue.js +++ /dev/null @@ -1,87 +0,0 @@ -/** - * Creates a new PriorityQueue - * - * @param {Function} priorityFunction Function over a element to state its integer priority - * @constructor - */ -function PriorityQueue(priorityFunction) { - if (!priorityFunction) { - throw new Error('The priority function for an element must be provided'); - } - this.priorityFn = (current, insert) => { - if (!priorityFunction(insert)) { - return undefined; - } - return priorityFunction(current) - priorityFunction(insert) > 0; - }; - this.queue = []; -} - -/** - * Enqueues an element - * - * @param {*} element Enqueues an element in the proper position using the comparator function - * @return {number} Position where the element was placed - */ -PriorityQueue.prototype.enqueue = function (element) { - if (Array.isArray(element)) { - return this.enqueueArray(element); - } - for (let i = 0; i < this.queue.length; i += 1) { - switch (this.priorityFn(this.queue[i], element)) { - case true: - this.queue.splice(i, 0, element); - return i; - case undefined: - return undefined; - // no default - } - } - return this.queue.push(element) - 1; -}; - -/** - * Enqueues an Array of elements - * - * @param {array} array Array of elements to be enqueued - * @return {array} Position array where the elements were placed - */ -PriorityQueue.prototype.enqueueArray = function (elements) { - return elements.map(element => this.enqueue(element)); -}; - -/** - * Dequeues an element - * - * @return {*} Gets the first element of the queue and removes from the list - */ -PriorityQueue.prototype.dequeue = function () { - return this.queue.shift(); -}; - -/** - * Clears the queue - */ -PriorityQueue.prototype.clear = function () { - this.queue = []; -}; - -/** - * Gets the queue size - * - * @returns {number} - */ -PriorityQueue.prototype.size = function () { - return this.queue.length; -}; - -/** - * Checks if the queue is empty - * - * @returns {boolean} - */ -PriorityQueue.prototype.empty = function () { - return this.size() === 0; -}; - -module.exports = PriorityQueue; diff --git a/test/corridor.js b/test/corridor.js deleted file mode 100644 index db35e5c..0000000 --- a/test/corridor.js +++ /dev/null @@ -1,26 +0,0 @@ -module.exports = { - initial: { - position: 1, - }, - goals: { - position: 4, - }, - actions: [ - { - name: 'right', - expand: (state) => { - if (state.position + 1 > 4) return undefined; - return { position: state.position + 1 }; - }, - }, - { - name: 'left', - expand: (state) => { - if (state.position - 1 < 0) return undefined; - return { position: state.position - 1 }; - }, - }, - ], - hash: 'position', - heuristics: state => 4 - state.position, -}; diff --git a/test/months.js b/test/months.js deleted file mode 100644 index 5db8c49..0000000 --- a/test/months.js +++ /dev/null @@ -1,50 +0,0 @@ -module.exports = [ - { - index: 1, - name: 'January', - }, - { - index: 2, - name: 'February', - }, - { - index: 3, - name: 'March', - }, - { - index: 4, - name: 'April', - }, - { - index: 5, - name: 'May', - }, - { - index: 6, - name: 'June', - }, - { - index: 7, - name: 'July', - }, - { - index: 8, - name: 'August', - }, - { - index: 9, - name: 'September', - }, - { - index: 10, - name: 'October', - }, - { - index: 11, - name: 'November', - }, - { - index: 12, - name: 'December', - }, -]; diff --git a/test/unit/core/core.js b/test/unit/core/core.js deleted file mode 100644 index 183a7dd..0000000 --- a/test/unit/core/core.js +++ /dev/null @@ -1,284 +0,0 @@ -const { expect } = require('chai'); -const sinon = require('sinon'); -const { to } = require('await-to-js'); -const maizal = require('../../../src/maizal'); -const Maizal = require('../../../src/core/Maizal'); -const { bfs } = require('../../../src/engine/engines'); -const corridor = require('../../corridor'); - -afterEach(() => { - sinon.restore(); -}); - -let c = {}; - -// All the core functionalities of maizal goes here - -// For specific engine funcitonalty go to the dedicated test file - -describe('Simple core base corridor search testing', () => { - it('A simple search should reach the last position in the corridor', async () => { - const res = await maizal.bfs(corridor); - it('The search should provide the solution found and the stats', () => { - expect(res).to.have.all.keys('solution', 'stats'); - }); - const { solution, stats } = res; - it('The solution must be and ordered array with the found nodes from initial to last', () => { - expect(solution).to.be.an('array'); - }); - it('Checks of the initial state of the solution', () => { - const initial = solution[0]; - expect(initial.action).to.be.an('undefined'); - expect(initial.depth).to.be(0); - expect(initial.cost).to.be(0); - }); - it('Checks of the goal state found', () => { - const final = solution.pop(); - expect(final.position).to.eq(corridor.goals.position); - expect(final.action).to.eq('right'); - expect(final.cost).to.eq(3); - expect(final.depth).to.eq(3); - }); - it('The statistics must be an object with search results info', () => { - expect(stats).to.be.an('object'); - expect(stats.nodes).to.eq(4); - expect(stats.depth).to.eq(3); - expect(stats.nodes).to.eq(3); - }); - }); - it('If the final states are not reachable the search should be rejected', async () => { - c = Object.assign({}, corridor); - c.actions = { - expand: ({ position }) => Promise.resolve({ position }), - }; - const [err, data] = await to(maizal.bfs(c)); - expect(err).to.be.an('error'); - expect(data).to.be.an('undefined'); - }); - it('Chain two maizal searches', async () => { - await maizal.bfs(corridor); - c = Object.assign({}, corridor); - c.goals = { - position: 2, - }; - const { solution, stats } = await maizal.bfs(c); - expect(stats.nodes).to.eq(1); - expect(solution.pop().data.position).to.eq(c.goals.position); - }); - - describe('Checking hashing function call assertions', () => { - it('Checking called assertions on the closed set hash function', async () => { - c = Object.assign({}, corridor); - c.engine = bfs; - // Remove limitations to better calculate function callings - c.actions = [ - { - name: 'right', - expand: ({ position }) => ({ position: position + 1 }), - }, - { - name: 'left', - expand: ({ position }) => ({ position: position - 1 }), - }, - ]; - const m = new Maizal(c); - const hash = sinon.spy(m.closed, 'hashFn'); - const { stats } = await m.solve(); - - // The hash function is called twice per closed state (has, add) - // The hash function is called once per iteration (state closed) per action (each action generates a new state) - sinon.assert.callCount(hash, stats.nodes * 2 * c.actions.length); - }); - it('The hash function is called for each new state to determine if it is a goal state including the initial', async () => { - c = Object.assign({}, corridor); - c.engine = bfs; - const m = new Maizal(c); - const hash = sinon.spy(m.goals, 'hashFn'); - await m.solve(); - // Being BFS every single state must be checked and therefore called - sinon.assert.callCount(hash, 4); - }); - }); - - describe('Checking priority function call assertions', () => { - it('The evaluation function is called only once to reach the next cell', async () => { - c = Object.assign({}, corridor); - c.engine = bfs; - c.goals = { - position: 2, - }; - const m = new Maizal(c); - const priority = sinon.spy(m.poll, 'priorityFn'); - await m.solve(); - // This is called once, the first element is inserted at the beggining (no call) and the next is placed after (1 call) - sinon.assert.calledOnce(priority); - }); - it('The evaluation function is called twice to reach the end of the corridor', async () => { - c = Object.assign({}, corridor); - c.engine = bfs; - const m = new Maizal(c); - const priority = sinon.spy(m.poll, 'priorityFn'); - await m.solve(); - // This is tricky. As elements are removed there is no need to call the priority function for every new state - sinon.assert.callCount(priority, 2); - }); - }); - - describe('Initial set testing', () => { - it('If the initial is the goal should not expand any action', async () => { - c = Object.assign({}, corridor); - c.goals = { position: 1 }; - const { solution, stats } = await maizal.bfs(c); - expect(solution).to.have.length(1); - expect(solution[0].data.position).to.eq(c.goals.position); - expect(stats.nodes).to.eq(0); - expect(stats.depth).to.eq(0); - expect(stats.cost).to.eq(0); - }); - it('A search cannot start without an initial state', async () => { - c = Object.assign({}, corridor); - delete c.initial; - const [err, data] = await to(maizal.bfs(c)); - expect(err).to.be.an('error'); - expect(data).to.be.an('undefined'); - }); - it('A search cannot start with an empty initial state', async () => { - c = Object.assign({}, corridor); - c.initial = {}; - const [err, data] = await to(maizal.bfs(c)); - expect(err).to.be.an('error'); - expect(data).to.be.an('undefined'); - }); - }); - describe('Action testing', () => { - it('Ensure the actions expand functions are called', async () => { - c = Object.assign({}, corridor); - c.engine = bfs; - const right = sinon.spy(c.actions[0], 'expand'); - const left = sinon.spy(c.actions[1], 'expand'); - const m = new Maizal(c); - await m.solve(); - sinon.assert.called(right); - sinon.assert.called(left); - }); - it('The expand actions should be called in order', async () => { - c = Object.assign({}, corridor); - c.engine = bfs; - const right = sinon.spy(c.actions[0], 'expand'); - const left = sinon.spy(c.actions[1], 'expand'); - const m = new Maizal(c); - await m.solve(); - sinon.assert.callOrder(right, left); - }); - it('Promise actions are supported and the robot shall reach the end of the corridor', async () => { - c = Object.assign({}, corridor); - c.actions = { - expand: ({ position }) => Promise.resolve({ position: position + 1 }), - }; - const { solution, stats } = await maizal.bfs(c); - const final = solution.pop(); - expect(final.data.position).to.eq(c.goals.position); - expect(final.action).to.eq('expand'); - expect(stats.nodes).to.eq(3); - }); - it('A search with no actions should be rejected', async () => { - c = Object.assign({}, corridor); - c.actions = {}; - const [err, data] = await to(maizal.bfs(c)); - expect(err).to.be.an('error'); - expect(data).to.be.an('undefined'); - }); - it('An empty expand function should be rejected', async () => { - c = Object.assign({}, corridor); - c.actions = { - name: 'IdoNotHaveExpand', - }; - const [err, data] = await to(maizal.bfs(c)); - expect(err).to.be.an('error'); - expect(data).to.be.an('undefined'); - }); - it('Newly generated states should be able to be hashed', async () => { - c = Object.assign({}, corridor); - c.actions = { - expand: () => ({ notPosition: 3 }), - }; - const [err, data] = await to(maizal.bfs(c)); - expect(err).to.be.an('error'); - expect(data).to.be.an('undefined'); - }); - it('Actions returning several states are allowed', async () => { - c = Object.assign({}, corridor); - c.actions = { - expand: ({ position }) => { - if (position > 5 || position < 0) { - return undefined; - } - return [{ position: position + 1 }, { position: position - 1 }]; - }, - }; - const { solution, stats } = await maizal.bfs(c); - expect(solution.pop().data.position).to.be.eq(c.goals.position); - expect(solution.pop().action).to.be.eq('expand'); - expect(stats.nodes).to.be.eq(5); - }); - }); - describe('Hash testing', () => { - it('Function hashes are allowed', async () => { - c = Object.assign({}, corridor); - c.hash = ({ position }) => position + 15; - const hash = sinon.spy(c, 'hash'); - const { solution } = await maizal.bfs(c); - expect(solution).to.have.length(4); - sinon.assert.called(hash); - }); - it('A search with no hash should be rejected', async () => { - c = Object.assign({}, corridor); - delete c.hash; - const [err, data] = await to(maizal.bfs(c)); - expect(err).to.be.an('error'); - expect(data).to.be.an('undefined'); - }); - it('A non existing hash should be rejected', async () => { - c = Object.assign({}, corridor); - c.hash = 'IdoNotExist'; - const [err, data] = await to(maizal.bfs(c)); - expect(err).to.be.an('error'); - expect(data).to.be.an('undefined'); - }); - }); - describe('Goals testing', () => { - it('A search with no goals should be rejected', async () => { - c = Object.assign({}, corridor); - delete c.goals; - const [err, data] = await to(maizal.bfs(c)); - expect(err).to.be.an('error'); - expect(data).to.be.an('undefined'); - }); - }); - - describe('Informed search testing', () => { - it('Perform a simple Best-first search to reach the end of the corridor', async () => { - c = Object.assign({}, corridor); - const heuristics = sinon.spy(c, 'heuristics'); - const { solution } = await maizal.bestfs(c); - expect(solution).to.have.length(4); - sinon.assert.called(heuristics); - }); - - it('Ensure the requirements of the search are fullfilled', async () => { - c = Object.assign({}, corridor); - delete c.heuristics; - const [err, data] = await to(maizal.bestfs(c)); - expect(err).to.be.an('error'); - expect(data).to.be.an('undefined'); - }); - - it('Ensure the requirements match the expected datatype', async () => { - c = Object.assign({}, corridor); - c.heuristics = 'iAmNotAFunction'; - const [err, data] = await to(maizal.bestfs(c)); - expect(err).to.be.an('error'); - expect(data).to.be.an('undefined'); - }); - }); -}); diff --git a/test/unit/informed/astar.js b/test/unit/informed/astar.js deleted file mode 100644 index b55ce72..0000000 --- a/test/unit/informed/astar.js +++ /dev/null @@ -1,69 +0,0 @@ -const { expect } = require('chai'); -const maizal = require('../../../src/maizal'); -const corridor = require('../../corridor'); - -let c = {}; - -describe('Simple A* corridor search', () => { - it('A simple search should reach the last position in the corridor', async () => { - const { solution } = await maizal.astar(corridor); - expect(solution.pop().data.position).to.eq(corridor.goals.position); - }); - it('A better solution must prioritize over a worse one if the heuristics point to them', async () => { - c = Object.assign({}, corridor); - c.goals = [{ - position: 2, - }, { - position: 4, - }]; - const { solution } = await maizal.astar(c); - expect(solution.pop().data.position).to.eq(c.goals[0].position); - }); - it('The solution is found at the least cost even if the heuristics point otherwise (incresing cost weight)', async () => { - c = Object.assign({}, corridor); - c.actions = [ - { - name: 'right', - cost: 50, - expand: (state) => { - if (state.position + 1 > 4) return undefined; - return { position: state.position + 1 }; - }, - }, - { - name: 'left', - cost: 50, - expand: (state) => { - if (state.position - 1 < 0) return undefined; - return { position: state.position - 1 }; - }, - }, - ]; - c.goals = [{ - position: 4, - }, { - position: 0, - }]; - const { solution } = await maizal.astar(c); - expect(solution.pop().data.position).to.eq(c.goals[1].position); - }); - it('Force the search to find the solution on the left', async () => { - c = Object.assign({}, corridor); - c.heuristics = ({ position }) => position; - c.goals = { - position: 0, - }; - const { solution } = await maizal.astar(c); - expect(solution.pop().data.position).to.eq(c.goals.position); - }); - it('Forcing the heuristics wrong must provide a solution if any but more costly (computer wise)', async () => { - c = Object.assign({}, corridor); - c.goals = { - position: 0, - }; - const { stats, solution } = await maizal.astar(c); - expect(solution.pop().data.position).to.eq(c.goals.position); - expect(stats.nodes).to.be.eq(4); - expect(stats.depth).to.be.eq(1); - }); -}); diff --git a/test/unit/informed/bestfs.js b/test/unit/informed/bestfs.js deleted file mode 100644 index 3cbfc29..0000000 --- a/test/unit/informed/bestfs.js +++ /dev/null @@ -1,50 +0,0 @@ -const { expect } = require('chai'); -const maizal = require('../../../src/maizal'); -const corridor = require('../../corridor'); - -let c = {}; - -describe('Simple Best-first corridor search', () => { - it('A simple search should reach the last position in the corridor', async () => { - const { solution } = await maizal.bestfs(corridor); - expect(solution.pop().data.position).to.eq(corridor.goals.position); - }); - it('A better solution must prioritize over a worse one if the heuristics point to them', async () => { - c = Object.assign({}, corridor); - c.goals = [{ - position: 2, - }, { - position: 4, - }]; - const { solution } = await maizal.bestfs(c); - expect(solution.pop().data.position).to.eq(c.goals[0].position); - }); - it('The solution is found at the heuristics even if there is a better solution', async () => { - c = Object.assign({}, corridor); - c.goals = [{ - position: 4, - }, { - position: 0, - }]; - const { solution } = await maizal.bestfs(c); - expect(solution.pop().data.position).to.eq(c.goals[0].position); - }); - it('Force the search to find the solution on the left', async () => { - c = Object.assign({}, corridor); - c.heuristics = ({ position }) => position; - c.goals = { - position: 0, - }; - const { solution } = await maizal.bestfs(c); - expect(solution.pop().data.position).to.eq(c.goals.position); - }); - it('Forcing the heuristics wrong must provide a solution if any but more costly (computer wise)', async () => { - c = Object.assign({}, corridor); - c.goals = { - position: 0, - }; - const { stats, solution } = await maizal.bestfs(c); - expect(solution.pop().data.position).to.eq(c.goals.position); - expect(stats.nodes).to.be.eq(4); - }); -}); diff --git a/test/unit/informed/weightedastar.js b/test/unit/informed/weightedastar.js deleted file mode 100644 index ad59ac6..0000000 --- a/test/unit/informed/weightedastar.js +++ /dev/null @@ -1,65 +0,0 @@ -const { expect } = require('chai'); -const { to } = require('await-to-js'); -const maizal = require('../../../src/maizal'); -const corridor = require('../../corridor'); - -let c = {}; - -describe('Simple Weighted-A* corridor search', () => { - it('A simple search should reach the last position in the corridor', async () => { - c = Object.assign({}, corridor); - c.epsilon = 1.2; - const { solution } = await maizal.weightedastar(c); - expect(solution.pop().data.position).to.eq(corridor.goals.position); - }); - it('A zero epsilon should behave as a dijkstra search', async () => { - c = Object.assign({}, corridor); - c.epsilon = 0; - c.initial = { - position: 3, - }; - c.actions = [{ - name: 'right', - cost: 50, - expand: (state) => { - if (state.position + 1 > 4) return undefined; - return { position: state.position + 1 }; - }, - }, - { - name: 'left', - expand: (state) => { - if (state.position - 1 < 0) return undefined; - return { position: state.position - 1 }; - }, - }]; - const { solution, stats } = await maizal.weightedastar(c); - expect(solution.pop().data.position).to.eq(c.goals.position); - expect(stats.nodes).to.eq(4); - expect(stats.depth).to.eq(1); - expect(stats.cost).to.eq(50); - }); - it('A really high epsilon should behave as a best first search', async () => { - c = Object.assign({}, corridor); - c.epsilon = 999; - c.goals = [{ - position: 4, - }, { - position: 0, - }]; - const { solution } = await maizal.weightedastar(c); - expect(solution.pop().data.position).to.eq(c.goals[0].position); - }); - it('A Weighted-A* cannot start without an epsilon value', async () => { - const [err, data] = await to(maizal.weightedastar(corridor)); - expect(err).to.be.an('error'); - expect(data).to.be.an('undefined'); - }); - it('The epsilon value must be a number', async () => { - c = Object.assign({}, corridor); - c.epsilon = "String"; - const [err, data] = await to(maizal.weightedastar(c)); - expect(err).to.be.an('error'); - expect(data).to.be.an('undefined'); - }); -}); diff --git a/test/unit/uninformed/bfs.js b/test/unit/uninformed/bfs.js deleted file mode 100644 index 413a6af..0000000 --- a/test/unit/uninformed/bfs.js +++ /dev/null @@ -1,50 +0,0 @@ -const { expect } = require('chai'); -const maizal = require('../../../src/maizal'); -const corridor = require('../../corridor'); - -let c = {}; - -describe('Simple BFS corridor search', () => { - it('A simple search should reach the last position in the corridor', async () => { - const { solution } = await maizal.bfs(corridor); - expect(solution.pop().data.position).to.eq(corridor.goals.position); - }); - it('A better solution must prioritize over a worse one', async () => { - c = Object.assign({}, corridor); - c.goals = [{ - position: 2, - }, { - position: 4, - }]; - const { solution } = await maizal.bfs(c); - expect(solution.pop().data.position).to.eq(c.goals[0].position); - }); - it('A better solution must prioritize over a worse one', async () => { - c = Object.assign({}, corridor); - c.goals = [{ - position: 4, - }, { - position: 0, - }]; - const { solution } = await maizal.bfs(c); - expect(solution.pop().data.position).to.eq(c.goals[1].position); - }); - it('In case there a two optimal solutions the first defined action should prioritize', async () => { - c = Object.assign({}, corridor); - c.goals = [{ - position: 0, - }, { - position: 2, - }]; - const { solution } = await maizal.bfs(c); - expect(solution.pop().action).to.eq(c.actions[0].name); - }); - it('Find the best solution on the left instead', async () => { - c = Object.assign({}, corridor); - c.goals = { - position: 0, - }; - const { solution } = await maizal.bfs(c); - expect(solution.pop().data.position).to.eq(c.goals.position); - }); -}); diff --git a/test/unit/uninformed/dfs.js b/test/unit/uninformed/dfs.js deleted file mode 100644 index 20f381c..0000000 --- a/test/unit/uninformed/dfs.js +++ /dev/null @@ -1,33 +0,0 @@ -const { expect } = require('chai'); -const maizal = require('../../../src/maizal'); -const corridor = require('../../corridor'); - -let c = {}; - -describe('Simple Depth-First corridor search', () => { - it('A simple search should reach the last position in the corridor', async () => { - const { solution } = await maizal.dfs(corridor); - expect(solution.pop().data.position).to.eq(corridor.goals.position); - }); - it('A better solution may not prioritize over a worse one', async () => { - // DFS expands on depth using the right as primary action losing the left - c = Object.assign({}, corridor); - c.goals = [{ - position: 0, - }, { - position: 4, - }]; - const { solution, stats } = await maizal.dfs(c); - expect(solution.pop().data.position).to.eq(c.goals[1].position); - expect(stats.nodes).to.eq(3); - }); - it('The search shall expand on right until the maximun depth is reached and retreat', async () => { - c = Object.assign({}, corridor); - c.goals = [{ - position: 0, - }]; - const { solution, stats } = await maizal.dfs(c); - expect(solution.pop().data.position).to.eq(c.goals[0].position); - expect(stats.nodes).to.eq(4); - }); -}); diff --git a/test/unit/uninformed/dijkstra.js b/test/unit/uninformed/dijkstra.js deleted file mode 100644 index e0f7f95..0000000 --- a/test/unit/uninformed/dijkstra.js +++ /dev/null @@ -1,59 +0,0 @@ -const { expect } = require('chai'); -const maizal = require('../../../src/maizal'); -const corridor = require('../../corridor'); - -let c = {}; - -describe('Simple Dijkstra corridor search', () => { - it('A simple search should reach the last position in the corridor', async () => { - const { solution } = await maizal.dijkstra(corridor); - expect(solution.pop().data.position).to.eq(corridor.goals.position); - }); - it('A better solution must prioritize over a worse one', async () => { - c = Object.assign({}, corridor); - c.goals = [{ - position: 2, - }, { - position: 4, - }]; - const { solution } = await maizal.dijkstra(c); - expect(solution.pop().data.position).to.eq(c.goals[0].position); - }); - it('A better solution must prioritize over a worse one, Dijkstra with action cost = 0 means a BFS so it should act as so', async () => { - // Dijkstra with action cost = 0 means a BFS so it should act as so - c = Object.assign({}, corridor); - c.goals = [{ - position: 0, - }, { - position: 4, - }]; - const { solution } = await maizal.dijkstra(c); - expect(solution.pop().data.position).to.eq(c.goals[0].position); - }); - it('Smaller cost nodes should be expanded first', async () => { - c = Object.assign({}, corridor); - c.initial = { - position: 3, - }; - c.actions = [{ - name: 'right', - cost: 50, - expand: (state) => { - if (state.position + 1 > 4) return undefined; - return { position: state.position + 1 }; - }, - }, - { - name: 'left', - expand: (state) => { - if (state.position - 1 < 0) return undefined; - return { position: state.position - 1 }; - }, - }]; - const { solution, stats } = await maizal.dijkstra(c); - expect(solution.pop().data.position).to.eq(c.goals.position); - expect(stats.nodes).to.eq(4); - expect(stats.depth).to.eq(1); - expect(stats.cost).to.eq(50); - }); -}); diff --git a/test/unit/uninformed/random.js b/test/unit/uninformed/random.js deleted file mode 100644 index 280d414..0000000 --- a/test/unit/uninformed/random.js +++ /dev/null @@ -1,23 +0,0 @@ -const { expect } = require('chai'); -const maizal = require('../../../src/maizal'); -const corridor = require('../../corridor'); - -let c = {}; - -describe('Simple Random corridor search', () => { - it('A simple search should reach the last position in the corridor', async () => { - const { solution } = await maizal.random(corridor); - expect(solution.pop().data.position).to.eq(corridor.goals.position); - }); - it('A random search with several solutions must return a solution', async () => { - c = Object.assign({}, corridor); - c.initial = { position: 3 }; - c.goals = [{ - position: 2, - }, { - position: 4, - }]; - const { solution } = await maizal.random(c); - expect(solution.pop().data.position).to.be.oneOf([2, 4]); - }); -}); diff --git a/test/unit/util/HashSet.js b/test/unit/util/HashSet.js deleted file mode 100644 index 224807e..0000000 --- a/test/unit/util/HashSet.js +++ /dev/null @@ -1,150 +0,0 @@ - -const { expect } = require('chai'); -const HashSet = require('../../../src/util/HashSet'); -const months = require('../../months'); - -describe('HashSet string Constructor using an integer field', () => { - const set = new HashSet('index'); - it('An elment not present should be inserted', () => { - expect(set.add(months[3])).to.eq(true); - }); - it('The same reference element should not be inserted', () => { - expect(set.add(months[3])).to.eq(false); - }); - it('The same new element should not be inserted', () => { - expect(set.add({ index: 4, name: 'April' })).to.eq(false); - }); - it('An element without field should not be inserted and an error returned', () => { - expect(() => set.add({ name: 'Blue' })).to.throw(); - }); - it('An element previously added should be present', () => { - expect(set.has(months[3])).to.eq(true); - expect(set.getHash(months[3])).to.eq(4); - }); - it('An element not previously added should not be present', () => { - expect(set.has(months[7])).to.eq(false); - }); - it('An element can be removed if its present', () => { - expect(set.remove(months[3])).to.eq(true); - expect(set.add(months[3])).to.eq(true); - expect(set.remove(4)).to.eq(true); - }); - it('Cannot remove an element if it is not present', () => { - expect(set.remove(months[9])).to.eq(false); - }); - it('Can get an element if its present', () => { - set.add(months[3]); - expect(set.getValue(4).name).to.eq('April'); - }); - it('Cannot get an element if it is not present', () => { - expect(set.getValue(6)).to.be.undefined; - }); - it('An array of elements can be added', () => { - expect(set.add(months).filter(value => value)).to.have.length(11); - }); - it('Clear a HashSet and maintaing the hash key', () => { - set.clear(); - expect(set.size()).to.eq(0); - expect(set.add(months[3])).to.eq(true); - }); -}); - -describe('HashSet string Constructor using a String field', () => { - const set = new HashSet('name'); - it('An elment not present should be inserted', () => { - expect(set.add(months[3])).to.eq(true); - }); - it('The same reference element should not be inserted', () => { - expect(set.add(months[3])).to.eq(false); - }); - it('The same new element should not be inserted', () => { - expect(set.add({ index: 4, name: 'April' })).to.eq(false); - }); - it('An element without field should not be inserted and an error returned', () => { - expect(() => set.add({ id: 5 })).to.throw(); - }); - it('An element previously added should be present', () => { - expect(set.has(months[3])).to.eq(true); - expect(set.getHash(months[3])).to.eq('April'); - }); - it('An element not previously added should not be present', () => { - expect(set.has(months[7])).to.eq(false); - }); - it('An element can be removed if its present', () => { - expect(set.remove(months[3])).to.eq(true); - expect(set.add(months[3])).to.eq(true); - expect(set.remove('April')).to.eq(true); - }); - it('Cannot remove an element if it is not present', () => { - expect(set.remove(months[9])).to.eq(false); - }); - it('Can get an element if its present', () => { - set.add(months[3]); - expect(set.getValue('April').id).to.eq(months[3].id); - }); - it('Cannot get an element if it is not present', () => { - expect(set.getValue(6)).to.be.undefined; - }); - it('An array of elements can be added', () => { - expect(set.add(months).filter(value => value)).to.have.length(11); - }); - it('Get the keys of a HashSet', () => { - set.clear(); - set.add(months); - expect(set.keys()).to.deep.eq(months.map(({ name }) => name)); - }); -}); - -describe('HashSet string Constructor using a not existent field', () => { - const set = new HashSet('iDoNotExist'); - it('An element cannot be inserted and error thrown', () => { - expect(() => set.add(months[3])).to.throw(); - }); - it('An array cannot be inserted an error thrown', () => { - expect(() => set.add(months)).to.throw(); - }); -}); - -describe('HashSet using a function', () => { - const set = new HashSet(value => value.index + 15); - it('An elment not present should be inserted', () => { - expect(set.add(months[3])).to.eq(true); - }); - it('The same reference element should not be inserted', () => { - expect(set.add(months[3])).to.eq(false); - }); - it('The same new element should not be inserted', () => { - expect(set.add({ index: 4, name: 'April' })).to.eq(false); - }); - it('An element without field may be added depending on how the function deals with hash asignment', () => { - expect(set.add({ name: 'Blue' })).to.eq(true); - }); - it('An element previously added should be present', () => { - expect(set.has(months[3])).to.eq(true); - }); - it('An element not previously added should not be present', () => { - expect(set.has(months[7])).to.eq(false); - }); - it('An element can be removed if its present', () => { - expect(set.remove(months[3])).to.eq(true); - }); - it('Cannot remove an element if it is not present', () => { - expect(set.remove(months[9])).to.eq(false); - }); - it('Can get an element if its present', () => { - set.add(months[3]); - expect(set.getValue(19).name).to.eq('April'); - }); - it('Cannot get an element if it is not present', () => { - expect(set.getValue(6)).to.be.undefined; - }); - it('An array of elements can be added', () => { - expect(set.add(months).filter(value => value)).to.have.length(11); - }); -}); - -describe('HashSet without providing a function', () => { - it('The set cannot be created and error thrown', () => { - expect(() => new HashSet()).to.throw(); - }); -}); diff --git a/test/unit/util/PriorityQueue.js b/test/unit/util/PriorityQueue.js deleted file mode 100644 index 29c953c..0000000 --- a/test/unit/util/PriorityQueue.js +++ /dev/null @@ -1,77 +0,0 @@ -const { expect } = require('chai'); -const PriorityQueue = require('../../../src/util/PriorityQueue'); -const months = require('../../months'); - -describe('PriorityQueue ascending order elements', () => { - const q = new PriorityQueue(({ index }) => index); - it('An element should be inserted', () => { - expect(q.enqueue(months[5])).to.eq(0); - }); - it('An element with higer priority should be first', () => { - expect(q.enqueue(months[0])).to.eq(0); - }); - it('The top of the queue should be the first element', () => { - expect(q.dequeue().name).to.eq('January'); - }); - it('An empty queue cannot dequeue its elements', () => { - q.dequeue(); - expect(q.dequeue()).to.be.an('undefined'); - }); - it('An empty queue should not contains elements', () => { - expect(q.size()).to.be.eq(0); - }); - it('An array of elements can be inserted in their porper positions', () => { - expect(q.enqueue(months)).to.have.lengthOf(12); - }); - it('The queue size should be increased', () => { - expect(q.size()).to.eq(12); - }); - it('A cleared queue should not have size', () => { - q.clear(); - expect(q.size()).to.be.eq(0); - }); - it('An emptied queue should be empty', () => { - expect(q.empty()).to.be.eq(true); - }); - it('Adding an array in a random order should not affect the priorities', () => { - const rev = months.slice(0); - q.enqueue(rev.reverse()); - expect(q.dequeue().name).to.eq('January'); - }); -}); - -describe('PriorityQueue reverse order', () => { - const q = new PriorityQueue(toInsert => -toInsert.index); - it('An element should be inserted first if the queue is empty', () => { - expect(q.enqueue(months[5])).to.eq(0); - }); - it('An element should be enqueued first if its priority is higher', () => { - expect(q.enqueue(months[8])).to.eq(0); - }); - it('An element with same priority will be inserted after', () => { - expect(q.enqueue({ index: 9, name: 'NoJanuary' })).to.eq(1); - }); - it('Dequeueing should return the top element', () => { - expect(q.dequeue().name).to.eq('September'); - }); - it('Any array should be added according to their priority regarding the order', () => { - q.enqueue(months); - expect(q.dequeue().name).to.eq('December'); - }); -}); - -describe('PriorityQueue no constructor', () => { - it('A Queue cannot be created if no argument is provided', () => { - expect(() => new PriorityQueue()).to.throw(); - }); -}); - -describe('PriorityQueue undefined priority', () => { - const q = new PriorityQueue(() => undefined); - it('Undefined priorities can only be inserted on top', () => { - expect(q.enqueue(months[2])).to.be.eq(0); - }); - it('Undefined priorities must not be inserted', () => { - expect(q.enqueue(months[5])).to.be.an('undefined'); - }); -}); diff --git a/tests/PriorityQueue.test.ts b/tests/PriorityQueue.test.ts new file mode 100644 index 0000000..1ac5a36 --- /dev/null +++ b/tests/PriorityQueue.test.ts @@ -0,0 +1,8 @@ +import { expect, it } from 'vitest' +import { priorityQueue, values } from '../src/PriorityQueue.js' + +it('creates empty PriorityQueue', () => { + const priorityQueueValues = values(priorityQueue(() => 1)([])) + + expect(priorityQueueValues).toStrictEqual([]) +}) diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..d22d4a3 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,44 @@ +{ + // Visit https://aka.ms/tsconfig to read more about this file + "compilerOptions": { + // File Layout + // "rootDir": "./src", + // "outDir": "./dist", + + // Environment Settings + // See also https://aka.ms/tsconfig/module + "module": "nodenext", + "target": "esnext", + "types": [], + // For nodejs: + // "lib": ["esnext"], + // "types": ["node"], + // and npm install -D @types/node + + // Other Outputs + "sourceMap": true, + "declaration": true, + "declarationMap": true, + + // Stricter Typechecking Options + "noUncheckedIndexedAccess": true, + "exactOptionalPropertyTypes": true, + + // Style Options + // "noImplicitReturns": true, + // "noImplicitOverride": true, + // "noUnusedLocals": true, + // "noUnusedParameters": true, + // "noFallthroughCasesInSwitch": true, + // "noPropertyAccessFromIndexSignature": true, + + // Recommended Options + "strict": true, + "jsx": "react-jsx", + "verbatimModuleSyntax": true, + "isolatedModules": true, + "noUncheckedSideEffectImports": true, + "moduleDetection": "force", + "skipLibCheck": true + } +} From 8902a1880c2746aa35f755949f3a9a6dd08273eb Mon Sep 17 00:00:00 2001 From: benjides Date: Sun, 16 Nov 2025 17:18:54 +0100 Subject: [PATCH 002/118] Add test it sorts values on creation --- tests/PriorityQueue.test.ts | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/tests/PriorityQueue.test.ts b/tests/PriorityQueue.test.ts index 1ac5a36..f8857ab 100644 --- a/tests/PriorityQueue.test.ts +++ b/tests/PriorityQueue.test.ts @@ -1,8 +1,42 @@ import { expect, it } from 'vitest' import { priorityQueue, values } from '../src/PriorityQueue.js' +type Month = { + index: number + name: string +} + it('creates empty PriorityQueue', () => { const priorityQueueValues = values(priorityQueue(() => 1)([])) expect(priorityQueueValues).toStrictEqual([]) }) + +it('sorts values on creation', () => { + const months: Month[] = [ + { + index: 1, + name: 'February', + }, + { + index: 0, + name: 'January', + }, + ] + + const priorityQueueValues = values( + priorityQueue((month: Month) => month.index)(months), + ) + + const expectedPriorityQueueValues: Month[] = [ + { + index: 0, + name: 'January', + }, + { + index: 1, + name: 'February', + } + ] + expect(priorityQueueValues).toStrictEqual(expectedPriorityQueueValues) +}) From c91414290051206bc040a3bffbb6542c4a45c198 Mon Sep 17 00:00:00 2001 From: benjides Date: Sun, 16 Nov 2025 19:21:30 +0100 Subject: [PATCH 003/118] Add insert and tests --- src/PriorityQueue.ts | 21 +++++++++++++++++++++ tests/PriorityQueue.test.ts | 34 ++++++++++++++++++++++++++++++++-- 2 files changed, 53 insertions(+), 2 deletions(-) diff --git a/src/PriorityQueue.ts b/src/PriorityQueue.ts index dfa8692..dac7b5e 100644 --- a/src/PriorityQueue.ts +++ b/src/PriorityQueue.ts @@ -17,3 +17,24 @@ export const priorityQueue = export const values = (priorityQueue: PriorityQueue) => priorityQueue.map((node: Node) => node.data) + +export const insert = + (priority: (node: T) => number) => + (value: T) => + (priorityQueue: PriorityQueue) => { + const nodeToInsert: Node = { + priority: priority(value), + data: value, + } + + const index: number = priorityQueue.findIndex( + (node: Node) => nodeToInsert.priority < node.priority, + ) + + if (index === -1) return [...priorityQueue, nodeToInsert] + return [ + ...priorityQueue.slice(0, index), + nodeToInsert, + ...priorityQueue.slice(index), + ] + } diff --git a/tests/PriorityQueue.test.ts b/tests/PriorityQueue.test.ts index f8857ab..4f6eef0 100644 --- a/tests/PriorityQueue.test.ts +++ b/tests/PriorityQueue.test.ts @@ -1,5 +1,5 @@ import { expect, it } from 'vitest' -import { priorityQueue, values } from '../src/PriorityQueue.js' +import { insert, priorityQueue, values } from '../src/PriorityQueue.js' type Month = { index: number @@ -36,7 +36,37 @@ it('sorts values on creation', () => { { index: 1, name: 'February', - } + }, ] expect(priorityQueueValues).toStrictEqual(expectedPriorityQueueValues) }) + +it('inserts value at last position', () => { + const months: Month[] = [ + { + index: 1, + name: 'February', + }, + ] + + const month: Month = { + index: 2, + name: 'March', + } + + const priority = (month: Month) => month.index + + const priorityQueueValues = insert(priority)(month)(priorityQueue(priority)(months)) + + const expectedPriorityQueueValues: Month[] = [ + { + index: 1, + name: 'February', + }, + { + index: 2, + name: 'March', + }, + ] + expect(values(priorityQueueValues)).toStrictEqual(expectedPriorityQueueValues) +}) From a2319dbd779e9d2f856b6c3be3ddce7a3075ffcc Mon Sep 17 00:00:00 2001 From: benjides Date: Sun, 16 Nov 2025 19:24:24 +0100 Subject: [PATCH 004/118] Add test it inserts value at first position --- tests/PriorityQueue.test.ts | 86 +++++++++++++++++++++++++++---------- 1 file changed, 63 insertions(+), 23 deletions(-) diff --git a/tests/PriorityQueue.test.ts b/tests/PriorityQueue.test.ts index 4f6eef0..d7faaee 100644 --- a/tests/PriorityQueue.test.ts +++ b/tests/PriorityQueue.test.ts @@ -1,4 +1,4 @@ -import { expect, it } from 'vitest' +import { describe, expect, it } from 'vitest' import { insert, priorityQueue, values } from '../src/PriorityQueue.js' type Month = { @@ -41,32 +41,72 @@ it('sorts values on creation', () => { expect(priorityQueueValues).toStrictEqual(expectedPriorityQueueValues) }) -it('inserts value at last position', () => { - const months: Month[] = [ - { - index: 1, - name: 'February', - }, - ] +describe('PriorityQueue insert', () => { + it('inserts value at last position', () => { + const months: Month[] = [ + { + index: 1, + name: 'February', + }, + ] - const month: Month = { - index: 2, - name: 'March', - } + const month: Month = { + index: 2, + name: 'March', + } - const priority = (month: Month) => month.index + const priority = (month: Month) => month.index - const priorityQueueValues = insert(priority)(month)(priorityQueue(priority)(months)) + const priorityQueueValues = insert(priority)(month)( + priorityQueue(priority)(months), + ) - const expectedPriorityQueueValues: Month[] = [ - { + const expectedPriorityQueueValues: Month[] = [ + { + index: 1, + name: 'February', + }, + { + index: 2, + name: 'March', + }, + ] + expect(values(priorityQueueValues)).toStrictEqual( + expectedPriorityQueueValues, + ) + }) + + it('inserts value at first position', () => { + const months: Month[] = [ + { + index: 2, + name: 'March', + }, + ] + + const month: Month = { index: 1, name: 'February', - }, - { - index: 2, - name: 'March', - }, - ] - expect(values(priorityQueueValues)).toStrictEqual(expectedPriorityQueueValues) + } + + const priority = (month: Month) => month.index + + const priorityQueueValues = insert(priority)(month)( + priorityQueue(priority)(months), + ) + + const expectedPriorityQueueValues: Month[] = [ + { + index: 1, + name: 'February', + }, + { + index: 2, + name: 'March', + }, + ] + expect(values(priorityQueueValues)).toStrictEqual( + expectedPriorityQueueValues, + ) + }) }) From 05f69c4d9d4a9061a467f3a72fd8764d9d53ed10 Mon Sep 17 00:00:00 2001 From: benjides Date: Sun, 16 Nov 2025 19:37:18 +0100 Subject: [PATCH 005/118] Add test it inserts value at the middle position --- tests/PriorityQueue.test.ts | 43 +++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/tests/PriorityQueue.test.ts b/tests/PriorityQueue.test.ts index d7faaee..850945b 100644 --- a/tests/PriorityQueue.test.ts +++ b/tests/PriorityQueue.test.ts @@ -109,4 +109,47 @@ describe('PriorityQueue insert', () => { expectedPriorityQueueValues, ) }) + + + it('inserts value at the middle position', () => { + const months: Month[] = [ + { + index: 0, + name: 'January', + }, + { + index: 2, + name: 'March', + }, + ] + + const month: Month = { + index: 1, + name: 'February', + } + + const priority = (month: Month) => month.index + + const priorityQueueValues = insert(priority)(month)( + priorityQueue(priority)(months), + ) + + const expectedPriorityQueueValues: Month[] = [ + { + index: 0, + name: 'January', + }, + { + index: 1, + name: 'February', + }, + { + index: 2, + name: 'March', + }, + ] + expect(values(priorityQueueValues)).toStrictEqual( + expectedPriorityQueueValues, + ) + }) }) From c9c53baf44f76026ffdde57f2297569497d6e15d Mon Sep 17 00:00:00 2001 From: benjides Date: Sun, 16 Nov 2025 19:50:40 +0100 Subject: [PATCH 006/118] test it inserts after in case of same priority --- tests/PriorityQueue.test.ts | 49 +++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/tests/PriorityQueue.test.ts b/tests/PriorityQueue.test.ts index 850945b..dd04ea2 100644 --- a/tests/PriorityQueue.test.ts +++ b/tests/PriorityQueue.test.ts @@ -152,4 +152,53 @@ describe('PriorityQueue insert', () => { expectedPriorityQueueValues, ) }) + + it('inserts after in case of same priority', () => { + + type Person = { + age: number, + name: string, + } + + const persons : Person[] = [ + { + age: 20, + name: 'John', + }, + { + age: 28, + name: 'Sally', + }, + ] + + const person = { + age: 20, + name: 'Jane', + } + + const priority = (person: Person) => person.age + + const priorityQueueValues = insert(priority)(person)( + priorityQueue(priority)(persons), + ) + + const expectedPriorityQueueValues: Person[] = [ + { + age: 20, + name: 'John', + }, + { + age: 20, + name: 'Jane', + }, + { + age: 28, + name: 'Sally', + }, + ] + expect(values(priorityQueueValues)).toStrictEqual( + expectedPriorityQueueValues, + ) + }) + }) From f91d2ae40db8426f4771fc754e356982d334d4ec Mon Sep 17 00:00:00 2001 From: benjides Date: Sun, 16 Nov 2025 19:51:22 +0100 Subject: [PATCH 007/118] format:fix --- tests/PriorityQueue.test.ts | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/tests/PriorityQueue.test.ts b/tests/PriorityQueue.test.ts index dd04ea2..6b253e4 100644 --- a/tests/PriorityQueue.test.ts +++ b/tests/PriorityQueue.test.ts @@ -110,7 +110,6 @@ describe('PriorityQueue insert', () => { ) }) - it('inserts value at the middle position', () => { const months: Month[] = [ { @@ -154,13 +153,12 @@ describe('PriorityQueue insert', () => { }) it('inserts after in case of same priority', () => { - type Person = { - age: number, - name: string, + age: number + name: string } - const persons : Person[] = [ + const persons: Person[] = [ { age: 20, name: 'John', @@ -200,5 +198,4 @@ describe('PriorityQueue insert', () => { expectedPriorityQueueValues, ) }) - }) From 38d9243f37c7dc44c21948ac12ef188cb8cc6aee Mon Sep 17 00:00:00 2001 From: benjides Date: Sun, 16 Nov 2025 20:05:47 +0100 Subject: [PATCH 008/118] Replace Month with Person type --- tests/PriorityQueue.test.ts | 123 +++++++++++++++++------------------- 1 file changed, 59 insertions(+), 64 deletions(-) diff --git a/tests/PriorityQueue.test.ts b/tests/PriorityQueue.test.ts index 6b253e4..9292070 100644 --- a/tests/PriorityQueue.test.ts +++ b/tests/PriorityQueue.test.ts @@ -1,8 +1,8 @@ import { describe, expect, it } from 'vitest' import { insert, priorityQueue, values } from '../src/PriorityQueue.js' -type Month = { - index: number +type Person = { + age: number name: string } @@ -13,29 +13,29 @@ it('creates empty PriorityQueue', () => { }) it('sorts values on creation', () => { - const months: Month[] = [ + const persons: Person[] = [ { - index: 1, - name: 'February', + age: 34, + name: 'John', }, { - index: 0, - name: 'January', + age: 27, + name: 'Jane', }, ] const priorityQueueValues = values( - priorityQueue((month: Month) => month.index)(months), + priorityQueue((person: Person) => person.age)(persons), ) - const expectedPriorityQueueValues: Month[] = [ + const expectedPriorityQueueValues: Person[] = [ { - index: 0, - name: 'January', + age: 27, + name: 'Jane', }, { - index: 1, - name: 'February', + age: 34, + name: 'John', }, ] expect(priorityQueueValues).toStrictEqual(expectedPriorityQueueValues) @@ -43,32 +43,32 @@ it('sorts values on creation', () => { describe('PriorityQueue insert', () => { it('inserts value at last position', () => { - const months: Month[] = [ + const persons: Person[] = [ { - index: 1, - name: 'February', + age: 27, + name: 'Jane', }, ] - const month: Month = { - index: 2, - name: 'March', + const person: Person = { + age: 34, + name: 'John', } - const priority = (month: Month) => month.index + const priority = (person: Person) => person.age - const priorityQueueValues = insert(priority)(month)( - priorityQueue(priority)(months), + const priorityQueueValues = insert(priority)(person)( + priorityQueue(priority)(persons), ) - const expectedPriorityQueueValues: Month[] = [ + const expectedPriorityQueueValues: Person[] = [ { - index: 1, - name: 'February', + age: 27, + name: 'Jane', }, { - index: 2, - name: 'March', + age: 34, + name: 'John', }, ] expect(values(priorityQueueValues)).toStrictEqual( @@ -77,32 +77,32 @@ describe('PriorityQueue insert', () => { }) it('inserts value at first position', () => { - const months: Month[] = [ + const persons: Person[] = [ { - index: 2, - name: 'March', + age: 34, + name: 'John', }, ] - const month: Month = { - index: 1, - name: 'February', + const person: Person = { + age: 27, + name: 'Jane', } - const priority = (month: Month) => month.index + const priority = (person: Person) => person.age - const priorityQueueValues = insert(priority)(month)( - priorityQueue(priority)(months), + const priorityQueueValues = insert(priority)(person)( + priorityQueue(priority)(persons), ) - const expectedPriorityQueueValues: Month[] = [ + const expectedPriorityQueueValues: Person[] = [ { - index: 1, - name: 'February', + age: 27, + name: 'Jane', }, { - index: 2, - name: 'March', + age: 34, + name: 'John', }, ] expect(values(priorityQueueValues)).toStrictEqual( @@ -111,40 +111,40 @@ describe('PriorityQueue insert', () => { }) it('inserts value at the middle position', () => { - const months: Month[] = [ + const persons: Person[] = [ { - index: 0, - name: 'January', + age: 27, + name: 'Jane', }, { - index: 2, - name: 'March', + age: 34, + name: 'John', }, ] - const month: Month = { - index: 1, - name: 'February', + const person: Person = { + age: 28, + name: 'Sally', } - const priority = (month: Month) => month.index + const priority = (person: Person) => person.age - const priorityQueueValues = insert(priority)(month)( - priorityQueue(priority)(months), + const priorityQueueValues = insert(priority)(person)( + priorityQueue(priority)(persons), ) - const expectedPriorityQueueValues: Month[] = [ + const expectedPriorityQueueValues: Person[] = [ { - index: 0, - name: 'January', + age: 27, + name: 'Jane', }, { - index: 1, - name: 'February', + age: 28, + name: 'Sally', }, { - index: 2, - name: 'March', + age: 34, + name: 'John', }, ] expect(values(priorityQueueValues)).toStrictEqual( @@ -153,11 +153,6 @@ describe('PriorityQueue insert', () => { }) it('inserts after in case of same priority', () => { - type Person = { - age: number - name: string - } - const persons: Person[] = [ { age: 20, From 783e6ce4a2a0a5756144256e8363dc1de3cdda56 Mon Sep 17 00:00:00 2001 From: benjides Date: Mon, 17 Nov 2025 20:36:05 +0100 Subject: [PATCH 009/118] Make PriorityQueue receive priorityNodes --- src/PriorityQueue.ts | 19 +++---------------- tests/PriorityQueue.test.ts | 37 +++++++++++++++++-------------------- 2 files changed, 20 insertions(+), 36 deletions(-) diff --git a/src/PriorityQueue.ts b/src/PriorityQueue.ts index dac7b5e..b54ffb5 100644 --- a/src/PriorityQueue.ts +++ b/src/PriorityQueue.ts @@ -5,28 +5,15 @@ export type Node = { data: T } -export const priorityQueue = - (priority: (node: T) => number) => - (data: T[]) => - data - .map((node: T) => ({ - priority: priority(node), - data: node, - })) - .sort((a: Node, b: Node) => a.priority - b.priority) +export const priorityQueue = (data: Node[]) => + data.sort((a: Node, b: Node) => a.priority - b.priority) export const values = (priorityQueue: PriorityQueue) => priorityQueue.map((node: Node) => node.data) export const insert = - (priority: (node: T) => number) => - (value: T) => + (nodeToInsert: Node) => (priorityQueue: PriorityQueue) => { - const nodeToInsert: Node = { - priority: priority(value), - data: value, - } - const index: number = priorityQueue.findIndex( (node: Node) => nodeToInsert.priority < node.priority, ) diff --git a/tests/PriorityQueue.test.ts b/tests/PriorityQueue.test.ts index 9292070..cd73563 100644 --- a/tests/PriorityQueue.test.ts +++ b/tests/PriorityQueue.test.ts @@ -6,8 +6,15 @@ type Person = { name: string } +const node = (person: Person) => ({ + data: person, + priority: person.age, +}) + +const priority = (persons: Person[]) => persons.map(node) + it('creates empty PriorityQueue', () => { - const priorityQueueValues = values(priorityQueue(() => 1)([])) + const priorityQueueValues = values(priorityQueue([])) expect(priorityQueueValues).toStrictEqual([]) }) @@ -24,9 +31,7 @@ it('sorts values on creation', () => { }, ] - const priorityQueueValues = values( - priorityQueue((person: Person) => person.age)(persons), - ) + const priorityQueueValues = values(priorityQueue(priority(persons))) const expectedPriorityQueueValues: Person[] = [ { @@ -55,10 +60,8 @@ describe('PriorityQueue insert', () => { name: 'John', } - const priority = (person: Person) => person.age - - const priorityQueueValues = insert(priority)(person)( - priorityQueue(priority)(persons), + const priorityQueueValues = insert(node(person))( + priorityQueue(priority(persons)), ) const expectedPriorityQueueValues: Person[] = [ @@ -89,10 +92,8 @@ describe('PriorityQueue insert', () => { name: 'Jane', } - const priority = (person: Person) => person.age - - const priorityQueueValues = insert(priority)(person)( - priorityQueue(priority)(persons), + const priorityQueueValues = insert(node(person))( + priorityQueue(priority(persons)), ) const expectedPriorityQueueValues: Person[] = [ @@ -127,10 +128,8 @@ describe('PriorityQueue insert', () => { name: 'Sally', } - const priority = (person: Person) => person.age - - const priorityQueueValues = insert(priority)(person)( - priorityQueue(priority)(persons), + const priorityQueueValues = insert(node(person))( + priorityQueue(priority(persons)), ) const expectedPriorityQueueValues: Person[] = [ @@ -169,10 +168,8 @@ describe('PriorityQueue insert', () => { name: 'Jane', } - const priority = (person: Person) => person.age - - const priorityQueueValues = insert(priority)(person)( - priorityQueue(priority)(persons), + const priorityQueueValues = insert(node(person))( + priorityQueue(priority(persons)), ) const expectedPriorityQueueValues: Person[] = [ From 7a4cd755f8eea57edb9a4e25fa49859b9c14a36a Mon Sep 17 00:00:00 2001 From: benjides Date: Mon, 17 Nov 2025 20:40:01 +0100 Subject: [PATCH 010/118] Add --fix flag to lint:fix script --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d13bb2b..aa4594b 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "description": "Search algorithm framework to find and expand trees.", "scripts": { "lint": "eslint ./src ./tests", - "lint:fix": "eslint ./src ./tests", + "lint:fix": "eslint ./src ./tests --fix", "format": "prettier --check .", "format:fix": "prettier --write .", "test": "vitest --run", From 7c754e54d525f502241ca2ccbab3c00be922ed8c Mon Sep 17 00:00:00 2001 From: benjides Date: Mon, 17 Nov 2025 22:00:57 +0100 Subject: [PATCH 011/118] export node function to create PriorityNodes --- src/PriorityQueue.ts | 12 ++++++++++- tests/PriorityQueue.test.ts | 40 ++++++++++++++++++++++--------------- 2 files changed, 35 insertions(+), 17 deletions(-) diff --git a/src/PriorityQueue.ts b/src/PriorityQueue.ts index b54ffb5..a5affe2 100644 --- a/src/PriorityQueue.ts +++ b/src/PriorityQueue.ts @@ -5,6 +5,13 @@ export type Node = { data: T } +export const node: (f: (e: T) => number) => (e: T) => Node = + (f: (e: T) => number) => + (e: T) => ({ + priority: f(e), + data: e, + }) + export const priorityQueue = (data: Node[]) => data.sort((a: Node, b: Node) => a.priority - b.priority) @@ -18,7 +25,10 @@ export const insert = (node: Node) => nodeToInsert.priority < node.priority, ) - if (index === -1) return [...priorityQueue, nodeToInsert] + if (index === -1) { + return [...priorityQueue, nodeToInsert] + } + return [ ...priorityQueue.slice(0, index), nodeToInsert, diff --git a/tests/PriorityQueue.test.ts b/tests/PriorityQueue.test.ts index cd73563..5d4c38b 100644 --- a/tests/PriorityQueue.test.ts +++ b/tests/PriorityQueue.test.ts @@ -1,20 +1,28 @@ import { describe, expect, it } from 'vitest' -import { insert, priorityQueue, values } from '../src/PriorityQueue.js' +import { + insert, + node, + priorityQueue, + values, + type Node, + type PriorityQueue, +} from '../src/PriorityQueue.js' type Person = { age: number name: string } -const node = (person: Person) => ({ - data: person, - priority: person.age, -}) +const priority = (person: Person) => person.age + +const personNode: (e: Person) => Node = node(priority) -const priority = (persons: Person[]) => persons.map(node) +const personsPriorityQueue: (persons: Person[]) => PriorityQueue = ( + persons: Person[], +) => priorityQueue(persons.map(personNode)) it('creates empty PriorityQueue', () => { - const priorityQueueValues = values(priorityQueue([])) + const priorityQueueValues = values(personsPriorityQueue([])) expect(priorityQueueValues).toStrictEqual([]) }) @@ -31,7 +39,7 @@ it('sorts values on creation', () => { }, ] - const priorityQueueValues = values(priorityQueue(priority(persons))) + const priorityQueueValues = values(personsPriorityQueue(persons)) const expectedPriorityQueueValues: Person[] = [ { @@ -60,8 +68,8 @@ describe('PriorityQueue insert', () => { name: 'John', } - const priorityQueueValues = insert(node(person))( - priorityQueue(priority(persons)), + const priorityQueueValues = insert(personNode(person))( + personsPriorityQueue(persons), ) const expectedPriorityQueueValues: Person[] = [ @@ -92,8 +100,8 @@ describe('PriorityQueue insert', () => { name: 'Jane', } - const priorityQueueValues = insert(node(person))( - priorityQueue(priority(persons)), + const priorityQueueValues = insert(personNode(person))( + personsPriorityQueue(persons), ) const expectedPriorityQueueValues: Person[] = [ @@ -128,8 +136,8 @@ describe('PriorityQueue insert', () => { name: 'Sally', } - const priorityQueueValues = insert(node(person))( - priorityQueue(priority(persons)), + const priorityQueueValues = insert(personNode(person))( + personsPriorityQueue(persons), ) const expectedPriorityQueueValues: Person[] = [ @@ -168,8 +176,8 @@ describe('PriorityQueue insert', () => { name: 'Jane', } - const priorityQueueValues = insert(node(person))( - priorityQueue(priority(persons)), + const priorityQueueValues = insert(personNode(person))( + personsPriorityQueue(persons), ) const expectedPriorityQueueValues: Person[] = [ From 30773900fb636ffe1dc6c88419a1d62ca7777dab Mon Sep 17 00:00:00 2001 From: benjides Date: Mon, 17 Nov 2025 22:03:30 +0100 Subject: [PATCH 012/118] Reorganize tests --- tests/PriorityQueue.test.ts | 56 +++++++++++++++++++------------------ 1 file changed, 29 insertions(+), 27 deletions(-) diff --git a/tests/PriorityQueue.test.ts b/tests/PriorityQueue.test.ts index 5d4c38b..295cbe9 100644 --- a/tests/PriorityQueue.test.ts +++ b/tests/PriorityQueue.test.ts @@ -21,37 +21,39 @@ const personsPriorityQueue: (persons: Person[]) => PriorityQueue = ( persons: Person[], ) => priorityQueue(persons.map(personNode)) -it('creates empty PriorityQueue', () => { - const priorityQueueValues = values(personsPriorityQueue([])) +describe('PriorityQueue creation', () => { + it('creates empty PriorityQueue', () => { + const priorityQueueValues = values(personsPriorityQueue([])) - expect(priorityQueueValues).toStrictEqual([]) -}) + expect(priorityQueueValues).toStrictEqual([]) + }) -it('sorts values on creation', () => { - const persons: Person[] = [ - { - age: 34, - name: 'John', - }, - { - age: 27, - name: 'Jane', - }, - ] + it('sorts values on creation', () => { + const persons: Person[] = [ + { + age: 34, + name: 'John', + }, + { + age: 27, + name: 'Jane', + }, + ] - const priorityQueueValues = values(personsPriorityQueue(persons)) + const priorityQueueValues = values(personsPriorityQueue(persons)) - const expectedPriorityQueueValues: Person[] = [ - { - age: 27, - name: 'Jane', - }, - { - age: 34, - name: 'John', - }, - ] - expect(priorityQueueValues).toStrictEqual(expectedPriorityQueueValues) + const expectedPriorityQueueValues: Person[] = [ + { + age: 27, + name: 'Jane', + }, + { + age: 34, + name: 'John', + }, + ] + expect(priorityQueueValues).toStrictEqual(expectedPriorityQueueValues) + }) }) describe('PriorityQueue insert', () => { From f52be7f7591a88adc43d349c72c76c5ca4a3e975 Mon Sep 17 00:00:00 2001 From: benjides Date: Mon, 17 Nov 2025 23:52:50 +0100 Subject: [PATCH 013/118] Add poll PriorityQueue function and tests --- src/PriorityQueue.ts | 7 ++++++ tests/PriorityQueue.test.ts | 48 +++++++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+) diff --git a/src/PriorityQueue.ts b/src/PriorityQueue.ts index a5affe2..978990d 100644 --- a/src/PriorityQueue.ts +++ b/src/PriorityQueue.ts @@ -35,3 +35,10 @@ export const insert = ...priorityQueue.slice(index), ] } + +export const poll: ( + priorityQueue: PriorityQueue, +) => [T, PriorityQueue] = (priorityQueue: PriorityQueue) => [ + priorityQueue[0]?.data as T, + priorityQueue.slice(1), +] diff --git a/tests/PriorityQueue.test.ts b/tests/PriorityQueue.test.ts index 295cbe9..ffef682 100644 --- a/tests/PriorityQueue.test.ts +++ b/tests/PriorityQueue.test.ts @@ -2,6 +2,7 @@ import { describe, expect, it } from 'vitest' import { insert, node, + poll, priorityQueue, values, type Node, @@ -201,3 +202,50 @@ describe('PriorityQueue insert', () => { ) }) }) + +describe('PriorityQueue poll', () => { + it('polls top element', () => { + const persons: Person[] = [ + { + age: 27, + name: 'Jane', + }, + { + age: 34, + name: 'John', + }, + ] + + const [person] = poll(personsPriorityQueue(persons)) + + const expectedPerson: Person = { + age: 27, + name: 'Jane', + } + + expect(person).toStrictEqual(expectedPerson) + }) + + it('pops element from the PriorityQueue', () => { + const persons: Person[] = [ + { + age: 27, + name: 'Jane', + }, + { + age: 34, + name: 'John', + }, + ] + + const [, priorityQueue] = poll(personsPriorityQueue(persons)) + + const expectedPriorityQueueValues: Person[] = [ + { + age: 34, + name: 'John', + }, + ] + expect(values(priorityQueue)).toStrictEqual(expectedPriorityQueueValues) + }) +}) From a07ef22c9cc98a5a0cf34062b678b13aab2bc7b3 Mon Sep 17 00:00:00 2001 From: benjides Date: Tue, 18 Nov 2025 00:16:31 +0100 Subject: [PATCH 014/118] test it polls empty PriorityQueue and fix impl --- src/PriorityQueue.ts | 4 ++-- tests/PriorityQueue.test.ts | 32 ++++++++++++-------------------- 2 files changed, 14 insertions(+), 22 deletions(-) diff --git a/src/PriorityQueue.ts b/src/PriorityQueue.ts index 978990d..90c3206 100644 --- a/src/PriorityQueue.ts +++ b/src/PriorityQueue.ts @@ -38,7 +38,7 @@ export const insert = export const poll: ( priorityQueue: PriorityQueue, -) => [T, PriorityQueue] = (priorityQueue: PriorityQueue) => [ - priorityQueue[0]?.data as T, +) => [T | null, PriorityQueue] = (priorityQueue: PriorityQueue) => [ + priorityQueue[0]?.data ?? null, priorityQueue.slice(1), ] diff --git a/tests/PriorityQueue.test.ts b/tests/PriorityQueue.test.ts index ffef682..57bb17a 100644 --- a/tests/PriorityQueue.test.ts +++ b/tests/PriorityQueue.test.ts @@ -204,7 +204,7 @@ describe('PriorityQueue insert', () => { }) describe('PriorityQueue poll', () => { - it('polls top element', () => { + it('polls non empty PriorityQueue', () => { const persons: Person[] = [ { age: 27, @@ -216,36 +216,28 @@ describe('PriorityQueue poll', () => { }, ] - const [person] = poll(personsPriorityQueue(persons)) + const [person, priorityQueue] = poll(personsPriorityQueue(persons)) const expectedPerson: Person = { age: 27, name: 'Jane', } - - expect(person).toStrictEqual(expectedPerson) - }) - - it('pops element from the PriorityQueue', () => { - const persons: Person[] = [ - { - age: 27, - name: 'Jane', - }, - { - age: 34, - name: 'John', - }, - ] - - const [, priorityQueue] = poll(personsPriorityQueue(persons)) - const expectedPriorityQueueValues: Person[] = [ { age: 34, name: 'John', }, ] + expect(person).toStrictEqual(expectedPerson) expect(values(priorityQueue)).toStrictEqual(expectedPriorityQueueValues) }) + + it('polls empty PriorityQueue', () => { + const persons: Person[] = [] + + const [person, priorityQueue] = poll(personsPriorityQueue(persons)) + + expect(person).toBeNull() + expect(priorityQueue).toStrictEqual([]) + }) }) From d055e340197c3ea96ae5de6dcc5d58fb0bee80c8 Mon Sep 17 00:00:00 2001 From: benjides Date: Tue, 18 Nov 2025 20:31:25 +0100 Subject: [PATCH 015/118] Add HashSet and tests --- src/HashSet.ts | 17 ++++++++ tests/HashSet.test.ts | 95 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 112 insertions(+) create mode 100644 src/HashSet.ts create mode 100644 tests/HashSet.test.ts diff --git a/src/HashSet.ts b/src/HashSet.ts new file mode 100644 index 0000000..e53a367 --- /dev/null +++ b/src/HashSet.ts @@ -0,0 +1,17 @@ +export type HashSet = T[] + +export type Eq = (x: T, y: T) => boolean + +export const empty: () => HashSet = () => [] + +export const insert: (element: T) => (hashSet: HashSet) => HashSet = + (element: T) => + (hashSet: HashSet) => [...hashSet, element] + +export const has: ( + eq: Eq, +) => (element: T) => (hashSet: HashSet) => boolean = + (eq: Eq) => + (element: T) => + (hashSet: HashSet): boolean => + hashSet.findIndex((a: T) => eq(a, element)) !== -1 diff --git a/tests/HashSet.test.ts b/tests/HashSet.test.ts new file mode 100644 index 0000000..336f783 --- /dev/null +++ b/tests/HashSet.test.ts @@ -0,0 +1,95 @@ +import { describe, expect, it } from 'vitest' + +import { empty, has, insert } from '../src/HashSet.js' + +type Vector = { + x: number + y: number +} + +describe('HashSet creation', () => { + it('creates empty HashSet', () => { + expect(empty()).toStrictEqual([]) + }) +}) + +describe('HashSet insert', () => { + it('inserts value', () => { + const vector: Vector = { + x: -3, + y: 8, + } + + const hashSet = insert(vector)(empty()) + + const expectedHashSetValues: Vector[] = [ + { + x: -3, + y: 8, + }, + ] + expect(hashSet).toStrictEqual(expectedHashSetValues) + }) +}) + +describe('PriorityQueue has', () => { + it('does not have element for an empty HashSet', () => { + const hashSet = empty() + + const hasVector = has((a: Vector, b: Vector) => a.x === b.x && b.y === b.y) + + const vector: Vector = { + x: -3, + y: 8, + } + expect(hasVector(vector)(hashSet)).toBe(false) + }) + + it('has element for an element present in the HashSet', () => { + const vector: Vector = { + x: -3, + y: 8, + } + + const hashSet = insert(vector)(empty()) + + const hasVector = has((a: Vector, b: Vector) => a.x === b.x && b.y === b.y) + + expect(hasVector(vector)(hashSet)).toBe(true) + }) + + it('has element for an element present in the HashSet with swapped properties', () => { + const vector: Vector = { + x: -3, + y: 8, + } + + const hashSet = insert(vector)(empty()) + + const hasVector = has((a: Vector, b: Vector) => a.x === b.x && b.y === b.y) + + const swappedPropertiesVector: Vector = { + y: 8, + x: -3, + } + expect(hasVector(swappedPropertiesVector)(hashSet)).toBe(true) + }) + + it('not present HashSet', () => { + const vector: Vector = { + x: -3, + y: 8, + } + + const hashSet = insert(vector)(empty()) + + const hasVector = has((a: Vector, b: Vector) => a.x === b.x && b.y === b.y) + + expect( + hasVector({ + x: 1, + y: -18, + })(hashSet), + ).toBe(false) + }) +}) From 17266a911a170acd0fd2a929d5ca56387e9458ea Mon Sep 17 00:00:00 2001 From: benjides Date: Tue, 18 Nov 2025 20:40:11 +0100 Subject: [PATCH 016/118] Use assert for type enforcing --- tests/HashSet.test.ts | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/tests/HashSet.test.ts b/tests/HashSet.test.ts index 336f783..77950ce 100644 --- a/tests/HashSet.test.ts +++ b/tests/HashSet.test.ts @@ -1,4 +1,4 @@ -import { describe, expect, it } from 'vitest' +import { assert, describe, it } from 'vitest' import { empty, has, insert } from '../src/HashSet.js' @@ -9,7 +9,7 @@ type Vector = { describe('HashSet creation', () => { it('creates empty HashSet', () => { - expect(empty()).toStrictEqual([]) + assert.deepStrictEqual(empty(), []) }) }) @@ -28,7 +28,7 @@ describe('HashSet insert', () => { y: 8, }, ] - expect(hashSet).toStrictEqual(expectedHashSetValues) + assert.deepStrictEqual(hashSet, expectedHashSetValues) }) }) @@ -42,7 +42,7 @@ describe('PriorityQueue has', () => { x: -3, y: 8, } - expect(hasVector(vector)(hashSet)).toBe(false) + assert.isFalse(hasVector(vector)(hashSet)) }) it('has element for an element present in the HashSet', () => { @@ -55,7 +55,7 @@ describe('PriorityQueue has', () => { const hasVector = has((a: Vector, b: Vector) => a.x === b.x && b.y === b.y) - expect(hasVector(vector)(hashSet)).toBe(true) + assert.isTrue(hasVector(vector)(hashSet)) }) it('has element for an element present in the HashSet with swapped properties', () => { @@ -72,7 +72,7 @@ describe('PriorityQueue has', () => { y: 8, x: -3, } - expect(hasVector(swappedPropertiesVector)(hashSet)).toBe(true) + assert.isTrue(hasVector(swappedPropertiesVector)(hashSet)) }) it('not present HashSet', () => { @@ -85,11 +85,6 @@ describe('PriorityQueue has', () => { const hasVector = has((a: Vector, b: Vector) => a.x === b.x && b.y === b.y) - expect( - hasVector({ - x: 1, - y: -18, - })(hashSet), - ).toBe(false) + assert.isFalse(hasVector({ x: 1, y: -18 })(hashSet)) }) }) From a78ffc73f5eb9ff51db414a5d55004a779e3ed2c Mon Sep 17 00:00:00 2001 From: benjides Date: Tue, 18 Nov 2025 20:41:53 +0100 Subject: [PATCH 017/118] Organise tests --- tests/HashSet.test.ts | 128 +++++++++++++++++++++++------------------- 1 file changed, 69 insertions(+), 59 deletions(-) diff --git a/tests/HashSet.test.ts b/tests/HashSet.test.ts index 77950ce..712b634 100644 --- a/tests/HashSet.test.ts +++ b/tests/HashSet.test.ts @@ -7,84 +7,94 @@ type Vector = { y: number } -describe('HashSet creation', () => { - it('creates empty HashSet', () => { - assert.deepStrictEqual(empty(), []) +describe('HashSet', () => { + describe('constructor', () => { + it('creates empty', () => { + assert.deepStrictEqual(empty(), []) + }) }) -}) - -describe('HashSet insert', () => { - it('inserts value', () => { - const vector: Vector = { - x: -3, - y: 8, - } - - const hashSet = insert(vector)(empty()) - const expectedHashSetValues: Vector[] = [ - { + describe('insert', () => { + it('inserts value', () => { + const vector: Vector = { x: -3, y: 8, - }, - ] - assert.deepStrictEqual(hashSet, expectedHashSetValues) + } + + const hashSet = insert(vector)(empty()) + + const expectedHashSetValues: Vector[] = [ + { + x: -3, + y: 8, + }, + ] + assert.deepStrictEqual(hashSet, expectedHashSetValues) + }) }) -}) -describe('PriorityQueue has', () => { - it('does not have element for an empty HashSet', () => { - const hashSet = empty() + describe('has', () => { + it('does not have element for an empty HashSet', () => { + const hashSet = empty() - const hasVector = has((a: Vector, b: Vector) => a.x === b.x && b.y === b.y) + const hasVector = has( + (a: Vector, b: Vector) => a.x === b.x && b.y === b.y, + ) - const vector: Vector = { - x: -3, - y: 8, - } - assert.isFalse(hasVector(vector)(hashSet)) - }) + const vector: Vector = { + x: -3, + y: 8, + } + assert.isFalse(hasVector(vector)(hashSet)) + }) - it('has element for an element present in the HashSet', () => { - const vector: Vector = { - x: -3, - y: 8, - } + it('has element for an element present in the HashSet', () => { + const vector: Vector = { + x: -3, + y: 8, + } - const hashSet = insert(vector)(empty()) + const hashSet = insert(vector)(empty()) - const hasVector = has((a: Vector, b: Vector) => a.x === b.x && b.y === b.y) + const hasVector = has( + (a: Vector, b: Vector) => a.x === b.x && b.y === b.y, + ) - assert.isTrue(hasVector(vector)(hashSet)) - }) + assert.isTrue(hasVector(vector)(hashSet)) + }) - it('has element for an element present in the HashSet with swapped properties', () => { - const vector: Vector = { - x: -3, - y: 8, - } + it('has element for an element present in the HashSet with swapped properties', () => { + const vector: Vector = { + x: -3, + y: 8, + } - const hashSet = insert(vector)(empty()) + const hashSet = insert(vector)(empty()) - const hasVector = has((a: Vector, b: Vector) => a.x === b.x && b.y === b.y) + const hasVector = has( + (a: Vector, b: Vector) => a.x === b.x && b.y === b.y, + ) - const swappedPropertiesVector: Vector = { - y: 8, - x: -3, - } - assert.isTrue(hasVector(swappedPropertiesVector)(hashSet)) - }) + const swappedPropertiesVector: Vector = { + y: 8, + x: -3, + } + assert.isTrue(hasVector(swappedPropertiesVector)(hashSet)) + }) - it('not present HashSet', () => { - const vector: Vector = { - x: -3, - y: 8, - } + it('not present HashSet', () => { + const vector: Vector = { + x: -3, + y: 8, + } - const hashSet = insert(vector)(empty()) + const hashSet = insert(vector)(empty()) - const hasVector = has((a: Vector, b: Vector) => a.x === b.x && b.y === b.y) + const hasVector = has( + (a: Vector, b: Vector) => a.x === b.x && b.y === b.y, + ) - assert.isFalse(hasVector({ x: 1, y: -18 })(hashSet)) + assert.isFalse(hasVector({ x: 1, y: -18 })(hashSet)) + }) }) }) From 36f755527b05a2075f6a65aa2edf4ab7a6f7d9ce Mon Sep 17 00:00:00 2001 From: benjides Date: Tue, 18 Nov 2025 20:44:00 +0100 Subject: [PATCH 018/118] Add documentation for HashSet.insert --- src/DepthFirstSearch.ts | 10 ++++++++++ src/HashSet.ts | 7 +++++++ 2 files changed, 17 insertions(+) create mode 100644 src/DepthFirstSearch.ts diff --git a/src/DepthFirstSearch.ts b/src/DepthFirstSearch.ts new file mode 100644 index 0000000..560b1fa --- /dev/null +++ b/src/DepthFirstSearch.ts @@ -0,0 +1,10 @@ +export type Node = { + parent: Node | null + depth: number + data: T + goal: boolean +} + +export type Search = (initial: S, goal: S) => Promise + +export type Expand = (state: S) => Array> diff --git a/src/HashSet.ts b/src/HashSet.ts index e53a367..8126b53 100644 --- a/src/HashSet.ts +++ b/src/HashSet.ts @@ -4,6 +4,13 @@ export type Eq = (x: T, y: T) => boolean export const empty: () => HashSet = () => [] +/** + * Inserts an element T for a given HashSet + * + * @example + * + * assert.deepStrictEqual(insert(2)(empty()), [2]) + */ export const insert: (element: T) => (hashSet: HashSet) => HashSet = (element: T) => (hashSet: HashSet) => [...hashSet, element] From 7792dfe4c880078816e8996111bc58729b924cc0 Mon Sep 17 00:00:00 2001 From: benjides Date: Tue, 18 Nov 2025 22:58:13 +0100 Subject: [PATCH 019/118] Organize tests --- tests/PriorityQueue.test.ts | 410 ++++++++++++++++++------------------ 1 file changed, 205 insertions(+), 205 deletions(-) diff --git a/tests/PriorityQueue.test.ts b/tests/PriorityQueue.test.ts index 57bb17a..fa5357e 100644 --- a/tests/PriorityQueue.test.ts +++ b/tests/PriorityQueue.test.ts @@ -22,222 +22,222 @@ const personsPriorityQueue: (persons: Person[]) => PriorityQueue = ( persons: Person[], ) => priorityQueue(persons.map(personNode)) -describe('PriorityQueue creation', () => { - it('creates empty PriorityQueue', () => { - const priorityQueueValues = values(personsPriorityQueue([])) - - expect(priorityQueueValues).toStrictEqual([]) +describe('PriorityQueue', () => { + describe('constructor', () => { + it('creates empty', () => { + const priorityQueueValues = values(personsPriorityQueue([])) + + expect(priorityQueueValues).toStrictEqual([]) + }) + + it('sorts Nodes on creation', () => { + const persons: Person[] = [ + { + age: 34, + name: 'John', + }, + { + age: 27, + name: 'Jane', + }, + ] + + const priorityQueueValues = values(personsPriorityQueue(persons)) + + const expectedPriorityQueueValues: Person[] = [ + { + age: 27, + name: 'Jane', + }, + { + age: 34, + name: 'John', + }, + ] + expect(priorityQueueValues).toStrictEqual(expectedPriorityQueueValues) + }) }) - - it('sorts values on creation', () => { - const persons: Person[] = [ - { - age: 34, - name: 'John', - }, - { - age: 27, - name: 'Jane', - }, - ] - - const priorityQueueValues = values(personsPriorityQueue(persons)) - - const expectedPriorityQueueValues: Person[] = [ - { - age: 27, - name: 'Jane', - }, - { + describe('insert', () => { + it('inserts value at last position', () => { + const persons: Person[] = [ + { + age: 27, + name: 'Jane', + }, + ] + + const person: Person = { age: 34, name: 'John', - }, - ] - expect(priorityQueueValues).toStrictEqual(expectedPriorityQueueValues) - }) -}) - -describe('PriorityQueue insert', () => { - it('inserts value at last position', () => { - const persons: Person[] = [ - { - age: 27, - name: 'Jane', - }, - ] - - const person: Person = { - age: 34, - name: 'John', - } - - const priorityQueueValues = insert(personNode(person))( - personsPriorityQueue(persons), - ) - - const expectedPriorityQueueValues: Person[] = [ - { - age: 27, - name: 'Jane', - }, - { - age: 34, - name: 'John', - }, - ] - expect(values(priorityQueueValues)).toStrictEqual( - expectedPriorityQueueValues, - ) - }) - - it('inserts value at first position', () => { - const persons: Person[] = [ - { - age: 34, - name: 'John', - }, - ] - - const person: Person = { - age: 27, - name: 'Jane', - } - - const priorityQueueValues = insert(personNode(person))( - personsPriorityQueue(persons), - ) - - const expectedPriorityQueueValues: Person[] = [ - { - age: 27, - name: 'Jane', - }, - { - age: 34, - name: 'John', - }, - ] - expect(values(priorityQueueValues)).toStrictEqual( - expectedPriorityQueueValues, - ) - }) - - it('inserts value at the middle position', () => { - const persons: Person[] = [ - { + } + + const priorityQueueValues = insert(personNode(person))( + personsPriorityQueue(persons), + ) + + const expectedPriorityQueueValues: Person[] = [ + { + age: 27, + name: 'Jane', + }, + { + age: 34, + name: 'John', + }, + ] + expect(values(priorityQueueValues)).toStrictEqual( + expectedPriorityQueueValues, + ) + }) + + it('inserts value at first position', () => { + const persons: Person[] = [ + { + age: 34, + name: 'John', + }, + ] + + const person: Person = { age: 27, name: 'Jane', - }, - { - age: 34, - name: 'John', - }, - ] - - const person: Person = { - age: 28, - name: 'Sally', - } - - const priorityQueueValues = insert(personNode(person))( - personsPriorityQueue(persons), - ) - - const expectedPriorityQueueValues: Person[] = [ - { - age: 27, - name: 'Jane', - }, - { + } + + const priorityQueueValues = insert(personNode(person))( + personsPriorityQueue(persons), + ) + + const expectedPriorityQueueValues: Person[] = [ + { + age: 27, + name: 'Jane', + }, + { + age: 34, + name: 'John', + }, + ] + expect(values(priorityQueueValues)).toStrictEqual( + expectedPriorityQueueValues, + ) + }) + + it('inserts value at the middle position', () => { + const persons: Person[] = [ + { + age: 27, + name: 'Jane', + }, + { + age: 34, + name: 'John', + }, + ] + + const person: Person = { age: 28, name: 'Sally', - }, - { - age: 34, - name: 'John', - }, - ] - expect(values(priorityQueueValues)).toStrictEqual( - expectedPriorityQueueValues, - ) - }) - - it('inserts after in case of same priority', () => { - const persons: Person[] = [ - { - age: 20, - name: 'John', - }, - { - age: 28, - name: 'Sally', - }, - ] - - const person = { - age: 20, - name: 'Jane', - } - - const priorityQueueValues = insert(personNode(person))( - personsPriorityQueue(persons), - ) - - const expectedPriorityQueueValues: Person[] = [ - { - age: 20, - name: 'John', - }, - { + } + + const priorityQueueValues = insert(personNode(person))( + personsPriorityQueue(persons), + ) + + const expectedPriorityQueueValues: Person[] = [ + { + age: 27, + name: 'Jane', + }, + { + age: 28, + name: 'Sally', + }, + { + age: 34, + name: 'John', + }, + ] + expect(values(priorityQueueValues)).toStrictEqual( + expectedPriorityQueueValues, + ) + }) + + it('inserts after in case of same priority', () => { + const persons: Person[] = [ + { + age: 20, + name: 'John', + }, + { + age: 28, + name: 'Sally', + }, + ] + + const person = { age: 20, name: 'Jane', - }, - { - age: 28, - name: 'Sally', - }, - ] - expect(values(priorityQueueValues)).toStrictEqual( - expectedPriorityQueueValues, - ) + } + + const priorityQueueValues = insert(personNode(person))( + personsPriorityQueue(persons), + ) + + const expectedPriorityQueueValues: Person[] = [ + { + age: 20, + name: 'John', + }, + { + age: 20, + name: 'Jane', + }, + { + age: 28, + name: 'Sally', + }, + ] + expect(values(priorityQueueValues)).toStrictEqual( + expectedPriorityQueueValues, + ) + }) }) -}) - -describe('PriorityQueue poll', () => { - it('polls non empty PriorityQueue', () => { - const persons: Person[] = [ - { + describe('poll', () => { + it('polls non empty PriorityQueue', () => { + const persons: Person[] = [ + { + age: 27, + name: 'Jane', + }, + { + age: 34, + name: 'John', + }, + ] + + const [person, priorityQueue] = poll(personsPriorityQueue(persons)) + + const expectedPerson: Person = { age: 27, name: 'Jane', - }, - { - age: 34, - name: 'John', - }, - ] - - const [person, priorityQueue] = poll(personsPriorityQueue(persons)) - - const expectedPerson: Person = { - age: 27, - name: 'Jane', - } - const expectedPriorityQueueValues: Person[] = [ - { - age: 34, - name: 'John', - }, - ] - expect(person).toStrictEqual(expectedPerson) - expect(values(priorityQueue)).toStrictEqual(expectedPriorityQueueValues) - }) - - it('polls empty PriorityQueue', () => { - const persons: Person[] = [] - - const [person, priorityQueue] = poll(personsPriorityQueue(persons)) - - expect(person).toBeNull() - expect(priorityQueue).toStrictEqual([]) + } + const expectedPriorityQueueValues: Person[] = [ + { + age: 34, + name: 'John', + }, + ] + expect(person).toStrictEqual(expectedPerson) + expect(values(priorityQueue)).toStrictEqual(expectedPriorityQueueValues) + }) + + it('polls empty PriorityQueue', () => { + const persons: Person[] = [] + + const [person, priorityQueue] = poll(personsPriorityQueue(persons)) + + expect(person).toBeNull() + expect(priorityQueue).toStrictEqual([]) + }) }) }) From 9709228b357f851de127d0fb4e4249740edbdee0 Mon Sep 17 00:00:00 2001 From: benjides Date: Tue, 18 Nov 2025 23:06:39 +0100 Subject: [PATCH 020/118] Use assert for type enforcing --- tests/PriorityQueue.test.ts | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/tests/PriorityQueue.test.ts b/tests/PriorityQueue.test.ts index fa5357e..68ec57c 100644 --- a/tests/PriorityQueue.test.ts +++ b/tests/PriorityQueue.test.ts @@ -1,4 +1,4 @@ -import { describe, expect, it } from 'vitest' +import { assert, describe, it } from 'vitest' import { insert, node, @@ -27,7 +27,7 @@ describe('PriorityQueue', () => { it('creates empty', () => { const priorityQueueValues = values(personsPriorityQueue([])) - expect(priorityQueueValues).toStrictEqual([]) + assert.deepStrictEqual(priorityQueueValues, []) }) it('sorts Nodes on creation', () => { @@ -54,7 +54,7 @@ describe('PriorityQueue', () => { name: 'John', }, ] - expect(priorityQueueValues).toStrictEqual(expectedPriorityQueueValues) + assert.deepStrictEqual(priorityQueueValues, expectedPriorityQueueValues) }) }) describe('insert', () => { @@ -85,7 +85,8 @@ describe('PriorityQueue', () => { name: 'John', }, ] - expect(values(priorityQueueValues)).toStrictEqual( + assert.deepStrictEqual( + values(priorityQueueValues), expectedPriorityQueueValues, ) }) @@ -117,7 +118,8 @@ describe('PriorityQueue', () => { name: 'John', }, ] - expect(values(priorityQueueValues)).toStrictEqual( + assert.deepStrictEqual( + values(priorityQueueValues), expectedPriorityQueueValues, ) }) @@ -157,7 +159,8 @@ describe('PriorityQueue', () => { name: 'John', }, ] - expect(values(priorityQueueValues)).toStrictEqual( + assert.deepStrictEqual( + values(priorityQueueValues), expectedPriorityQueueValues, ) }) @@ -197,7 +200,8 @@ describe('PriorityQueue', () => { name: 'Sally', }, ] - expect(values(priorityQueueValues)).toStrictEqual( + assert.deepStrictEqual( + values(priorityQueueValues), expectedPriorityQueueValues, ) }) @@ -227,8 +231,8 @@ describe('PriorityQueue', () => { name: 'John', }, ] - expect(person).toStrictEqual(expectedPerson) - expect(values(priorityQueue)).toStrictEqual(expectedPriorityQueueValues) + assert.deepStrictEqual(person, expectedPerson) + assert.deepStrictEqual(values(priorityQueue), expectedPriorityQueueValues) }) it('polls empty PriorityQueue', () => { @@ -236,8 +240,8 @@ describe('PriorityQueue', () => { const [person, priorityQueue] = poll(personsPriorityQueue(persons)) - expect(person).toBeNull() - expect(priorityQueue).toStrictEqual([]) + assert.isNull(person) + assert.deepStrictEqual(priorityQueue, []) }) }) }) From 385d41083de5012a0e5cca6428fd9db8714ed742 Mon Sep 17 00:00:00 2001 From: benjides Date: Wed, 19 Nov 2025 00:11:59 +0100 Subject: [PATCH 021/118] Add DepthFirstSearch intial impl --- src/DepthFirstSearch.ts | 12 ++++-------- tests/DepthFirstSearch.test.ts | 27 +++++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 8 deletions(-) create mode 100644 tests/DepthFirstSearch.test.ts diff --git a/src/DepthFirstSearch.ts b/src/DepthFirstSearch.ts index 560b1fa..d2e9382 100644 --- a/src/DepthFirstSearch.ts +++ b/src/DepthFirstSearch.ts @@ -1,10 +1,6 @@ -export type Node = { - parent: Node | null - depth: number - data: T - goal: boolean -} +export type Eq = (a: S, b: S) => boolean -export type Search = (initial: S, goal: S) => Promise +export type Search = (initial: S, goal: S, eq: Eq) => Promise -export type Expand = (state: S) => Array> +export const depthFirstSearch: Search = (initial: S, goal: S, eq: Eq) => + eq(initial, goal) ? Promise.resolve([goal]) : Promise.resolve([]) diff --git a/tests/DepthFirstSearch.test.ts b/tests/DepthFirstSearch.test.ts new file mode 100644 index 0000000..eb16f59 --- /dev/null +++ b/tests/DepthFirstSearch.test.ts @@ -0,0 +1,27 @@ +import { describe, it, assert } from 'vitest' +import { depthFirstSearch, type Eq } from '../src/DepthFirstSearch.js' + +describe('DepthFirstSearch', () => { + it('solves when initial and goal are equals', async () => { + type Position = { + x: number + y: number + } + const initial: Position = { + x: 0, + y: 0, + } + const goal: Position = { + x: 0, + y: 0, + } + const eq: Eq = (a: Position, b: Position) => + a.y === b.y && a.x === b.x + + const actualSolution = await depthFirstSearch(initial, goal, eq) + + const expectedSolution: Position[] = [{ x: 0, y: 0 }] + + assert.deepStrictEqual(actualSolution, expectedSolution) + }) +}) From f6ac73736a9df93ef83f23e9e8a2c9f8bbe3d439 Mon Sep 17 00:00:00 2001 From: benjides Date: Wed, 19 Nov 2025 11:16:27 +0100 Subject: [PATCH 022/118] test it solves after expanding once --- src/DepthFirstSearch.ts | 65 ++++++++++++++++++++++++++++++++-- tests/DepthFirstSearch.test.ts | 30 +++++++++++++++- 2 files changed, 91 insertions(+), 4 deletions(-) diff --git a/src/DepthFirstSearch.ts b/src/DepthFirstSearch.ts index d2e9382..061d831 100644 --- a/src/DepthFirstSearch.ts +++ b/src/DepthFirstSearch.ts @@ -1,6 +1,65 @@ +import { + insert as insertQueue, + poll, + priorityQueue, + type PriorityQueue, +} from './PriorityQueue.js' + export type Eq = (a: S, b: S) => boolean -export type Search = (initial: S, goal: S, eq: Eq) => Promise +export type Expand = (s: S) => Promise + +export type Search = ( + initial: S, + goal: S, + eq: Eq, + expand: Expand, +) => Promise + +type State = { + parent: State | null + state: S + depth: number +} + +export const depthFirstSearch: Search = ( + initial: S, + goal: S, + eq: Eq, + expand: Expand, +): Promise => { + const open: PriorityQueue> = priorityQueue([]) + + async function expandRecursively( + open: PriorityQueue>, + s: State, + ): Promise { + if (eq(goal, s.state)) { + if (s.parent !== null) { + return [s.state, s.parent.state].reverse() + } + + return [s.state] + } + + const ns: S = await expand(s.state) + + const data: State = { + parent: s, + depth: s.depth + 1, + state: ns, + } + const queue = insertQueue({ + priority: s.depth + 1, + data: data, + })(open) + return expandRecursively(queue, poll(queue)[0] as State) + } + const initState: State = { + depth: 0, + parent: null, + state: initial, + } -export const depthFirstSearch: Search = (initial: S, goal: S, eq: Eq) => - eq(initial, goal) ? Promise.resolve([goal]) : Promise.resolve([]) + return expandRecursively(open, initState) +} diff --git a/tests/DepthFirstSearch.test.ts b/tests/DepthFirstSearch.test.ts index eb16f59..304e45b 100644 --- a/tests/DepthFirstSearch.test.ts +++ b/tests/DepthFirstSearch.test.ts @@ -18,10 +18,38 @@ describe('DepthFirstSearch', () => { const eq: Eq = (a: Position, b: Position) => a.y === b.y && a.x === b.x - const actualSolution = await depthFirstSearch(initial, goal, eq) + const actualSolution = await depthFirstSearch(initial, goal, eq, () => + Promise.resolve(goal), + ) const expectedSolution: Position[] = [{ x: 0, y: 0 }] + assert.deepStrictEqual(actualSolution, expectedSolution) + }) + + it('solves after expanding once', async () => { + type Position = { + x: number + y: number + } + const initial: Position = { + x: 0, + y: 0, + } + const goal: Position = { + x: 0, + y: 1, + } + const eq: Eq = (a: Position, b: Position) => + a.y === b.y && a.x === b.x + + const actualSolution = await depthFirstSearch(initial, goal, eq, () => + Promise.resolve(goal), + ) + const expectedSolution: Position[] = [ + { x: 0, y: 0 }, + { x: 0, y: 1 }, + ] assert.deepStrictEqual(actualSolution, expectedSolution) }) }) From d02272313d1f1d27b1480ab71716ba0dc8306ef9 Mon Sep 17 00:00:00 2001 From: benjides Date: Wed, 19 Nov 2025 12:07:04 +0100 Subject: [PATCH 023/118] test it solves after expanding twice --- src/DepthFirstSearch.ts | 20 +++++++++++++------ tests/DepthFirstSearch.test.ts | 35 ++++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 6 deletions(-) diff --git a/src/DepthFirstSearch.ts b/src/DepthFirstSearch.ts index 061d831..db3c63f 100644 --- a/src/DepthFirstSearch.ts +++ b/src/DepthFirstSearch.ts @@ -22,6 +22,17 @@ type State = { depth: number } +const solution = (state: State) => { + let node: State | null = state + const sol: S[] = [] + while (node !== null) { + sol.push(node.state) + node = node.parent + } + + return sol.reverse() +} + export const depthFirstSearch: Search = ( initial: S, goal: S, @@ -35,11 +46,7 @@ export const depthFirstSearch: Search = ( s: State, ): Promise { if (eq(goal, s.state)) { - if (s.parent !== null) { - return [s.state, s.parent.state].reverse() - } - - return [s.state] + return solution(s) } const ns: S = await expand(s.state) @@ -53,7 +60,8 @@ export const depthFirstSearch: Search = ( priority: s.depth + 1, data: data, })(open) - return expandRecursively(queue, poll(queue)[0] as State) + const [next, pq] = poll(queue) + return expandRecursively(pq, next as State) } const initState: State = { depth: 0, diff --git a/tests/DepthFirstSearch.test.ts b/tests/DepthFirstSearch.test.ts index 304e45b..52326e6 100644 --- a/tests/DepthFirstSearch.test.ts +++ b/tests/DepthFirstSearch.test.ts @@ -52,4 +52,39 @@ describe('DepthFirstSearch', () => { ] assert.deepStrictEqual(actualSolution, expectedSolution) }) + + it('solves after expanding twice', async () => { + type Position = { + x: number + y: number + } + const initial: Position = { + x: 0, + y: 0, + } + const goal: Position = { + x: 0, + y: 2, + } + const eq: Eq = (a: Position, b: Position) => + a.y === b.y && a.x === b.x + + const actualSolution = await depthFirstSearch( + initial, + goal, + eq, + (a: Position) => + Promise.resolve({ + x: a.x, + y: a.y + 1, + }), + ) + + const expectedSolution: Position[] = [ + { x: 0, y: 0 }, + { x: 0, y: 1 }, + { x: 0, y: 2 }, + ] + assert.deepStrictEqual(actualSolution, expectedSolution) + }) }) From 5ab4dbcf0d012e1dad1a1212e7c5daba1af533b7 Mon Sep 17 00:00:00 2001 From: benjides Date: Wed, 19 Nov 2025 12:10:29 +0100 Subject: [PATCH 024/118] Reorganise teests --- tests/DepthFirstSearch.test.ts | 26 ++++++++------------------ 1 file changed, 8 insertions(+), 18 deletions(-) diff --git a/tests/DepthFirstSearch.test.ts b/tests/DepthFirstSearch.test.ts index 52326e6..0d3a78c 100644 --- a/tests/DepthFirstSearch.test.ts +++ b/tests/DepthFirstSearch.test.ts @@ -1,12 +1,16 @@ import { describe, it, assert } from 'vitest' import { depthFirstSearch, type Eq } from '../src/DepthFirstSearch.js' +type Position = { + x: number + y: number +} + +const eq: Eq = (a: Position, b: Position) => + a.y === b.y && a.x === b.x + describe('DepthFirstSearch', () => { it('solves when initial and goal are equals', async () => { - type Position = { - x: number - y: number - } const initial: Position = { x: 0, y: 0, @@ -15,8 +19,6 @@ describe('DepthFirstSearch', () => { x: 0, y: 0, } - const eq: Eq = (a: Position, b: Position) => - a.y === b.y && a.x === b.x const actualSolution = await depthFirstSearch(initial, goal, eq, () => Promise.resolve(goal), @@ -27,10 +29,6 @@ describe('DepthFirstSearch', () => { }) it('solves after expanding once', async () => { - type Position = { - x: number - y: number - } const initial: Position = { x: 0, y: 0, @@ -39,8 +37,6 @@ describe('DepthFirstSearch', () => { x: 0, y: 1, } - const eq: Eq = (a: Position, b: Position) => - a.y === b.y && a.x === b.x const actualSolution = await depthFirstSearch(initial, goal, eq, () => Promise.resolve(goal), @@ -54,10 +50,6 @@ describe('DepthFirstSearch', () => { }) it('solves after expanding twice', async () => { - type Position = { - x: number - y: number - } const initial: Position = { x: 0, y: 0, @@ -66,8 +58,6 @@ describe('DepthFirstSearch', () => { x: 0, y: 2, } - const eq: Eq = (a: Position, b: Position) => - a.y === b.y && a.x === b.x const actualSolution = await depthFirstSearch( initial, From 07b1629fb50e013bc8dda699160b58a0a1973587 Mon Sep 17 00:00:00 2001 From: benjides Date: Wed, 19 Nov 2025 13:04:14 +0100 Subject: [PATCH 025/118] test it solves after expanding multiple states --- src/DepthFirstSearch.ts | 30 ++++++++++++--------- tests/DepthFirstSearch.test.ts | 49 +++++++++++++++++++++++++++++----- 2 files changed, 61 insertions(+), 18 deletions(-) diff --git a/src/DepthFirstSearch.ts b/src/DepthFirstSearch.ts index db3c63f..e911d1b 100644 --- a/src/DepthFirstSearch.ts +++ b/src/DepthFirstSearch.ts @@ -7,7 +7,7 @@ import { export type Eq = (a: S, b: S) => boolean -export type Expand = (s: S) => Promise +export type Expand = (s: S) => Promise[] export type Search = ( initial: S, @@ -49,19 +49,25 @@ export const depthFirstSearch: Search = ( return solution(s) } - const ns: S = await expand(s.state) + const ns: S[] = await Promise.all(expand(s.state)) - const data: State = { - parent: s, - depth: s.depth + 1, - state: ns, + const newStates = ns.map( + (state: S): State => ({ + parent: s, + depth: s.depth + 1, + state: state, + }), + ) + + for (const newState of newStates) { + open = insertQueue({ + priority: newState.depth, + data: newState, + })(open) } - const queue = insertQueue({ - priority: s.depth + 1, - data: data, - })(open) - const [next, pq] = poll(queue) - return expandRecursively(pq, next as State) + + const [next, nextQueue] = poll(open) + return expandRecursively(nextQueue, next as State) } const initState: State = { depth: 0, diff --git a/tests/DepthFirstSearch.test.ts b/tests/DepthFirstSearch.test.ts index 0d3a78c..4be2d70 100644 --- a/tests/DepthFirstSearch.test.ts +++ b/tests/DepthFirstSearch.test.ts @@ -1,5 +1,9 @@ import { describe, it, assert } from 'vitest' -import { depthFirstSearch, type Eq } from '../src/DepthFirstSearch.js' +import { + depthFirstSearch, + type Eq, + type Expand, +} from '../src/DepthFirstSearch.js' type Position = { x: number @@ -20,9 +24,9 @@ describe('DepthFirstSearch', () => { y: 0, } - const actualSolution = await depthFirstSearch(initial, goal, eq, () => + const actualSolution = await depthFirstSearch(initial, goal, eq, () => [ Promise.resolve(goal), - ) + ]) const expectedSolution: Position[] = [{ x: 0, y: 0 }] assert.deepStrictEqual(actualSolution, expectedSolution) @@ -38,9 +42,9 @@ describe('DepthFirstSearch', () => { y: 1, } - const actualSolution = await depthFirstSearch(initial, goal, eq, () => + const actualSolution = await depthFirstSearch(initial, goal, eq, () => [ Promise.resolve(goal), - ) + ]) const expectedSolution: Position[] = [ { x: 0, y: 0 }, @@ -63,11 +67,12 @@ describe('DepthFirstSearch', () => { initial, goal, eq, - (a: Position) => + (a: Position) => [ Promise.resolve({ x: a.x, y: a.y + 1, }), + ], ) const expectedSolution: Position[] = [ @@ -77,4 +82,36 @@ describe('DepthFirstSearch', () => { ] assert.deepStrictEqual(actualSolution, expectedSolution) }) + + it('solves after expanding multiple states', async () => { + const initial: Position = { + x: 0, + y: 0, + } + const goal: Position = { + x: 0, + y: 1, + } + type Movement = (position: Position) => Position + const up: Movement = (position: Position) => ({ + x: position.x, + y: position.y + 1, + }) + const right: Movement = (position: Position) => ({ + x: position.x + 1, + y: position.y, + }) + const expand: Expand = (position: Position) => + [up, right] + .map((movement: Movement) => movement(position)) + .map((position: Position) => Promise.resolve(position)) + + const actualSolution = await depthFirstSearch(initial, goal, eq, expand) + + const expectedSolution: Position[] = [ + { x: 0, y: 0 }, + { x: 0, y: 1 }, + ] + assert.deepStrictEqual(actualSolution, expectedSolution) + }) }) From 51d227b72b92e68f9fd6fb72a4a278501ca32939 Mon Sep 17 00:00:00 2001 From: benjides Date: Wed, 19 Nov 2025 14:39:37 +0100 Subject: [PATCH 026/118] test it solves after expanding multiple states filtering invalid movements --- tests/DepthFirstSearch.test.ts | 38 ++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/tests/DepthFirstSearch.test.ts b/tests/DepthFirstSearch.test.ts index 4be2d70..3f199a9 100644 --- a/tests/DepthFirstSearch.test.ts +++ b/tests/DepthFirstSearch.test.ts @@ -114,4 +114,42 @@ describe('DepthFirstSearch', () => { ] assert.deepStrictEqual(actualSolution, expectedSolution) }) + + it('solves after expanding multiple states filtering invalid movements', async () => { + const initial: Position = { + x: 0, + y: 1, + } + const goal: Position = { + x: 1, + y: 1, + } + type Movement = (position: Position) => Position | null + const up: Movement = (position: Position) => { + if (position.y === 1) { + return null + } + return { + x: position.x, + y: position.y + 1, + } + } + const right: Movement = (position: Position): Position => ({ + x: position.x + 1, + y: position.y, + }) + const expand: Expand = (position: Position) => + [up, right] + .map((movement: Movement) => movement(position)) + .filter((position: Position | null) => position !== null) + .map((position: Position) => Promise.resolve(position)) + + const actualSolution = await depthFirstSearch(initial, goal, eq, expand) + + const expectedSolution: Position[] = [ + { x: 0, y: 1 }, + { x: 1, y: 1 }, + ] + assert.deepStrictEqual(actualSolution, expectedSolution) + }) }) From 5d99060e662f549f36e64a94107ad88598ca0be8 Mon Sep 17 00:00:00 2001 From: benjides Date: Wed, 19 Nov 2025 16:03:31 +0100 Subject: [PATCH 027/118] Fix priority for DepthFirstSearch --- src/DepthFirstSearch.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/DepthFirstSearch.ts b/src/DepthFirstSearch.ts index e911d1b..2afaa04 100644 --- a/src/DepthFirstSearch.ts +++ b/src/DepthFirstSearch.ts @@ -61,7 +61,7 @@ export const depthFirstSearch: Search = ( for (const newState of newStates) { open = insertQueue({ - priority: newState.depth, + priority: -newState.depth, data: newState, })(open) } From 11525b5b2191a74e23a9723b574d9b113d67bee3 Mon Sep 17 00:00:00 2001 From: benjides Date: Wed, 19 Nov 2025 17:31:19 +0100 Subject: [PATCH 028/118] test it solves filtering already visited states --- src/DepthFirstSearch.ts | 24 ++++++++++------- tests/DepthFirstSearch.test.ts | 48 ++++++++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+), 9 deletions(-) diff --git a/src/DepthFirstSearch.ts b/src/DepthFirstSearch.ts index 2afaa04..9b2fb0b 100644 --- a/src/DepthFirstSearch.ts +++ b/src/DepthFirstSearch.ts @@ -4,6 +4,7 @@ import { priorityQueue, type PriorityQueue, } from './PriorityQueue.js' +import { empty, has, type HashSet, insert } from './HashSet.js' export type Eq = (a: S, b: S) => boolean @@ -40,9 +41,11 @@ export const depthFirstSearch: Search = ( expand: Expand, ): Promise => { const open: PriorityQueue> = priorityQueue([]) + const closed: HashSet = empty() async function expandRecursively( open: PriorityQueue>, + closed: HashSet, s: State, ): Promise { if (eq(goal, s.state)) { @@ -51,13 +54,15 @@ export const depthFirstSearch: Search = ( const ns: S[] = await Promise.all(expand(s.state)) - const newStates = ns.map( - (state: S): State => ({ - parent: s, - depth: s.depth + 1, - state: state, - }), - ) + const newStates = ns + .filter((s: S) => !has(eq)(s)(closed)) + .map( + (state: S): State => ({ + parent: s, + depth: s.depth + 1, + state: state, + }), + ) for (const newState of newStates) { open = insertQueue({ @@ -67,7 +72,8 @@ export const depthFirstSearch: Search = ( } const [next, nextQueue] = poll(open) - return expandRecursively(nextQueue, next as State) + closed = insert(s.state)(closed) + return expandRecursively(nextQueue, closed, next as State) } const initState: State = { depth: 0, @@ -75,5 +81,5 @@ export const depthFirstSearch: Search = ( state: initial, } - return expandRecursively(open, initState) + return expandRecursively(open, closed, initState) } diff --git a/tests/DepthFirstSearch.test.ts b/tests/DepthFirstSearch.test.ts index 3f199a9..30d67af 100644 --- a/tests/DepthFirstSearch.test.ts +++ b/tests/DepthFirstSearch.test.ts @@ -152,4 +152,52 @@ describe('DepthFirstSearch', () => { ] assert.deepStrictEqual(actualSolution, expectedSolution) }) + + it('solves filtering already visited states', async () => { + const initial: Position = { + x: 0, + y: 0, + } + const goal: Position = { + x: 1, + y: 1, + } + type Movement = (position: Position) => Position | null + const up: Movement = (position: Position) => { + if (position.y === 1) { + return null + } + return { + x: position.x, + y: position.y + 1, + } + } + const down: Movement = (position: Position) => { + if (position.y === 0) { + return null + } + return { + x: position.x, + y: position.y - 1, + } + } + const right: Movement = (position: Position): Position => ({ + x: position.x + 1, + y: position.y, + }) + const expand: Expand = (position: Position) => + [up, down, right] + .map((movement: Movement) => movement(position)) + .filter((position: Position | null) => position !== null) + .map((position: Position) => Promise.resolve(position)) + + const actualSolution = await depthFirstSearch(initial, goal, eq, expand) + + const expectedSolution: Position[] = [ + { x: 0, y: 0 }, + { x: 0, y: 1 }, + { x: 1, y: 1 }, + ] + assert.deepStrictEqual(actualSolution, expectedSolution) + }) }) From 2e4b7668946c17127bca6bd350fd84a5e540674f Mon Sep 17 00:00:00 2001 From: benjides Date: Wed, 19 Nov 2025 20:56:52 +0100 Subject: [PATCH 029/118] refactor: remove unused variable --- src/DepthFirstSearch.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/DepthFirstSearch.ts b/src/DepthFirstSearch.ts index 9b2fb0b..eb081de 100644 --- a/src/DepthFirstSearch.ts +++ b/src/DepthFirstSearch.ts @@ -52,9 +52,7 @@ export const depthFirstSearch: Search = ( return solution(s) } - const ns: S[] = await Promise.all(expand(s.state)) - - const newStates = ns + const newStates = (await Promise.all(expand(s.state))) .filter((s: S) => !has(eq)(s)(closed)) .map( (state: S): State => ({ From 1a5fd4d58ccaf339177fffde8d63c8f55c58c1f1 Mon Sep 17 00:00:00 2001 From: benjides Date: Wed, 19 Nov 2025 21:33:45 +0100 Subject: [PATCH 030/118] refactor: reorganise tests --- tests/DepthFirstSearch.test.ts | 160 +++++++++------------------------ 1 file changed, 42 insertions(+), 118 deletions(-) diff --git a/tests/DepthFirstSearch.test.ts b/tests/DepthFirstSearch.test.ts index 30d67af..97cf05d 100644 --- a/tests/DepthFirstSearch.test.ts +++ b/tests/DepthFirstSearch.test.ts @@ -13,6 +13,42 @@ type Position = { const eq: Eq = (a: Position, b: Position) => a.y === b.y && a.x === b.x +type Movement = (position: Position) => Position | null + +const up: Movement = (position: Position) => { + if (position.y === 2) { + return null + } + return { + x: position.x, + y: position.y + 1, + } +} +const down: Movement = (position: Position) => { + if (position.y === 0) { + return null + } + return { + x: position.x, + y: position.y - 1, + } +} +const right: Movement = (position: Position) => { + if (position.x === 2) { + return null + } + + return { + x: position.x + 1, + y: position.y, + } +} +const expand: Expand = (position: Position) => + [up, down, right] + .map((movement: Movement) => movement(position)) + .filter((position: Position | null) => position !== null) + .map((position: Position) => Promise.resolve(position)) + describe('DepthFirstSearch', () => { it('solves when initial and goal are equals', async () => { const initial: Position = { @@ -24,9 +60,7 @@ describe('DepthFirstSearch', () => { y: 0, } - const actualSolution = await depthFirstSearch(initial, goal, eq, () => [ - Promise.resolve(goal), - ]) + const actualSolution = await depthFirstSearch(initial, goal, eq, expand) const expectedSolution: Position[] = [{ x: 0, y: 0 }] assert.deepStrictEqual(actualSolution, expectedSolution) @@ -42,9 +76,7 @@ describe('DepthFirstSearch', () => { y: 1, } - const actualSolution = await depthFirstSearch(initial, goal, eq, () => [ - Promise.resolve(goal), - ]) + const actualSolution = await depthFirstSearch(initial, goal, eq, expand) const expectedSolution: Position[] = [ { x: 0, y: 0 }, @@ -63,54 +95,12 @@ describe('DepthFirstSearch', () => { y: 2, } - const actualSolution = await depthFirstSearch( - initial, - goal, - eq, - (a: Position) => [ - Promise.resolve({ - x: a.x, - y: a.y + 1, - }), - ], - ) - - const expectedSolution: Position[] = [ - { x: 0, y: 0 }, - { x: 0, y: 1 }, - { x: 0, y: 2 }, - ] - assert.deepStrictEqual(actualSolution, expectedSolution) - }) - - it('solves after expanding multiple states', async () => { - const initial: Position = { - x: 0, - y: 0, - } - const goal: Position = { - x: 0, - y: 1, - } - type Movement = (position: Position) => Position - const up: Movement = (position: Position) => ({ - x: position.x, - y: position.y + 1, - }) - const right: Movement = (position: Position) => ({ - x: position.x + 1, - y: position.y, - }) - const expand: Expand = (position: Position) => - [up, right] - .map((movement: Movement) => movement(position)) - .map((position: Position) => Promise.resolve(position)) - const actualSolution = await depthFirstSearch(initial, goal, eq, expand) const expectedSolution: Position[] = [ { x: 0, y: 0 }, { x: 0, y: 1 }, + { x: 0, y: 2 }, ] assert.deepStrictEqual(actualSolution, expectedSolution) }) @@ -122,81 +112,15 @@ describe('DepthFirstSearch', () => { } const goal: Position = { x: 1, - y: 1, - } - type Movement = (position: Position) => Position | null - const up: Movement = (position: Position) => { - if (position.y === 1) { - return null - } - return { - x: position.x, - y: position.y + 1, - } - } - const right: Movement = (position: Position): Position => ({ - x: position.x + 1, - y: position.y, - }) - const expand: Expand = (position: Position) => - [up, right] - .map((movement: Movement) => movement(position)) - .filter((position: Position | null) => position !== null) - .map((position: Position) => Promise.resolve(position)) - - const actualSolution = await depthFirstSearch(initial, goal, eq, expand) - - const expectedSolution: Position[] = [ - { x: 0, y: 1 }, - { x: 1, y: 1 }, - ] - assert.deepStrictEqual(actualSolution, expectedSolution) - }) - - it('solves filtering already visited states', async () => { - const initial: Position = { - x: 0, - y: 0, - } - const goal: Position = { - x: 1, - y: 1, - } - type Movement = (position: Position) => Position | null - const up: Movement = (position: Position) => { - if (position.y === 1) { - return null - } - return { - x: position.x, - y: position.y + 1, - } - } - const down: Movement = (position: Position) => { - if (position.y === 0) { - return null - } - return { - x: position.x, - y: position.y - 1, - } + y: 2, } - const right: Movement = (position: Position): Position => ({ - x: position.x + 1, - y: position.y, - }) - const expand: Expand = (position: Position) => - [up, down, right] - .map((movement: Movement) => movement(position)) - .filter((position: Position | null) => position !== null) - .map((position: Position) => Promise.resolve(position)) const actualSolution = await depthFirstSearch(initial, goal, eq, expand) const expectedSolution: Position[] = [ - { x: 0, y: 0 }, { x: 0, y: 1 }, - { x: 1, y: 1 }, + { x: 0, y: 2 }, + { x: 1, y: 2 }, ] assert.deepStrictEqual(actualSolution, expectedSolution) }) From 658e568c09010cc2a7c55945e889bfda30ad4d07 Mon Sep 17 00:00:00 2001 From: benjides Date: Wed, 19 Nov 2025 21:35:45 +0100 Subject: [PATCH 031/118] refactor: extract gridSize constant --- tests/DepthFirstSearch.test.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/DepthFirstSearch.test.ts b/tests/DepthFirstSearch.test.ts index 97cf05d..9e3aecb 100644 --- a/tests/DepthFirstSearch.test.ts +++ b/tests/DepthFirstSearch.test.ts @@ -15,8 +15,10 @@ const eq: Eq = (a: Position, b: Position) => type Movement = (position: Position) => Position | null +const gridSize: number = 2; + const up: Movement = (position: Position) => { - if (position.y === 2) { + if (position.y === gridSize) { return null } return { @@ -34,7 +36,7 @@ const down: Movement = (position: Position) => { } } const right: Movement = (position: Position) => { - if (position.x === 2) { + if (position.x === gridSize) { return null } From e1bfb3955b8a21feba1acb805b0210d6eec57e62 Mon Sep 17 00:00:00 2001 From: benjides Date: Wed, 19 Nov 2025 21:36:46 +0100 Subject: [PATCH 032/118] Add missing grid movement --- tests/DepthFirstSearch.test.ts | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/tests/DepthFirstSearch.test.ts b/tests/DepthFirstSearch.test.ts index 9e3aecb..0a0c5f2 100644 --- a/tests/DepthFirstSearch.test.ts +++ b/tests/DepthFirstSearch.test.ts @@ -15,7 +15,7 @@ const eq: Eq = (a: Position, b: Position) => type Movement = (position: Position) => Position | null -const gridSize: number = 2; +const gridSize: number = 2 const up: Movement = (position: Position) => { if (position.y === gridSize) { @@ -45,8 +45,20 @@ const right: Movement = (position: Position) => { y: position.y, } } + +const left: Movement = (position: Position) => { + if (position.x === 0) { + return null + } + + return { + x: position.x - 1, + y: position.y, + } +} + const expand: Expand = (position: Position) => - [up, down, right] + [up, down, right, left] .map((movement: Movement) => movement(position)) .filter((position: Position | null) => position !== null) .map((position: Position) => Promise.resolve(position)) From 0dbc260b8d72294fb2da2b6c35435a9f51f23fbc Mon Sep 17 00:00:00 2001 From: benjides Date: Wed, 19 Nov 2025 21:37:12 +0100 Subject: [PATCH 033/118] Rename test --- tests/DepthFirstSearch.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/DepthFirstSearch.test.ts b/tests/DepthFirstSearch.test.ts index 0a0c5f2..59f324d 100644 --- a/tests/DepthFirstSearch.test.ts +++ b/tests/DepthFirstSearch.test.ts @@ -119,7 +119,7 @@ describe('DepthFirstSearch', () => { assert.deepStrictEqual(actualSolution, expectedSolution) }) - it('solves after expanding multiple states filtering invalid movements', async () => { + it('solves after expanding n times', async () => { const initial: Position = { x: 0, y: 1, From 3363c47ce435b29f4c959fce891ceabe9edfa836 Mon Sep 17 00:00:00 2001 From: benjides Date: Thu, 20 Nov 2025 01:33:41 +0100 Subject: [PATCH 034/118] Extract Grid.ts for testing --- tests/DepthFirstSearch.test.ts | 93 ++++++++++------------------------ tests/Grid.ts | 59 +++++++++++++++++++++ 2 files changed, 85 insertions(+), 67 deletions(-) create mode 100644 tests/Grid.ts diff --git a/tests/DepthFirstSearch.test.ts b/tests/DepthFirstSearch.test.ts index 59f324d..465e095 100644 --- a/tests/DepthFirstSearch.test.ts +++ b/tests/DepthFirstSearch.test.ts @@ -1,67 +1,6 @@ import { describe, it, assert } from 'vitest' -import { - depthFirstSearch, - type Eq, - type Expand, -} from '../src/DepthFirstSearch.js' - -type Position = { - x: number - y: number -} - -const eq: Eq = (a: Position, b: Position) => - a.y === b.y && a.x === b.x - -type Movement = (position: Position) => Position | null - -const gridSize: number = 2 - -const up: Movement = (position: Position) => { - if (position.y === gridSize) { - return null - } - return { - x: position.x, - y: position.y + 1, - } -} -const down: Movement = (position: Position) => { - if (position.y === 0) { - return null - } - return { - x: position.x, - y: position.y - 1, - } -} -const right: Movement = (position: Position) => { - if (position.x === gridSize) { - return null - } - - return { - x: position.x + 1, - y: position.y, - } -} - -const left: Movement = (position: Position) => { - if (position.x === 0) { - return null - } - - return { - x: position.x - 1, - y: position.y, - } -} - -const expand: Expand = (position: Position) => - [up, down, right, left] - .map((movement: Movement) => movement(position)) - .filter((position: Position | null) => position !== null) - .map((position: Position) => Promise.resolve(position)) +import { depthFirstSearch } from '../src/DepthFirstSearch.js' +import { expandPosition, type Position, positionEquality } from './Grid.js' describe('DepthFirstSearch', () => { it('solves when initial and goal are equals', async () => { @@ -74,7 +13,12 @@ describe('DepthFirstSearch', () => { y: 0, } - const actualSolution = await depthFirstSearch(initial, goal, eq, expand) + const actualSolution = await depthFirstSearch( + initial, + goal, + positionEquality, + expandPosition, + ) const expectedSolution: Position[] = [{ x: 0, y: 0 }] assert.deepStrictEqual(actualSolution, expectedSolution) @@ -90,7 +34,12 @@ describe('DepthFirstSearch', () => { y: 1, } - const actualSolution = await depthFirstSearch(initial, goal, eq, expand) + const actualSolution = await depthFirstSearch( + initial, + goal, + positionEquality, + expandPosition, + ) const expectedSolution: Position[] = [ { x: 0, y: 0 }, @@ -109,7 +58,12 @@ describe('DepthFirstSearch', () => { y: 2, } - const actualSolution = await depthFirstSearch(initial, goal, eq, expand) + const actualSolution = await depthFirstSearch( + initial, + goal, + positionEquality, + expandPosition, + ) const expectedSolution: Position[] = [ { x: 0, y: 0 }, @@ -129,7 +83,12 @@ describe('DepthFirstSearch', () => { y: 2, } - const actualSolution = await depthFirstSearch(initial, goal, eq, expand) + const actualSolution = await depthFirstSearch( + initial, + goal, + positionEquality, + expandPosition, + ) const expectedSolution: Position[] = [ { x: 0, y: 1 }, diff --git a/tests/Grid.ts b/tests/Grid.ts new file mode 100644 index 0000000..1995c06 --- /dev/null +++ b/tests/Grid.ts @@ -0,0 +1,59 @@ +import type { Eq, Expand } from '../src/DepthFirstSearch.js' + +export type Position = { + x: number + y: number +} + +export const positionEquality: Eq = (a: Position, b: Position) => + a.y === b.y && a.x === b.x + +type Movement = (position: Position) => Position | null + +const gridSize: number = 2 + +const up: Movement = (position: Position) => { + if (position.y === gridSize) { + return null + } + return { + x: position.x, + y: position.y + 1, + } +} +const down: Movement = (position: Position) => { + if (position.y === 0) { + return null + } + return { + x: position.x, + y: position.y - 1, + } +} +const right: Movement = (position: Position) => { + if (position.x === gridSize) { + return null + } + + return { + x: position.x + 1, + y: position.y, + } +} + +const left: Movement = (position: Position) => { + if (position.x === 0) { + return null + } + + return { + x: position.x - 1, + y: position.y, + } +} + +export const expandPosition: Expand = (position: Position) => + [up, down, right, left] + .map((movement: Movement) => movement(position)) + .filter((position: Position | null) => position !== null) + .map((position: Position) => Promise.resolve(position)) From a4d6e49e9d44c668d40ad8b8ef6a3191f0b27bb9 Mon Sep 17 00:00:00 2001 From: benjides Date: Thu, 20 Nov 2025 09:25:28 +0100 Subject: [PATCH 035/118] Add Grid type to create Expand dynamically --- tests/DepthFirstSearch.test.ts | 22 +++++++++++++---- tests/Grid.ts | 44 +++++++++++++++++++--------------- 2 files changed, 42 insertions(+), 24 deletions(-) diff --git a/tests/DepthFirstSearch.test.ts b/tests/DepthFirstSearch.test.ts index 465e095..9340148 100644 --- a/tests/DepthFirstSearch.test.ts +++ b/tests/DepthFirstSearch.test.ts @@ -1,8 +1,20 @@ import { describe, it, assert } from 'vitest' import { depthFirstSearch } from '../src/DepthFirstSearch.js' -import { expandPosition, type Position, positionEquality } from './Grid.js' +import { + expandPosition, + type Grid, + type Position, + positionEquality, +} from './Grid.js' describe('DepthFirstSearch', () => { + const grid: Grid = { + rows: 2, + columns: 2, + } + + const expand = expandPosition(grid) + it('solves when initial and goal are equals', async () => { const initial: Position = { x: 0, @@ -17,7 +29,7 @@ describe('DepthFirstSearch', () => { initial, goal, positionEquality, - expandPosition, + expand, ) const expectedSolution: Position[] = [{ x: 0, y: 0 }] @@ -38,7 +50,7 @@ describe('DepthFirstSearch', () => { initial, goal, positionEquality, - expandPosition, + expand, ) const expectedSolution: Position[] = [ @@ -62,7 +74,7 @@ describe('DepthFirstSearch', () => { initial, goal, positionEquality, - expandPosition, + expand, ) const expectedSolution: Position[] = [ @@ -87,7 +99,7 @@ describe('DepthFirstSearch', () => { initial, goal, positionEquality, - expandPosition, + expand, ) const expectedSolution: Position[] = [ diff --git a/tests/Grid.ts b/tests/Grid.ts index 1995c06..d27cfe0 100644 --- a/tests/Grid.ts +++ b/tests/Grid.ts @@ -1,5 +1,10 @@ import type { Eq, Expand } from '../src/DepthFirstSearch.js' +export type Grid = { + rows: number + columns: number +} + export type Position = { x: number y: number @@ -10,10 +15,8 @@ export const positionEquality: Eq = (a: Position, b: Position) => type Movement = (position: Position) => Position | null -const gridSize: number = 2 - -const up: Movement = (position: Position) => { - if (position.y === gridSize) { +const up: (grid: Grid) => Movement = (grid: Grid) => (position: Position) => { + if (position.y === grid.rows) { return null } return { @@ -21,7 +24,7 @@ const up: Movement = (position: Position) => { y: position.y + 1, } } -const down: Movement = (position: Position) => { +const down: (grid: Grid) => Movement = () => (position: Position) => { if (position.y === 0) { return null } @@ -30,18 +33,19 @@ const down: Movement = (position: Position) => { y: position.y - 1, } } -const right: Movement = (position: Position) => { - if (position.x === gridSize) { - return null - } +const right: (grid: Grid) => Movement = + (grid: Grid) => (position: Position) => { + if (position.x === grid.columns) { + return null + } - return { - x: position.x + 1, - y: position.y, + return { + x: position.x + 1, + y: position.y, + } } -} -const left: Movement = (position: Position) => { +const left: (grid: Grid) => Movement = () => (position: Position) => { if (position.x === 0) { return null } @@ -52,8 +56,10 @@ const left: Movement = (position: Position) => { } } -export const expandPosition: Expand = (position: Position) => - [up, down, right, left] - .map((movement: Movement) => movement(position)) - .filter((position: Position | null) => position !== null) - .map((position: Position) => Promise.resolve(position)) +export const expandPosition: (grid: Grid) => Expand = + (grid: Grid) => (position: Position) => + [up, down, right, left] + .map((m: (grid: Grid) => Movement) => m(grid)) + .map((movement: Movement) => movement(position)) + .filter((position: Position | null) => position !== null) + .map((position: Position) => Promise.resolve(position)) From 25b7e3a280614b5aaef727145724c766cae26fa3 Mon Sep 17 00:00:00 2001 From: benjides Date: Thu, 20 Nov 2025 15:58:36 +0100 Subject: [PATCH 036/118] Add Grid type to create Expand dynamically --- src/DepthFirstSearch.ts | 40 +++++++++++++++++++++++----------- tests/DepthFirstSearch.test.ts | 21 ++++++++++++++++++ 2 files changed, 48 insertions(+), 13 deletions(-) diff --git a/src/DepthFirstSearch.ts b/src/DepthFirstSearch.ts index eb081de..9a54105 100644 --- a/src/DepthFirstSearch.ts +++ b/src/DepthFirstSearch.ts @@ -46,38 +46,52 @@ export const depthFirstSearch: Search = ( async function expandRecursively( open: PriorityQueue>, closed: HashSet, - s: State, ): Promise { - if (eq(goal, s.state)) { - return solution(s) + const r = poll(open) + + const next = r[0] + let nextQueue = r[1] + + if (next === null) { + return [] } - const newStates = (await Promise.all(expand(s.state))) - .filter((s: S) => !has(eq)(s)(closed)) + if (eq(goal, next.state)) { + return solution(next) + } + + const newStates = (await Promise.all(expand(next.state))) + .filter((state: S) => !has(eq)(state)(closed)) .map( (state: S): State => ({ - parent: s, - depth: s.depth + 1, + parent: next, + depth: next.depth + 1, state: state, }), ) for (const newState of newStates) { - open = insertQueue({ + nextQueue = insertQueue({ priority: -newState.depth, data: newState, - })(open) + })(nextQueue) } - const [next, nextQueue] = poll(open) - closed = insert(s.state)(closed) - return expandRecursively(nextQueue, closed, next as State) + closed = insert(next.state)(closed) + return expandRecursively(nextQueue, closed) } + const initState: State = { depth: 0, parent: null, state: initial, } - return expandRecursively(open, closed, initState) + return expandRecursively( + insertQueue({ + data: initState, + priority: initState.depth, + })(open), + closed, + ) } diff --git a/tests/DepthFirstSearch.test.ts b/tests/DepthFirstSearch.test.ts index 9340148..7c45892 100644 --- a/tests/DepthFirstSearch.test.ts +++ b/tests/DepthFirstSearch.test.ts @@ -109,4 +109,25 @@ describe('DepthFirstSearch', () => { ] assert.deepStrictEqual(actualSolution, expectedSolution) }) + + it('return empty array for unsolvable searches', async () => { + const initial: Position = { + x: 0, + y: 1, + } + const goal: Position = { + x: 100, + y: 100, + } + + const actualSolution = await depthFirstSearch( + initial, + goal, + positionEquality, + expand, + ) + + const expectedSolution: Position[] = [] + assert.deepStrictEqual(actualSolution, expectedSolution) + }) }) From 95dcc2602b69c67d8c59c0d694036298aec902c4 Mon Sep 17 00:00:00 2001 From: benjides Date: Thu, 20 Nov 2025 23:24:05 +0100 Subject: [PATCH 037/118] Adjust tests --- tests/DepthFirstSearch.test.ts | 22 ++++++++++------------ tests/Grid.ts | 4 ++-- 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/tests/DepthFirstSearch.test.ts b/tests/DepthFirstSearch.test.ts index 7c45892..3323b72 100644 --- a/tests/DepthFirstSearch.test.ts +++ b/tests/DepthFirstSearch.test.ts @@ -8,14 +8,8 @@ import { } from './Grid.js' describe('DepthFirstSearch', () => { - const grid: Grid = { - rows: 2, - columns: 2, - } - - const expand = expandPosition(grid) - it('solves when initial and goal are equals', async () => { + const grid: Grid = { rows: 1, columns: 1 } const initial: Position = { x: 0, y: 0, @@ -29,7 +23,7 @@ describe('DepthFirstSearch', () => { initial, goal, positionEquality, - expand, + expandPosition(grid), ) const expectedSolution: Position[] = [{ x: 0, y: 0 }] @@ -37,6 +31,7 @@ describe('DepthFirstSearch', () => { }) it('solves after expanding once', async () => { + const grid: Grid = { rows: 2, columns: 1 } const initial: Position = { x: 0, y: 0, @@ -50,7 +45,7 @@ describe('DepthFirstSearch', () => { initial, goal, positionEquality, - expand, + expandPosition(grid), ) const expectedSolution: Position[] = [ @@ -61,6 +56,7 @@ describe('DepthFirstSearch', () => { }) it('solves after expanding twice', async () => { + const grid: Grid = { rows: 3, columns: 1 } const initial: Position = { x: 0, y: 0, @@ -74,7 +70,7 @@ describe('DepthFirstSearch', () => { initial, goal, positionEquality, - expand, + expandPosition(grid), ) const expectedSolution: Position[] = [ @@ -86,6 +82,7 @@ describe('DepthFirstSearch', () => { }) it('solves after expanding n times', async () => { + const grid: Grid = { rows: 3, columns: 2 } const initial: Position = { x: 0, y: 1, @@ -99,7 +96,7 @@ describe('DepthFirstSearch', () => { initial, goal, positionEquality, - expand, + expandPosition(grid), ) const expectedSolution: Position[] = [ @@ -111,6 +108,7 @@ describe('DepthFirstSearch', () => { }) it('return empty array for unsolvable searches', async () => { + const grid: Grid = { rows: 2, columns: 2 } const initial: Position = { x: 0, y: 1, @@ -124,7 +122,7 @@ describe('DepthFirstSearch', () => { initial, goal, positionEquality, - expand, + expandPosition(grid), ) const expectedSolution: Position[] = [] diff --git a/tests/Grid.ts b/tests/Grid.ts index d27cfe0..24c4fb6 100644 --- a/tests/Grid.ts +++ b/tests/Grid.ts @@ -16,7 +16,7 @@ export const positionEquality: Eq = (a: Position, b: Position) => type Movement = (position: Position) => Position | null const up: (grid: Grid) => Movement = (grid: Grid) => (position: Position) => { - if (position.y === grid.rows) { + if (position.y === grid.rows - 1) { return null } return { @@ -35,7 +35,7 @@ const down: (grid: Grid) => Movement = () => (position: Position) => { } const right: (grid: Grid) => Movement = (grid: Grid) => (position: Position) => { - if (position.x === grid.columns) { + if (position.x === grid.columns - 1) { return null } From 11649430e3fed5e3c8d70df7d960514ab79c550b Mon Sep 17 00:00:00 2001 From: benjides Date: Thu, 20 Nov 2025 23:32:46 +0100 Subject: [PATCH 038/118] Add test it solves worst solution --- tests/DepthFirstSearch.test.ts | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/tests/DepthFirstSearch.test.ts b/tests/DepthFirstSearch.test.ts index 3323b72..5a5b264 100644 --- a/tests/DepthFirstSearch.test.ts +++ b/tests/DepthFirstSearch.test.ts @@ -107,6 +107,38 @@ describe('DepthFirstSearch', () => { assert.deepStrictEqual(actualSolution, expectedSolution) }) + it('solves worst solution', async () => { + const grid: Grid = { rows: 3, columns: 3 } + const initial: Position = { + x: 0, + y: 0, + } + const goal: Position = { + x: 2, + y: 2, + } + + const actualSolution = await depthFirstSearch( + initial, + goal, + positionEquality, + expandPosition(grid), + ) + + const expectedSolution: Position[] = [ + { x: 0, y: 0 }, + { x: 0, y: 1 }, + { x: 0, y: 2 }, + { x: 1, y: 2 }, + { x: 1, y: 1 }, + { x: 1, y: 0 }, + { x: 2, y: 0 }, + { x: 2, y: 1 }, + { x: 2, y: 2 }, + ] + assert.deepStrictEqual(actualSolution, expectedSolution) + }) + it('return empty array for unsolvable searches', async () => { const grid: Grid = { rows: 2, columns: 2 } const initial: Position = { From 0c49a0b23babba97b65410bb4e27050947dc3cbb Mon Sep 17 00:00:00 2001 From: benjides Date: Fri, 21 Nov 2025 18:45:28 +0100 Subject: [PATCH 039/118] Add Tree impl and tests --- src/Tree.ts | 85 ++++++++++++++++++++++++++++++++++++++++++++++ tests/Tree.test.ts | 52 ++++++++++++++++++++++++++++ 2 files changed, 137 insertions(+) create mode 100644 src/Tree.ts create mode 100644 tests/Tree.test.ts diff --git a/src/Tree.ts b/src/Tree.ts new file mode 100644 index 0000000..4d84edb --- /dev/null +++ b/src/Tree.ts @@ -0,0 +1,85 @@ +/** + * Tree main Node data containing arbitrary key and value and optionally a parent Node + * + * @see Node + */ +export type Node = { + key: K + value: V + parent: Node | null +} + +/** + * Tree containing arbitrary key and values for each Node + * + * @see Node + */ +export type Tree = Node + +/** + * Particular case of a Node without parent pointer + * + * @see Tree + * @see Node + */ +export type Root = Omit, 'parent'> + +/** + * Particular case of a Node without parent pointer + * + * @see Tree + * @see Node + */ +export type Child = Omit, 'parent'> + +/** + * Returns a Tree for a given Root + * + * @example + * ```ts + * assert.deepStrictEqual(toArray(tree({ key: 1, value: 1 })), [1]) + * ``` + * + * @see Root + */ +export const tree: (root: Root) => Tree = ( + root: Root, +): Tree => ({ + key: root.key, + value: root.value, + parent: null, +}) + +/** + * Inserts a Child for a given Node and returns a Node pointing to the inserted Child + * + * @example + * ```ts + * assert.deepStrictEqual(toArray(insert({ key: 1, value: 1 })(tree({ key: 1, value: 1 }))), [1, 2]) + * ``` + * + * @see Node + * @see Child + */ +export const insert: ( + child: Child, +) => (node: Node) => Node = + (child: Child) => + (node: Node) => ({ + key: child.key, + value: child.value, + parent: node, + }) + +/** + * Returns an ordered list of values from Roo for a given Node iterating over its parents recursively + * + * @example + * ```ts + * assert.deepStrictEqual(toArray(tree({ key: 1, value: 1 })), [1]) + * ``` + */ +export const toArray: (node: Node) => V[] = ( + node: Node, +) => + node.parent === null ? [node.value] : [...toArray(node.parent), node.value] diff --git a/tests/Tree.test.ts b/tests/Tree.test.ts new file mode 100644 index 0000000..e8bb44e --- /dev/null +++ b/tests/Tree.test.ts @@ -0,0 +1,52 @@ +import { assert, describe, it } from 'vitest' +import { insert, toArray, type Tree, tree } from '../src/Tree.js' + +describe('Tree', () => { + describe('constructor', () => { + it('creates for Tree Root', () => { + const t: Tree = tree({ + key: 1, + value: 1, + }) + + const expectedValues = [1] + assert.deepStrictEqual(toArray(t), expectedValues) + }) + }) + + describe('insert', () => { + it('inserts value', () => { + const childInsert = insert({ + key: 2, + value: 2, + }) + + const t: Tree = childInsert(tree({ key: 1, value: 1 })) + + const expectedValues = [1, 2] + assert.deepStrictEqual(toArray(t), expectedValues) + }) + + it('inserts multiple values', () => { + const childInsert = insert({ + key: 2, + value: 2, + }) + + const t: Tree = insert({ + key: 3, + value: 3, + })( + childInsert( + tree({ + key: 1, + value: 1, + }), + ), + ) + + const expectedValues = [1, 2, 3] + assert.deepStrictEqual(toArray(t), expectedValues) + }) + }) +}) From 6159d9b6269f444344a7eb4bbab0d747542445ea Mon Sep 17 00:00:00 2001 From: benjides Date: Fri, 21 Nov 2025 18:45:55 +0100 Subject: [PATCH 040/118] Use Tree to represent Search states --- src/DepthFirstSearch.ts | 61 +++++++++++++++++------------------------ 1 file changed, 25 insertions(+), 36 deletions(-) diff --git a/src/DepthFirstSearch.ts b/src/DepthFirstSearch.ts index 9a54105..0ad30e3 100644 --- a/src/DepthFirstSearch.ts +++ b/src/DepthFirstSearch.ts @@ -5,6 +5,13 @@ import { type PriorityQueue, } from './PriorityQueue.js' import { empty, has, type HashSet, insert } from './HashSet.js' +import { + type Child, + toArray, + type Tree, + insert as insertChild, + tree, +} from './Tree.js' export type Eq = (a: S, b: S) => boolean @@ -17,34 +24,17 @@ export type Search = ( expand: Expand, ) => Promise -type State = { - parent: State | null - state: S - depth: number -} - -const solution = (state: State) => { - let node: State | null = state - const sol: S[] = [] - while (node !== null) { - sol.push(node.state) - node = node.parent - } - - return sol.reverse() -} - export const depthFirstSearch: Search = ( initial: S, goal: S, eq: Eq, expand: Expand, ): Promise => { - const open: PriorityQueue> = priorityQueue([]) + const open: PriorityQueue> = priorityQueue([]) const closed: HashSet = empty() async function expandRecursively( - open: PriorityQueue>, + open: PriorityQueue>, closed: HashSet, ): Promise { const r = poll(open) @@ -56,41 +46,40 @@ export const depthFirstSearch: Search = ( return [] } - if (eq(goal, next.state)) { - return solution(next) + if (eq(goal, next.value)) { + return toArray(next) } - const newStates = (await Promise.all(expand(next.state))) + const newStates = (await Promise.all(expand(next.value))) .filter((state: S) => !has(eq)(state)(closed)) .map( - (state: S): State => ({ - parent: next, - depth: next.depth + 1, - state: state, + (state: S): Child => ({ + key: next.key + 1, + value: state, }), ) for (const newState of newStates) { + const tree: Tree = insertChild(newState)(next) nextQueue = insertQueue({ - priority: -newState.depth, - data: newState, + priority: -newState.key, + data: tree, })(nextQueue) } - closed = insert(next.state)(closed) + closed = insert(next.value)(closed) return expandRecursively(nextQueue, closed) } - const initState: State = { - depth: 0, - parent: null, - state: initial, - } + const t: Tree = tree({ + key: 0, + value: initial, + }) return expandRecursively( insertQueue({ - data: initState, - priority: initState.depth, + data: t, + priority: 0, })(open), closed, ) From 27fd9e16d843219f12fec1e175693fa1f6234ad5 Mon Sep 17 00:00:00 2001 From: benjides Date: Fri, 21 Nov 2025 18:51:28 +0100 Subject: [PATCH 041/118] Use Namespaced imports --- src/DepthFirstSearch.ts | 40 ++++++++++++++++------------------------ 1 file changed, 16 insertions(+), 24 deletions(-) diff --git a/src/DepthFirstSearch.ts b/src/DepthFirstSearch.ts index 0ad30e3..d12356a 100644 --- a/src/DepthFirstSearch.ts +++ b/src/DepthFirstSearch.ts @@ -1,17 +1,9 @@ -import { - insert as insertQueue, - poll, - priorityQueue, - type PriorityQueue, -} from './PriorityQueue.js' -import { empty, has, type HashSet, insert } from './HashSet.js' -import { - type Child, - toArray, - type Tree, - insert as insertChild, - tree, -} from './Tree.js' +import * as PQ from './PriorityQueue.js' +import * as T from './Tree.js' +import * as HS from './HashSet.js' +import type { PriorityQueue } from './PriorityQueue.js' +import type { Child, Tree } from './Tree.js' +import type { HashSet } from './HashSet.js' export type Eq = (a: S, b: S) => boolean @@ -30,14 +22,14 @@ export const depthFirstSearch: Search = ( eq: Eq, expand: Expand, ): Promise => { - const open: PriorityQueue> = priorityQueue([]) - const closed: HashSet = empty() + const open: PriorityQueue> = PQ.priorityQueue([]) + const closed: HashSet = HS.empty() async function expandRecursively( open: PriorityQueue>, closed: HashSet, ): Promise { - const r = poll(open) + const r = PQ.poll(open) const next = r[0] let nextQueue = r[1] @@ -47,11 +39,11 @@ export const depthFirstSearch: Search = ( } if (eq(goal, next.value)) { - return toArray(next) + return T.toArray(next) } const newStates = (await Promise.all(expand(next.value))) - .filter((state: S) => !has(eq)(state)(closed)) + .filter((state: S) => !HS.has(eq)(state)(closed)) .map( (state: S): Child => ({ key: next.key + 1, @@ -60,24 +52,24 @@ export const depthFirstSearch: Search = ( ) for (const newState of newStates) { - const tree: Tree = insertChild(newState)(next) - nextQueue = insertQueue({ + const tree: Tree = T.insert(newState)(next) + nextQueue = PQ.insert({ priority: -newState.key, data: tree, })(nextQueue) } - closed = insert(next.value)(closed) + closed = HS.insert(next.value)(closed) return expandRecursively(nextQueue, closed) } - const t: Tree = tree({ + const t: T.Tree = T.tree({ key: 0, value: initial, }) return expandRecursively( - insertQueue({ + PQ.insert({ data: t, priority: 0, })(open), From 671f070e5567a3b31a96a5905b0d133ca6036207 Mon Sep 17 00:00:00 2001 From: benjides Date: Fri, 21 Nov 2025 18:54:24 +0100 Subject: [PATCH 042/118] Rename Tree constructor to fromRoot --- src/DepthFirstSearch.ts | 2 +- src/Tree.ts | 10 +++++----- tests/Tree.test.ts | 10 ++++++---- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/src/DepthFirstSearch.ts b/src/DepthFirstSearch.ts index d12356a..c1afcb9 100644 --- a/src/DepthFirstSearch.ts +++ b/src/DepthFirstSearch.ts @@ -63,7 +63,7 @@ export const depthFirstSearch: Search = ( return expandRecursively(nextQueue, closed) } - const t: T.Tree = T.tree({ + const t: T.Tree = T.fromRoot({ key: 0, value: initial, }) diff --git a/src/Tree.ts b/src/Tree.ts index 4d84edb..34d8736 100644 --- a/src/Tree.ts +++ b/src/Tree.ts @@ -33,16 +33,16 @@ export type Root = Omit, 'parent'> export type Child = Omit, 'parent'> /** - * Returns a Tree for a given Root + * Creates a Tree for a given Root * * @example * ```ts - * assert.deepStrictEqual(toArray(tree({ key: 1, value: 1 })), [1]) + * assert.deepStrictEqual(toArray(fromRoot({ key: 1, value: 1 })), [1]) * ``` * * @see Root */ -export const tree: (root: Root) => Tree = ( +export const fromRoot: (root: Root) => Tree = ( root: Root, ): Tree => ({ key: root.key, @@ -55,7 +55,7 @@ export const tree: (root: Root) => Tree = ( * * @example * ```ts - * assert.deepStrictEqual(toArray(insert({ key: 1, value: 1 })(tree({ key: 1, value: 1 }))), [1, 2]) + * assert.deepStrictEqual(toArray(insert({ key: 1, value: 1 })(fromRoot({ key: 1, value: 1 }))), [1, 2]) * ``` * * @see Node @@ -76,7 +76,7 @@ export const insert: ( * * @example * ```ts - * assert.deepStrictEqual(toArray(tree({ key: 1, value: 1 })), [1]) + * assert.deepStrictEqual(toArray(fromRoot({ key: 1, value: 1 })), [1]) * ``` */ export const toArray: (node: Node) => V[] = ( diff --git a/tests/Tree.test.ts b/tests/Tree.test.ts index e8bb44e..99ee9d5 100644 --- a/tests/Tree.test.ts +++ b/tests/Tree.test.ts @@ -1,10 +1,10 @@ import { assert, describe, it } from 'vitest' -import { insert, toArray, type Tree, tree } from '../src/Tree.js' +import { insert, toArray, type Tree, fromRoot } from '../src/Tree.js' describe('Tree', () => { describe('constructor', () => { it('creates for Tree Root', () => { - const t: Tree = tree({ + const t: Tree = fromRoot({ key: 1, value: 1, }) @@ -21,7 +21,9 @@ describe('Tree', () => { value: 2, }) - const t: Tree = childInsert(tree({ key: 1, value: 1 })) + const t: Tree = childInsert( + fromRoot({ key: 1, value: 1 }), + ) const expectedValues = [1, 2] assert.deepStrictEqual(toArray(t), expectedValues) @@ -38,7 +40,7 @@ describe('Tree', () => { value: 3, })( childInsert( - tree({ + fromRoot({ key: 1, value: 1, }), From f24be4fc30b295939c49f89b94b987b744a43326 Mon Sep 17 00:00:00 2001 From: benjides Date: Fri, 21 Nov 2025 19:22:55 +0100 Subject: [PATCH 043/118] Map to insertable Tree.Nodes resulting states --- src/DepthFirstSearch.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/DepthFirstSearch.ts b/src/DepthFirstSearch.ts index c1afcb9..7177260 100644 --- a/src/DepthFirstSearch.ts +++ b/src/DepthFirstSearch.ts @@ -2,7 +2,7 @@ import * as PQ from './PriorityQueue.js' import * as T from './Tree.js' import * as HS from './HashSet.js' import type { PriorityQueue } from './PriorityQueue.js' -import type { Child, Tree } from './Tree.js' +import type { Child, Tree, Node } from './Tree.js' import type { HashSet } from './HashSet.js' export type Eq = (a: S, b: S) => boolean @@ -29,6 +29,7 @@ export const depthFirstSearch: Search = ( open: PriorityQueue>, closed: HashSet, ): Promise { + console.log(open) const r = PQ.poll(open) const next = r[0] @@ -42,7 +43,7 @@ export const depthFirstSearch: Search = ( return T.toArray(next) } - const newStates = (await Promise.all(expand(next.value))) + const newStates: Node[] = (await Promise.all(expand(next.value))) .filter((state: S) => !HS.has(eq)(state)(closed)) .map( (state: S): Child => ({ @@ -50,12 +51,12 @@ export const depthFirstSearch: Search = ( value: state, }), ) + .map((child: Child) => T.insert(child)(next)) for (const newState of newStates) { - const tree: Tree = T.insert(newState)(next) nextQueue = PQ.insert({ priority: -newState.key, - data: tree, + data: newState, })(nextQueue) } From d995f89a0d9dd9f06c6ff96f616a74484151fec6 Mon Sep 17 00:00:00 2001 From: benjides Date: Fri, 21 Nov 2025 19:27:32 +0100 Subject: [PATCH 044/118] Map to priorityNodes Tree.Nodes resulting states --- src/DepthFirstSearch.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/DepthFirstSearch.ts b/src/DepthFirstSearch.ts index 7177260..93690d5 100644 --- a/src/DepthFirstSearch.ts +++ b/src/DepthFirstSearch.ts @@ -43,7 +43,9 @@ export const depthFirstSearch: Search = ( return T.toArray(next) } - const newStates: Node[] = (await Promise.all(expand(next.value))) + const newStates: PQ.Node>[] = ( + await Promise.all(expand(next.value)) + ) .filter((state: S) => !HS.has(eq)(state)(closed)) .map( (state: S): Child => ({ @@ -52,12 +54,12 @@ export const depthFirstSearch: Search = ( }), ) .map((child: Child) => T.insert(child)(next)) + .map((node: Node) => + PQ.node((a: Node) => -a.key)(node), + ) for (const newState of newStates) { - nextQueue = PQ.insert({ - priority: -newState.key, - data: newState, - })(nextQueue) + nextQueue = PQ.insert(newState)(nextQueue) } closed = HS.insert(next.value)(closed) From e03592049e101d8b855e5b05d73a4204a7cdabd2 Mon Sep 17 00:00:00 2001 From: benjides Date: Fri, 21 Nov 2025 19:29:10 +0100 Subject: [PATCH 045/118] Create Tree Nodes with inverse key to avoid negating priorityQueue nodes --- src/DepthFirstSearch.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/DepthFirstSearch.ts b/src/DepthFirstSearch.ts index 93690d5..7445b6b 100644 --- a/src/DepthFirstSearch.ts +++ b/src/DepthFirstSearch.ts @@ -49,13 +49,13 @@ export const depthFirstSearch: Search = ( .filter((state: S) => !HS.has(eq)(state)(closed)) .map( (state: S): Child => ({ - key: next.key + 1, + key: next.key - 1, value: state, }), ) .map((child: Child) => T.insert(child)(next)) .map((node: Node) => - PQ.node((a: Node) => -a.key)(node), + PQ.node((a: Node) => a.key)(node), ) for (const newState of newStates) { From 808f043f216f36dcd3038d7c53cab042428f516a Mon Sep 17 00:00:00 2001 From: benjides Date: Sat, 22 Nov 2025 23:44:45 +0100 Subject: [PATCH 046/118] Use Array reduce to insert nodes into PriorityQueue --- src/DepthFirstSearch.ts | 39 +++++++++++++++++++-------------------- 1 file changed, 19 insertions(+), 20 deletions(-) diff --git a/src/DepthFirstSearch.ts b/src/DepthFirstSearch.ts index 7445b6b..e5b6039 100644 --- a/src/DepthFirstSearch.ts +++ b/src/DepthFirstSearch.ts @@ -1,7 +1,7 @@ import * as PQ from './PriorityQueue.js' import * as T from './Tree.js' import * as HS from './HashSet.js' -import type { PriorityQueue } from './PriorityQueue.js' +import { type PriorityQueue } from './PriorityQueue.js' import type { Child, Tree, Node } from './Tree.js' import type { HashSet } from './HashSet.js' @@ -29,41 +29,40 @@ export const depthFirstSearch: Search = ( open: PriorityQueue>, closed: HashSet, ): Promise { - console.log(open) - const r = PQ.poll(open) + const [currentState, priorityQueue] = PQ.poll(open) - const next = r[0] - let nextQueue = r[1] - - if (next === null) { + if (currentState === null) { return [] } - if (eq(goal, next.value)) { - return T.toArray(next) + if (eq(goal, currentState.value)) { + return T.toArray(currentState) } - const newStates: PQ.Node>[] = ( - await Promise.all(expand(next.value)) + const newStates: PriorityQueue> = ( + await Promise.all(expand(currentState.value)) ) .filter((state: S) => !HS.has(eq)(state)(closed)) .map( (state: S): Child => ({ - key: next.key - 1, + key: currentState.key - 1, value: state, }), ) - .map((child: Child) => T.insert(child)(next)) + .map((child: Child) => T.insert(child)(currentState)) .map((node: Node) => - PQ.node((a: Node) => a.key)(node), + PQ.node((treeNode: Node) => treeNode.key)(node), + ) + .reduce( + ( + priorityQueue: PriorityQueue>, + priorityNode: PQ.Node>, + ) => PQ.insert(priorityNode)(priorityQueue), + priorityQueue, ) - for (const newState of newStates) { - nextQueue = PQ.insert(newState)(nextQueue) - } - - closed = HS.insert(next.value)(closed) - return expandRecursively(nextQueue, closed) + closed = HS.insert(currentState.value)(closed) + return expandRecursively(newStates, closed) } const t: T.Tree = T.fromRoot({ From cc6dfe72572365471142e9f2296d2c9dec6328ee Mon Sep 17 00:00:00 2001 From: benjides Date: Sat, 22 Nov 2025 23:55:04 +0100 Subject: [PATCH 047/118] Use namespace types --- src/DepthFirstSearch.ts | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/src/DepthFirstSearch.ts b/src/DepthFirstSearch.ts index e5b6039..7065319 100644 --- a/src/DepthFirstSearch.ts +++ b/src/DepthFirstSearch.ts @@ -1,9 +1,6 @@ import * as PQ from './PriorityQueue.js' import * as T from './Tree.js' import * as HS from './HashSet.js' -import { type PriorityQueue } from './PriorityQueue.js' -import type { Child, Tree, Node } from './Tree.js' -import type { HashSet } from './HashSet.js' export type Eq = (a: S, b: S) => boolean @@ -22,12 +19,12 @@ export const depthFirstSearch: Search = ( eq: Eq, expand: Expand, ): Promise => { - const open: PriorityQueue> = PQ.priorityQueue([]) - const closed: HashSet = HS.empty() + const open: PQ.PriorityQueue> = PQ.priorityQueue([]) + const closed: HS.HashSet = HS.empty() async function expandRecursively( - open: PriorityQueue>, - closed: HashSet, + open: PQ.PriorityQueue>, + closed: HS.HashSet, ): Promise { const [currentState, priorityQueue] = PQ.poll(open) @@ -39,23 +36,23 @@ export const depthFirstSearch: Search = ( return T.toArray(currentState) } - const newStates: PriorityQueue> = ( + const newStates: PQ.PriorityQueue> = ( await Promise.all(expand(currentState.value)) ) .filter((state: S) => !HS.has(eq)(state)(closed)) .map( - (state: S): Child => ({ + (state: S): T.Child => ({ key: currentState.key - 1, value: state, }), ) - .map((child: Child) => T.insert(child)(currentState)) - .map((node: Node) => - PQ.node((treeNode: Node) => treeNode.key)(node), + .map((child: T.Child) => T.insert(child)(currentState)) + .map((node: T.Node) => + PQ.node((treeNode: T.Node) => treeNode.key)(node), ) .reduce( ( - priorityQueue: PriorityQueue>, + priorityQueue: PQ.PriorityQueue>, priorityNode: PQ.Node>, ) => PQ.insert(priorityNode)(priorityQueue), priorityQueue, From d092f93af80f0729d7dbff3b47da9d68a8ecd236 Mon Sep 17 00:00:00 2001 From: benjides Date: Sun, 23 Nov 2025 00:40:03 +0100 Subject: [PATCH 048/118] Add expand constant --- tests/Grid.ts | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/tests/Grid.ts b/tests/Grid.ts index 24c4fb6..3a37d75 100644 --- a/tests/Grid.ts +++ b/tests/Grid.ts @@ -56,10 +56,18 @@ const left: (grid: Grid) => Movement = () => (position: Position) => { } } -export const expandPosition: (grid: Grid) => Expand = - (grid: Grid) => (position: Position) => - [up, down, right, left] - .map((m: (grid: Grid) => Movement) => m(grid)) +export const expand: ( + grid: Grid, +) => (allowedMoves: ((grid: Grid) => Movement)[]) => Expand = + (grid: Grid) => + (allowedMoves: ((grid: Grid) => Movement)[]) => + (position: Position) => + allowedMoves + .map((gridMovement: (grid: Grid) => Movement) => gridMovement(grid)) .map((movement: Movement) => movement(position)) .filter((position: Position | null) => position !== null) .map((position: Position) => Promise.resolve(position)) + +export const expandPosition: (grid: Grid) => Expand = + (grid: Grid) => (position: Position) => + expand(grid)([up, down, right, left])(position) From 9c44205236bf4cd44a2cdc84c333879f4b19872f Mon Sep 17 00:00:00 2001 From: benjides Date: Sun, 23 Nov 2025 00:49:00 +0100 Subject: [PATCH 049/118] test it filters already visited states on expanding --- src/DepthFirstSearch.ts | 2 +- tests/DepthFirstSearch.test.ts | 35 ++++++++++++++++++++++++++++++++++ tests/Grid.ts | 7 ++++++- 3 files changed, 42 insertions(+), 2 deletions(-) diff --git a/src/DepthFirstSearch.ts b/src/DepthFirstSearch.ts index 7065319..700833d 100644 --- a/src/DepthFirstSearch.ts +++ b/src/DepthFirstSearch.ts @@ -31,6 +31,7 @@ export const depthFirstSearch: Search = ( if (currentState === null) { return [] } + closed = HS.insert(currentState.value)(closed) if (eq(goal, currentState.value)) { return T.toArray(currentState) @@ -58,7 +59,6 @@ export const depthFirstSearch: Search = ( priorityQueue, ) - closed = HS.insert(currentState.value)(closed) return expandRecursively(newStates, closed) } diff --git a/tests/DepthFirstSearch.test.ts b/tests/DepthFirstSearch.test.ts index 5a5b264..38cdf66 100644 --- a/tests/DepthFirstSearch.test.ts +++ b/tests/DepthFirstSearch.test.ts @@ -1,10 +1,13 @@ import { describe, it, assert } from 'vitest' import { depthFirstSearch } from '../src/DepthFirstSearch.js' import { + expand, expandPosition, type Grid, type Position, positionEquality, + right, + stall, } from './Grid.js' describe('DepthFirstSearch', () => { @@ -160,4 +163,36 @@ describe('DepthFirstSearch', () => { const expectedSolution: Position[] = [] assert.deepStrictEqual(actualSolution, expectedSolution) }) + + it('filters already visited states on expanding', async () => { + const corridor: Grid = { rows: 1, columns: 3 } + const initial: Position = { + x: 0, + y: 0, + } + const goal: Position = { + x: 2, + y: 0, + } + + const actualSolution = await depthFirstSearch( + initial, + goal, + positionEquality, + expand(corridor)([stall, right]), + ) + + const expectedSolution: Position[] = [ + { + x: 0, + y: 0, + }, + { + x: 1, + y: 0, + }, + { x: 2, y: 0 }, + ] + assert.deepStrictEqual(actualSolution, expectedSolution) + }) }) diff --git a/tests/Grid.ts b/tests/Grid.ts index 3a37d75..9ab3e2c 100644 --- a/tests/Grid.ts +++ b/tests/Grid.ts @@ -33,7 +33,7 @@ const down: (grid: Grid) => Movement = () => (position: Position) => { y: position.y - 1, } } -const right: (grid: Grid) => Movement = +export const right: (grid: Grid) => Movement = (grid: Grid) => (position: Position) => { if (position.x === grid.columns - 1) { return null @@ -56,6 +56,11 @@ const left: (grid: Grid) => Movement = () => (position: Position) => { } } +export const stall: () => Movement = () => (position: Position) => ({ + x: position.x, + y: position.y, +}) + export const expand: ( grid: Grid, ) => (allowedMoves: ((grid: Grid) => Movement)[]) => Expand = From de01f57c69035097354071c7b2618a66c6c08e32 Mon Sep 17 00:00:00 2001 From: benjides Date: Sun, 23 Nov 2025 00:57:44 +0100 Subject: [PATCH 050/118] Use expand for tests and remove unused expandPosition --- tests/DepthFirstSearch.test.ts | 44 ++++++++++++++++------------------ tests/Grid.ts | 25 +++++++++---------- 2 files changed, 31 insertions(+), 38 deletions(-) diff --git a/tests/DepthFirstSearch.test.ts b/tests/DepthFirstSearch.test.ts index 38cdf66..e978cc0 100644 --- a/tests/DepthFirstSearch.test.ts +++ b/tests/DepthFirstSearch.test.ts @@ -1,13 +1,15 @@ import { describe, it, assert } from 'vitest' import { depthFirstSearch } from '../src/DepthFirstSearch.js' import { + down, expand, - expandPosition, type Grid, + left, type Position, positionEquality, right, stall, + up, } from './Grid.js' describe('DepthFirstSearch', () => { @@ -26,7 +28,7 @@ describe('DepthFirstSearch', () => { initial, goal, positionEquality, - expandPosition(grid), + expand(grid)([]), ) const expectedSolution: Position[] = [{ x: 0, y: 0 }] @@ -34,52 +36,52 @@ describe('DepthFirstSearch', () => { }) it('solves after expanding once', async () => { - const grid: Grid = { rows: 2, columns: 1 } + const corridor: Grid = { rows: 1, columns: 2 } const initial: Position = { x: 0, y: 0, } const goal: Position = { - x: 0, - y: 1, + x: 1, + y: 0, } const actualSolution = await depthFirstSearch( initial, goal, positionEquality, - expandPosition(grid), + expand(corridor)([right]), ) const expectedSolution: Position[] = [ { x: 0, y: 0 }, - { x: 0, y: 1 }, + { x: 1, y: 0 }, ] assert.deepStrictEqual(actualSolution, expectedSolution) }) it('solves after expanding twice', async () => { - const grid: Grid = { rows: 3, columns: 1 } + const corridor: Grid = { rows: 1, columns: 3 } const initial: Position = { x: 0, y: 0, } const goal: Position = { - x: 0, - y: 2, + x: 2, + y: 0, } const actualSolution = await depthFirstSearch( initial, goal, positionEquality, - expandPosition(grid), + expand(corridor)([right]), ) const expectedSolution: Position[] = [ { x: 0, y: 0 }, - { x: 0, y: 1 }, - { x: 0, y: 2 }, + { x: 1, y: 0 }, + { x: 2, y: 0 }, ] assert.deepStrictEqual(actualSolution, expectedSolution) }) @@ -99,7 +101,7 @@ describe('DepthFirstSearch', () => { initial, goal, positionEquality, - expandPosition(grid), + expand(grid)([up, down, right, left]), ) const expectedSolution: Position[] = [ @@ -125,7 +127,7 @@ describe('DepthFirstSearch', () => { initial, goal, positionEquality, - expandPosition(grid), + expand(grid)([up, down, right, left]), ) const expectedSolution: Position[] = [ @@ -157,7 +159,7 @@ describe('DepthFirstSearch', () => { initial, goal, positionEquality, - expandPosition(grid), + expand(grid)([up]), ) const expectedSolution: Position[] = [] @@ -183,14 +185,8 @@ describe('DepthFirstSearch', () => { ) const expectedSolution: Position[] = [ - { - x: 0, - y: 0, - }, - { - x: 1, - y: 0, - }, + { x: 0, y: 0 }, + { x: 1, y: 0 }, { x: 2, y: 0 }, ] assert.deepStrictEqual(actualSolution, expectedSolution) diff --git a/tests/Grid.ts b/tests/Grid.ts index 9ab3e2c..9d8df95 100644 --- a/tests/Grid.ts +++ b/tests/Grid.ts @@ -15,16 +15,17 @@ export const positionEquality: Eq = (a: Position, b: Position) => type Movement = (position: Position) => Position | null -const up: (grid: Grid) => Movement = (grid: Grid) => (position: Position) => { - if (position.y === grid.rows - 1) { - return null - } - return { - x: position.x, - y: position.y + 1, +export const up: (grid: Grid) => Movement = + (grid: Grid) => (position: Position) => { + if (position.y === grid.rows - 1) { + return null + } + return { + x: position.x, + y: position.y + 1, + } } -} -const down: (grid: Grid) => Movement = () => (position: Position) => { +export const down: (grid: Grid) => Movement = () => (position: Position) => { if (position.y === 0) { return null } @@ -45,7 +46,7 @@ export const right: (grid: Grid) => Movement = } } -const left: (grid: Grid) => Movement = () => (position: Position) => { +export const left: (grid: Grid) => Movement = () => (position: Position) => { if (position.x === 0) { return null } @@ -72,7 +73,3 @@ export const expand: ( .map((movement: Movement) => movement(position)) .filter((position: Position | null) => position !== null) .map((position: Position) => Promise.resolve(position)) - -export const expandPosition: (grid: Grid) => Expand = - (grid: Grid) => (position: Position) => - expand(grid)([up, down, right, left])(position) From cfd65d33c3c09dce556353683460fe32da368e4a Mon Sep 17 00:00:00 2001 From: benjides Date: Sun, 23 Nov 2025 10:23:35 +0100 Subject: [PATCH 051/118] Typo --- src/Tree.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Tree.ts b/src/Tree.ts index 34d8736..200c31d 100644 --- a/src/Tree.ts +++ b/src/Tree.ts @@ -72,7 +72,7 @@ export const insert: ( }) /** - * Returns an ordered list of values from Roo for a given Node iterating over its parents recursively + * Returns an ordered list of values from Root for a given Node iterating over its parents recursively * * @example * ```ts From d16a9c5a94c4af2f2bede556c33db9ed10494a62 Mon Sep 17 00:00:00 2001 From: benjides Date: Sun, 23 Nov 2025 10:42:20 +0100 Subject: [PATCH 052/118] Remove sort priority nodes on creation unused use case --- src/PriorityQueue.ts | 3 +-- tests/PriorityQueue.test.ts | 27 --------------------------- 2 files changed, 1 insertion(+), 29 deletions(-) diff --git a/src/PriorityQueue.ts b/src/PriorityQueue.ts index 90c3206..caff47f 100644 --- a/src/PriorityQueue.ts +++ b/src/PriorityQueue.ts @@ -12,8 +12,7 @@ export const node: (f: (e: T) => number) => (e: T) => Node = data: e, }) -export const priorityQueue = (data: Node[]) => - data.sort((a: Node, b: Node) => a.priority - b.priority) +export const priorityQueue = (data: Node[]) => data export const values = (priorityQueue: PriorityQueue) => priorityQueue.map((node: Node) => node.data) diff --git a/tests/PriorityQueue.test.ts b/tests/PriorityQueue.test.ts index 68ec57c..65dc059 100644 --- a/tests/PriorityQueue.test.ts +++ b/tests/PriorityQueue.test.ts @@ -29,33 +29,6 @@ describe('PriorityQueue', () => { assert.deepStrictEqual(priorityQueueValues, []) }) - - it('sorts Nodes on creation', () => { - const persons: Person[] = [ - { - age: 34, - name: 'John', - }, - { - age: 27, - name: 'Jane', - }, - ] - - const priorityQueueValues = values(personsPriorityQueue(persons)) - - const expectedPriorityQueueValues: Person[] = [ - { - age: 27, - name: 'Jane', - }, - { - age: 34, - name: 'John', - }, - ] - assert.deepStrictEqual(priorityQueueValues, expectedPriorityQueueValues) - }) }) describe('insert', () => { it('inserts value at last position', () => { From 68440ce5d72984c8eacbe93fb83829c3a39e2101 Mon Sep 17 00:00:00 2001 From: benjides Date: Sun, 23 Nov 2025 10:52:41 +0100 Subject: [PATCH 053/118] Change PriorityQueue.insert signature to receive priority and data pairs --- src/DepthFirstSearch.ts | 15 +++------------ src/PriorityQueue.ts | 7 ++++++- tests/PriorityQueue.test.ts | 28 ++++++++++++++++------------ 3 files changed, 25 insertions(+), 25 deletions(-) diff --git a/src/DepthFirstSearch.ts b/src/DepthFirstSearch.ts index 700833d..82f33ea 100644 --- a/src/DepthFirstSearch.ts +++ b/src/DepthFirstSearch.ts @@ -48,14 +48,11 @@ export const depthFirstSearch: Search = ( }), ) .map((child: T.Child) => T.insert(child)(currentState)) - .map((node: T.Node) => - PQ.node((treeNode: T.Node) => treeNode.key)(node), - ) .reduce( ( priorityQueue: PQ.PriorityQueue>, - priorityNode: PQ.Node>, - ) => PQ.insert(priorityNode)(priorityQueue), + treeBranch: T.Tree, + ) => PQ.insert(treeBranch.key, treeBranch)(priorityQueue), priorityQueue, ) @@ -67,11 +64,5 @@ export const depthFirstSearch: Search = ( value: initial, }) - return expandRecursively( - PQ.insert({ - data: t, - priority: 0, - })(open), - closed, - ) + return expandRecursively(PQ.insert(0, t)(open), closed) } diff --git a/src/PriorityQueue.ts b/src/PriorityQueue.ts index caff47f..c473d08 100644 --- a/src/PriorityQueue.ts +++ b/src/PriorityQueue.ts @@ -18,8 +18,13 @@ export const values = (priorityQueue: PriorityQueue) => priorityQueue.map((node: Node) => node.data) export const insert = - (nodeToInsert: Node) => + (priority: number, data: T) => (priorityQueue: PriorityQueue) => { + const nodeToInsert: Node = { + priority: priority, + data: data, + } + const index: number = priorityQueue.findIndex( (node: Node) => nodeToInsert.priority < node.priority, ) diff --git a/tests/PriorityQueue.test.ts b/tests/PriorityQueue.test.ts index 65dc059..b22ade2 100644 --- a/tests/PriorityQueue.test.ts +++ b/tests/PriorityQueue.test.ts @@ -44,9 +44,10 @@ describe('PriorityQueue', () => { name: 'John', } - const priorityQueueValues = insert(personNode(person))( - personsPriorityQueue(persons), - ) + const priorityQueueValues = insert( + person.age, + person, + )(personsPriorityQueue(persons)) const expectedPriorityQueueValues: Person[] = [ { @@ -77,9 +78,10 @@ describe('PriorityQueue', () => { name: 'Jane', } - const priorityQueueValues = insert(personNode(person))( - personsPriorityQueue(persons), - ) + const priorityQueueValues = insert( + person.age, + person, + )(personsPriorityQueue(persons)) const expectedPriorityQueueValues: Person[] = [ { @@ -114,9 +116,10 @@ describe('PriorityQueue', () => { name: 'Sally', } - const priorityQueueValues = insert(personNode(person))( - personsPriorityQueue(persons), - ) + const priorityQueueValues = insert( + person.age, + person, + )(personsPriorityQueue(persons)) const expectedPriorityQueueValues: Person[] = [ { @@ -155,9 +158,10 @@ describe('PriorityQueue', () => { name: 'Jane', } - const priorityQueueValues = insert(personNode(person))( - personsPriorityQueue(persons), - ) + const priorityQueueValues = insert( + person.age, + person, + )(personsPriorityQueue(persons)) const expectedPriorityQueueValues: Person[] = [ { From 6cab23733b19ad047adfcc163a56b005345dd0e6 Mon Sep 17 00:00:00 2001 From: benjides Date: Sun, 23 Nov 2025 11:41:49 +0100 Subject: [PATCH 054/118] Use manual inserts on PriorityQueue for tests --- tests/PriorityQueue.test.ts | 153 +++++++++++++----------------------- 1 file changed, 56 insertions(+), 97 deletions(-) diff --git a/tests/PriorityQueue.test.ts b/tests/PriorityQueue.test.ts index b22ade2..5bbd934 100644 --- a/tests/PriorityQueue.test.ts +++ b/tests/PriorityQueue.test.ts @@ -1,53 +1,32 @@ import { assert, describe, it } from 'vitest' -import { - insert, - node, - poll, - priorityQueue, - values, - type Node, - type PriorityQueue, -} from '../src/PriorityQueue.js' +import { insert, poll, priorityQueue, values } from '../src/PriorityQueue.js' type Person = { age: number name: string } -const priority = (person: Person) => person.age - -const personNode: (e: Person) => Node = node(priority) - -const personsPriorityQueue: (persons: Person[]) => PriorityQueue = ( - persons: Person[], -) => priorityQueue(persons.map(personNode)) - describe('PriorityQueue', () => { describe('constructor', () => { it('creates empty', () => { - const priorityQueueValues = values(personsPriorityQueue([])) + const priorityQueueValues = values(priorityQueue([])) assert.deepStrictEqual(priorityQueueValues, []) }) }) describe('insert', () => { it('inserts value at last position', () => { - const persons: Person[] = [ - { - age: 27, - name: 'Jane', - }, - ] + const jane: Person = { + age: 27, + name: 'Jane', + } + const personsPriorityQueue = insert(jane.age, jane)(priorityQueue([])) - const person: Person = { + const john: Person = { age: 34, name: 'John', } - - const priorityQueueValues = insert( - person.age, - person, - )(personsPriorityQueue(persons)) + const priorityQueueValues = insert(john.age, john)(personsPriorityQueue) const expectedPriorityQueueValues: Person[] = [ { @@ -66,22 +45,17 @@ describe('PriorityQueue', () => { }) it('inserts value at first position', () => { - const persons: Person[] = [ - { - age: 34, - name: 'John', - }, - ] + const john: Person = { + age: 34, + name: 'John', + } + const personsPriorityQueue = insert(john.age, john)(priorityQueue([])) - const person: Person = { + const jane: Person = { age: 27, name: 'Jane', } - - const priorityQueueValues = insert( - person.age, - person, - )(personsPriorityQueue(persons)) + const priorityQueueValues = insert(jane.age, jane)(personsPriorityQueue) const expectedPriorityQueueValues: Person[] = [ { @@ -100,26 +74,24 @@ describe('PriorityQueue', () => { }) it('inserts value at the middle position', () => { - const persons: Person[] = [ - { - age: 27, - name: 'Jane', - }, - { - age: 34, - name: 'John', - }, - ] + const jane: Person = { + age: 27, + name: 'Jane', + } + const john: Person = { + age: 34, + name: 'John', + } + const personsPriorityQueue = insert( + jane.age, + jane, + )(insert(john.age, john)(priorityQueue([]))) - const person: Person = { + const sally: Person = { age: 28, name: 'Sally', } - - const priorityQueueValues = insert( - person.age, - person, - )(personsPriorityQueue(persons)) + const priorityQueueValues = insert(sally.age, sally)(personsPriorityQueue) const expectedPriorityQueueValues: Person[] = [ { @@ -142,38 +114,25 @@ describe('PriorityQueue', () => { }) it('inserts after in case of same priority', () => { - const persons: Person[] = [ - { - age: 20, - name: 'John', - }, - { - age: 28, - name: 'Sally', - }, - ] - - const person = { - age: 20, + const jane: Person = { + age: 27, name: 'Jane', } + const personsPriorityQueue = insert(jane.age, jane)(priorityQueue([])) - const priorityQueueValues = insert( - person.age, - person, - )(personsPriorityQueue(persons)) + const sally = { + age: 27, + name: 'Sally', + } + const priorityQueueValues = insert(sally.age, sally)(personsPriorityQueue) const expectedPriorityQueueValues: Person[] = [ { - age: 20, - name: 'John', - }, - { - age: 20, + age: 27, name: 'Jane', }, { - age: 28, + age: 27, name: 'Sally', }, ] @@ -185,18 +144,20 @@ describe('PriorityQueue', () => { }) describe('poll', () => { it('polls non empty PriorityQueue', () => { - const persons: Person[] = [ - { - age: 27, - name: 'Jane', - }, - { - age: 34, - name: 'John', - }, - ] + const jane: Person = { + age: 27, + name: 'Jane', + } + const john: Person = { + age: 34, + name: 'John', + } + const personsPriorityQueue = insert( + jane.age, + jane, + )(insert(john.age, john)(priorityQueue([]))) - const [person, priorityQueue] = poll(personsPriorityQueue(persons)) + const [person, modifiedQueue] = poll(personsPriorityQueue) const expectedPerson: Person = { age: 27, @@ -209,16 +170,14 @@ describe('PriorityQueue', () => { }, ] assert.deepStrictEqual(person, expectedPerson) - assert.deepStrictEqual(values(priorityQueue), expectedPriorityQueueValues) + assert.deepStrictEqual(values(modifiedQueue), expectedPriorityQueueValues) }) it('polls empty PriorityQueue', () => { - const persons: Person[] = [] - - const [person, priorityQueue] = poll(personsPriorityQueue(persons)) + const [person, modifiedQueue] = poll(priorityQueue([])) assert.isNull(person) - assert.deepStrictEqual(priorityQueue, []) + assert.deepStrictEqual(modifiedQueue, []) }) }) }) From e0d824dfe2f65f55661702ef2750c004f1580b26 Mon Sep 17 00:00:00 2001 From: benjides Date: Sun, 23 Nov 2025 11:48:35 +0100 Subject: [PATCH 055/118] Remove unused symbol and add types to exported symbols --- src/PriorityQueue.ts | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/src/PriorityQueue.ts b/src/PriorityQueue.ts index c473d08..13911ab 100644 --- a/src/PriorityQueue.ts +++ b/src/PriorityQueue.ts @@ -5,19 +5,12 @@ export type Node = { data: T } -export const node: (f: (e: T) => number) => (e: T) => Node = - (f: (e: T) => number) => - (e: T) => ({ - priority: f(e), - data: e, - }) - -export const priorityQueue = (data: Node[]) => data +export const priorityQueue: (data: Node[]) => PriorityQueue = (data: Node[]) => data export const values = (priorityQueue: PriorityQueue) => priorityQueue.map((node: Node) => node.data) -export const insert = +export const insert: (priority: number, data: T) => (priorityQueue: PriorityQueue) => PriorityQueue = (priority: number, data: T) => (priorityQueue: PriorityQueue) => { const nodeToInsert: Node = { From ce4c5891a9707abf0c54f941e5f6fb1c9a647c7b Mon Sep 17 00:00:00 2001 From: benjides Date: Sun, 23 Nov 2025 17:00:33 +0100 Subject: [PATCH 056/118] Add empty constructor --- src/DepthFirstSearch.ts | 2 +- src/PriorityQueue.ts | 11 +++++++++-- tests/PriorityQueue.test.ts | 16 ++++++++-------- 3 files changed, 18 insertions(+), 11 deletions(-) diff --git a/src/DepthFirstSearch.ts b/src/DepthFirstSearch.ts index 82f33ea..4f1e8a0 100644 --- a/src/DepthFirstSearch.ts +++ b/src/DepthFirstSearch.ts @@ -19,7 +19,7 @@ export const depthFirstSearch: Search = ( eq: Eq, expand: Expand, ): Promise => { - const open: PQ.PriorityQueue> = PQ.priorityQueue([]) + const open: PQ.PriorityQueue> = PQ.empty() const closed: HS.HashSet = HS.empty() async function expandRecursively( diff --git a/src/PriorityQueue.ts b/src/PriorityQueue.ts index 13911ab..346d328 100644 --- a/src/PriorityQueue.ts +++ b/src/PriorityQueue.ts @@ -5,12 +5,19 @@ export type Node = { data: T } -export const priorityQueue: (data: Node[]) => PriorityQueue = (data: Node[]) => data +export const priorityQueue: (data: Node[]) => PriorityQueue = ( + data: Node[], +) => data + +export const empty: () => PriorityQueue = () => [] export const values = (priorityQueue: PriorityQueue) => priorityQueue.map((node: Node) => node.data) -export const insert: (priority: number, data: T) => (priorityQueue: PriorityQueue) => PriorityQueue = +export const insert: ( + priority: number, + data: T, +) => (priorityQueue: PriorityQueue) => PriorityQueue = (priority: number, data: T) => (priorityQueue: PriorityQueue) => { const nodeToInsert: Node = { diff --git a/tests/PriorityQueue.test.ts b/tests/PriorityQueue.test.ts index 5bbd934..69a3bc8 100644 --- a/tests/PriorityQueue.test.ts +++ b/tests/PriorityQueue.test.ts @@ -1,5 +1,5 @@ import { assert, describe, it } from 'vitest' -import { insert, poll, priorityQueue, values } from '../src/PriorityQueue.js' +import { empty, insert, poll, values } from '../src/PriorityQueue.js' type Person = { age: number @@ -9,7 +9,7 @@ type Person = { describe('PriorityQueue', () => { describe('constructor', () => { it('creates empty', () => { - const priorityQueueValues = values(priorityQueue([])) + const priorityQueueValues = values(empty()) assert.deepStrictEqual(priorityQueueValues, []) }) @@ -20,7 +20,7 @@ describe('PriorityQueue', () => { age: 27, name: 'Jane', } - const personsPriorityQueue = insert(jane.age, jane)(priorityQueue([])) + const personsPriorityQueue = insert(jane.age, jane)(empty()) const john: Person = { age: 34, @@ -49,7 +49,7 @@ describe('PriorityQueue', () => { age: 34, name: 'John', } - const personsPriorityQueue = insert(john.age, john)(priorityQueue([])) + const personsPriorityQueue = insert(john.age, john)(empty()) const jane: Person = { age: 27, @@ -85,7 +85,7 @@ describe('PriorityQueue', () => { const personsPriorityQueue = insert( jane.age, jane, - )(insert(john.age, john)(priorityQueue([]))) + )(insert(john.age, john)(empty())) const sally: Person = { age: 28, @@ -118,7 +118,7 @@ describe('PriorityQueue', () => { age: 27, name: 'Jane', } - const personsPriorityQueue = insert(jane.age, jane)(priorityQueue([])) + const personsPriorityQueue = insert(jane.age, jane)(empty()) const sally = { age: 27, @@ -155,7 +155,7 @@ describe('PriorityQueue', () => { const personsPriorityQueue = insert( jane.age, jane, - )(insert(john.age, john)(priorityQueue([]))) + )(insert(john.age, john)(empty())) const [person, modifiedQueue] = poll(personsPriorityQueue) @@ -174,7 +174,7 @@ describe('PriorityQueue', () => { }) it('polls empty PriorityQueue', () => { - const [person, modifiedQueue] = poll(priorityQueue([])) + const [person, modifiedQueue] = poll(empty()) assert.isNull(person) assert.deepStrictEqual(modifiedQueue, []) From 3971d5510f561468cfa114aac44486f05a2466ca Mon Sep 17 00:00:00 2001 From: benjides Date: Sun, 23 Nov 2025 17:02:40 +0100 Subject: [PATCH 057/118] Remove unused symbol --- src/PriorityQueue.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/PriorityQueue.ts b/src/PriorityQueue.ts index 346d328..c394a09 100644 --- a/src/PriorityQueue.ts +++ b/src/PriorityQueue.ts @@ -5,10 +5,6 @@ export type Node = { data: T } -export const priorityQueue: (data: Node[]) => PriorityQueue = ( - data: Node[], -) => data - export const empty: () => PriorityQueue = () => [] export const values = (priorityQueue: PriorityQueue) => From 76dec6a39bbd0202540c42f07e477028e34e32a7 Mon Sep 17 00:00:00 2001 From: benjides Date: Sun, 23 Nov 2025 17:08:47 +0100 Subject: [PATCH 058/118] Add PriorityQueue.of constructor method --- src/DepthFirstSearch.ts | 3 +-- src/PriorityQueue.ts | 5 +++++ tests/PriorityQueue.test.ts | 26 +++++++++++++++++++++++++- 3 files changed, 31 insertions(+), 3 deletions(-) diff --git a/src/DepthFirstSearch.ts b/src/DepthFirstSearch.ts index 4f1e8a0..04a48e9 100644 --- a/src/DepthFirstSearch.ts +++ b/src/DepthFirstSearch.ts @@ -19,7 +19,6 @@ export const depthFirstSearch: Search = ( eq: Eq, expand: Expand, ): Promise => { - const open: PQ.PriorityQueue> = PQ.empty() const closed: HS.HashSet = HS.empty() async function expandRecursively( @@ -64,5 +63,5 @@ export const depthFirstSearch: Search = ( value: initial, }) - return expandRecursively(PQ.insert(0, t)(open), closed) + return expandRecursively(PQ.of(0, t), closed) } diff --git a/src/PriorityQueue.ts b/src/PriorityQueue.ts index c394a09..42f8aa7 100644 --- a/src/PriorityQueue.ts +++ b/src/PriorityQueue.ts @@ -7,6 +7,11 @@ export type Node = { export const empty: () => PriorityQueue = () => [] +export const of: (priority: number, data: T) => PriorityQueue = ( + priority: number, + data: T, +) => insert(priority, data)(empty()) + export const values = (priorityQueue: PriorityQueue) => priorityQueue.map((node: Node) => node.data) diff --git a/tests/PriorityQueue.test.ts b/tests/PriorityQueue.test.ts index 69a3bc8..5974617 100644 --- a/tests/PriorityQueue.test.ts +++ b/tests/PriorityQueue.test.ts @@ -1,5 +1,12 @@ import { assert, describe, it } from 'vitest' -import { empty, insert, poll, values } from '../src/PriorityQueue.js' +import { + empty, + insert, + of, + poll, + type PriorityQueue, + values, +} from '../src/PriorityQueue.js' type Person = { age: number @@ -13,6 +20,23 @@ describe('PriorityQueue', () => { assert.deepStrictEqual(priorityQueueValues, []) }) + + it('creates for element', () => { + const jane: Person = { + age: 27, + name: 'Jane', + } + + const priorityQueue: PriorityQueue = of(jane.age, jane) + + const expectedPriorityQueueValues: Person[] = [ + { + age: 27, + name: 'Jane', + }, + ] + assert.deepStrictEqual(values(priorityQueue), expectedPriorityQueueValues) + }) }) describe('insert', () => { it('inserts value at last position', () => { From 70b66b2fdc7a435f2b08cfac227963000d806841 Mon Sep 17 00:00:00 2001 From: benjides Date: Sun, 23 Nov 2025 17:09:35 +0100 Subject: [PATCH 059/118] Remove unused export --- src/PriorityQueue.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PriorityQueue.ts b/src/PriorityQueue.ts index 42f8aa7..e8c1a7c 100644 --- a/src/PriorityQueue.ts +++ b/src/PriorityQueue.ts @@ -1,6 +1,6 @@ export type PriorityQueue = Node[] -export type Node = { +type Node = { priority: number data: T } From 187c1e397b33c531672bae7bfb4d3eb6bc252f2d Mon Sep 17 00:00:00 2001 From: benjides Date: Sun, 23 Nov 2025 17:33:15 +0100 Subject: [PATCH 060/118] Add docs --- src/PriorityQueue.ts | 66 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/src/PriorityQueue.ts b/src/PriorityQueue.ts index e8c1a7c..5466e72 100644 --- a/src/PriorityQueue.ts +++ b/src/PriorityQueue.ts @@ -1,3 +1,12 @@ +/** + * Naive implementation of a PriorityQueue using sorted array as internal data structure + */ + +/** + * Queue containing arbitrary data sorted by ascending priority + * + * @category model + */ export type PriorityQueue = Node[] type Node = { @@ -5,16 +14,54 @@ type Node = { data: T } +/** + * Creates an empty PriorityQueue + * + * @category constructors + * + * @example + * ```ts + * assert.deepStrictEqual(values(empty()), []) + * ``` + */ export const empty: () => PriorityQueue = () => [] +/** + * Creates a PriorityQueue for a given priority and element + * + * @category constructors + * + * @example + * ```ts + * assert.deepStrictEqual(values(of(1, 'Hello World!'), ['Hello World!']) + * ``` + */ export const of: (priority: number, data: T) => PriorityQueue = ( priority: number, data: T, ) => insert(priority, data)(empty()) +/** + * Converts a given PriorityQueue into an array containing current values maintaining priority order + * + * @category conversions + * + * @example + * ```ts + * assert.deepStrictEqual(values(of(1, 'Hello World!'), ['Hello World!']) + * ``` + */ export const values = (priorityQueue: PriorityQueue) => priorityQueue.map((node: Node) => node.data) +/** + * Inserts an element with priority for a given PriorityQueue + * + * @example + * ```ts + * assert.deepStrictEqual(values(insert(1, 'Hello World!')(empty()), ['Hello World!']) + * ``` + */ export const insert: ( priority: number, data: T, @@ -41,6 +88,25 @@ export const insert: ( ] } +/** + * Returns the next element in the queue and the resulting PriorityQueue if possible + * + * @example + * ```ts + * const [element, priorityQueue] = poll(of(1, 'Hello World!')) + * + * assert.deepStrictEqual(element, 'Hello World!') + * assert.deepStrictEqual(priorityQueue, []) + * ``` + * + * @example + * ```ts + * const [element, priorityQueue] = poll(empty()) + * + * assert.isNull(element) + * assert.deepStrictEqual(priorityQueue, []) + * ``` + */ export const poll: ( priorityQueue: PriorityQueue, ) => [T | null, PriorityQueue] = (priorityQueue: PriorityQueue) => [ From 0629eac46a4574a99595ce8859522a601582c4fd Mon Sep 17 00:00:00 2001 From: benjides Date: Sun, 23 Nov 2025 18:00:07 +0100 Subject: [PATCH 061/118] Remove extension from imported files --- src/DepthFirstSearch.ts | 6 +++--- tests/DepthFirstSearch.test.ts | 4 ++-- tests/Grid.ts | 2 +- tests/HashSet.test.ts | 2 +- tests/PriorityQueue.test.ts | 2 +- tests/Tree.test.ts | 2 +- tsconfig.json | 5 +++-- 7 files changed, 12 insertions(+), 11 deletions(-) diff --git a/src/DepthFirstSearch.ts b/src/DepthFirstSearch.ts index 04a48e9..df8472e 100644 --- a/src/DepthFirstSearch.ts +++ b/src/DepthFirstSearch.ts @@ -1,6 +1,6 @@ -import * as PQ from './PriorityQueue.js' -import * as T from './Tree.js' -import * as HS from './HashSet.js' +import * as PQ from './PriorityQueue' +import * as T from './Tree' +import * as HS from './HashSet' export type Eq = (a: S, b: S) => boolean diff --git a/tests/DepthFirstSearch.test.ts b/tests/DepthFirstSearch.test.ts index e978cc0..0ff45b4 100644 --- a/tests/DepthFirstSearch.test.ts +++ b/tests/DepthFirstSearch.test.ts @@ -1,5 +1,5 @@ import { describe, it, assert } from 'vitest' -import { depthFirstSearch } from '../src/DepthFirstSearch.js' +import { depthFirstSearch } from '../src/DepthFirstSearch' import { down, expand, @@ -10,7 +10,7 @@ import { right, stall, up, -} from './Grid.js' +} from './Grid' describe('DepthFirstSearch', () => { it('solves when initial and goal are equals', async () => { diff --git a/tests/Grid.ts b/tests/Grid.ts index 9d8df95..62641dc 100644 --- a/tests/Grid.ts +++ b/tests/Grid.ts @@ -1,4 +1,4 @@ -import type { Eq, Expand } from '../src/DepthFirstSearch.js' +import type { Eq, Expand } from '../src/DepthFirstSearch' export type Grid = { rows: number diff --git a/tests/HashSet.test.ts b/tests/HashSet.test.ts index 712b634..9775558 100644 --- a/tests/HashSet.test.ts +++ b/tests/HashSet.test.ts @@ -1,6 +1,6 @@ import { assert, describe, it } from 'vitest' -import { empty, has, insert } from '../src/HashSet.js' +import { empty, has, insert } from '../src/HashSet' type Vector = { x: number diff --git a/tests/PriorityQueue.test.ts b/tests/PriorityQueue.test.ts index 5974617..bfac55f 100644 --- a/tests/PriorityQueue.test.ts +++ b/tests/PriorityQueue.test.ts @@ -6,7 +6,7 @@ import { poll, type PriorityQueue, values, -} from '../src/PriorityQueue.js' +} from '../src/PriorityQueue' type Person = { age: number diff --git a/tests/Tree.test.ts b/tests/Tree.test.ts index 99ee9d5..89f418e 100644 --- a/tests/Tree.test.ts +++ b/tests/Tree.test.ts @@ -1,5 +1,5 @@ import { assert, describe, it } from 'vitest' -import { insert, toArray, type Tree, fromRoot } from '../src/Tree.js' +import { insert, toArray, type Tree, fromRoot } from '../src/Tree' describe('Tree', () => { describe('constructor', () => { diff --git a/tsconfig.json b/tsconfig.json index d22d4a3..905c65b 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -7,7 +7,8 @@ // Environment Settings // See also https://aka.ms/tsconfig/module - "module": "nodenext", + "module": "commonjs", + "moduleResolution": "bundler", "target": "esnext", "types": [], // For nodejs: @@ -35,7 +36,7 @@ // Recommended Options "strict": true, "jsx": "react-jsx", - "verbatimModuleSyntax": true, + "verbatimModuleSyntax": false, "isolatedModules": true, "noUncheckedSideEffectImports": true, "moduleDetection": "force", From 466e0057179e5673da79e9d9a91602cb88e8685f Mon Sep 17 00:00:00 2001 From: benjides Date: Sun, 23 Nov 2025 18:43:01 +0100 Subject: [PATCH 062/118] Add docs --- src/HashSet.ts | 45 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 43 insertions(+), 2 deletions(-) diff --git a/src/HashSet.ts b/src/HashSet.ts index 8126b53..da34a19 100644 --- a/src/HashSet.ts +++ b/src/HashSet.ts @@ -1,20 +1,61 @@ +/** + * HashSet containing arbitrary data + * + * @category model + */ export type HashSet = T[] +/** + * Given two elements of the same type, checks if they are essentially the same + * + * @category model + */ export type Eq = (x: T, y: T) => boolean +/** + * Creates an empty HashSet + * + * @category constructor + * + * @example + * ```ts + * assert.deepStrictEqual(empty(), [2]) + * ``` + */ export const empty: () => HashSet = () => [] /** - * Inserts an element T for a given HashSet + * Inserts an element for a given HashSet. Does not check if the element is already present * * @example - * + * ```ts * assert.deepStrictEqual(insert(2)(empty()), [2]) + * ``` */ export const insert: (element: T) => (hashSet: HashSet) => HashSet = (element: T) => (hashSet: HashSet) => [...hashSet, element] +/** + * Checks if an element is present for a given Eq and a given HashSet + * + * @example + * ```ts + * const hashSet : hashSet = insert(2)(empty()) + * const eq : Eq = (x: number, y: number) => x === y + * + * assert.isTrue(has(eq)(2)(hashSet)) + * ``` + * @example + * ```ts + * const hashSet : hashSet = insert(2)(empty()) + * const eq : Eq = (x: number, y: number) => x === y + * + * assert.isFalse(has(eq)(87557)(hashSet)) + * ``` + * + * @see Eq + */ export const has: ( eq: Eq, ) => (element: T) => (hashSet: HashSet) => boolean = From 1604b99068a01e7625ca9c7c6c500eb60656edba Mon Sep 17 00:00:00 2001 From: benjides Date: Sun, 23 Nov 2025 18:46:19 +0100 Subject: [PATCH 063/118] Fine tune types --- src/Tree.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Tree.ts b/src/Tree.ts index 200c31d..94fef43 100644 --- a/src/Tree.ts +++ b/src/Tree.ts @@ -63,12 +63,12 @@ export const fromRoot: (root: Root) => Tree = ( */ export const insert: ( child: Child, -) => (node: Node) => Node = +) => (tree: Tree) => Tree = (child: Child) => - (node: Node) => ({ + (tree: Tree) => ({ key: child.key, value: child.value, - parent: node, + parent: tree, }) /** @@ -79,7 +79,7 @@ export const insert: ( * assert.deepStrictEqual(toArray(fromRoot({ key: 1, value: 1 })), [1]) * ``` */ -export const toArray: (node: Node) => V[] = ( - node: Node, +export const toArray: (tree: Tree) => V[] = ( + tree: Tree, ) => - node.parent === null ? [node.value] : [...toArray(node.parent), node.value] + tree.parent === null ? [tree.value] : [...toArray(tree.parent), tree.value] From d20acee2ef4415296451d05c7b9ea316cb2e0b3f Mon Sep 17 00:00:00 2001 From: benjides Date: Sun, 23 Nov 2025 18:56:50 +0100 Subject: [PATCH 064/118] Remove Child and Root types from Tree --- src/DepthFirstSearch.ts | 12 +++------- src/Tree.ts | 49 +++++++++++++---------------------------- tests/Tree.test.ts | 33 +++++---------------------- 3 files changed, 24 insertions(+), 70 deletions(-) diff --git a/src/DepthFirstSearch.ts b/src/DepthFirstSearch.ts index df8472e..5a9ee82 100644 --- a/src/DepthFirstSearch.ts +++ b/src/DepthFirstSearch.ts @@ -41,12 +41,9 @@ export const depthFirstSearch: Search = ( ) .filter((state: S) => !HS.has(eq)(state)(closed)) .map( - (state: S): T.Child => ({ - key: currentState.key - 1, - value: state, - }), + (state: S): T.Tree => + T.insert(currentState.key - 1, state)(currentState), ) - .map((child: T.Child) => T.insert(child)(currentState)) .reduce( ( priorityQueue: PQ.PriorityQueue>, @@ -58,10 +55,7 @@ export const depthFirstSearch: Search = ( return expandRecursively(newStates, closed) } - const t: T.Tree = T.fromRoot({ - key: 0, - value: initial, - }) + const t: T.Tree = T.fromRoot(0, initial) return expandRecursively(PQ.of(0, t), closed) } diff --git a/src/Tree.ts b/src/Tree.ts index 94fef43..4bf655d 100644 --- a/src/Tree.ts +++ b/src/Tree.ts @@ -16,67 +16,48 @@ export type Node = { */ export type Tree = Node -/** - * Particular case of a Node without parent pointer - * - * @see Tree - * @see Node - */ -export type Root = Omit, 'parent'> - -/** - * Particular case of a Node without parent pointer - * - * @see Tree - * @see Node - */ -export type Child = Omit, 'parent'> - /** * Creates a Tree for a given Root * * @example * ```ts - * assert.deepStrictEqual(toArray(fromRoot({ key: 1, value: 1 })), [1]) + * assert.deepStrictEqual(toArray(fromRoot(1, 1)), [1]) * ``` - * - * @see Root */ -export const fromRoot: (root: Root) => Tree = ( - root: Root, +export const fromRoot: (key: K, value: V) => Tree = ( + key: K, + value: V, ): Tree => ({ - key: root.key, - value: root.value, + key: key, + value: value, parent: null, }) /** - * Inserts a Child for a given Node and returns a Node pointing to the inserted Child + * Inserts a child for a given Tree and returns a Tree pointing to the inserted child * * @example * ```ts - * assert.deepStrictEqual(toArray(insert({ key: 1, value: 1 })(fromRoot({ key: 1, value: 1 }))), [1, 2]) + * assert.deepStrictEqual(toArray(insert(2, 2)(fromRoot(1, 1))), [1, 2]) * ``` - * - * @see Node - * @see Child */ export const insert: ( - child: Child, + key: K, + value: V, ) => (tree: Tree) => Tree = - (child: Child) => + (key: K, value: V) => (tree: Tree) => ({ - key: child.key, - value: child.value, + key: key, + value: value, parent: tree, }) /** - * Returns an ordered list of values from Root for a given Node iterating over its parents recursively + * Returns an ordered list of values from Root for a given Tree iterating over its parents recursively * * @example * ```ts - * assert.deepStrictEqual(toArray(fromRoot({ key: 1, value: 1 })), [1]) + * assert.deepStrictEqual(toArray(fromRoot(1, 1)), [1]) * ``` */ export const toArray: (tree: Tree) => V[] = ( diff --git a/tests/Tree.test.ts b/tests/Tree.test.ts index 89f418e..3a3c99e 100644 --- a/tests/Tree.test.ts +++ b/tests/Tree.test.ts @@ -4,10 +4,7 @@ import { insert, toArray, type Tree, fromRoot } from '../src/Tree' describe('Tree', () => { describe('constructor', () => { it('creates for Tree Root', () => { - const t: Tree = fromRoot({ - key: 1, - value: 1, - }) + const t: Tree = fromRoot(1, 1) const expectedValues = [1] assert.deepStrictEqual(toArray(t), expectedValues) @@ -16,36 +13,18 @@ describe('Tree', () => { describe('insert', () => { it('inserts value', () => { - const childInsert = insert({ - key: 2, - value: 2, - }) + const childInsert = insert(2, 2) - const t: Tree = childInsert( - fromRoot({ key: 1, value: 1 }), - ) + const t: Tree = childInsert(fromRoot(1, 1)) const expectedValues = [1, 2] assert.deepStrictEqual(toArray(t), expectedValues) }) it('inserts multiple values', () => { - const childInsert = insert({ - key: 2, - value: 2, - }) - - const t: Tree = insert({ - key: 3, - value: 3, - })( - childInsert( - fromRoot({ - key: 1, - value: 1, - }), - ), - ) + const childInsert = insert(2, 2) + + const t: Tree = insert(3, 3)(childInsert(fromRoot(1, 1))) const expectedValues = [1, 2, 3] assert.deepStrictEqual(toArray(t), expectedValues) From 5b905476c63658933304e83c73ddaac5bd6d8965 Mon Sep 17 00:00:00 2001 From: benjides Date: Sun, 23 Nov 2025 18:58:45 +0100 Subject: [PATCH 065/118] Convert expandRecursively to constant --- src/DepthFirstSearch.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/DepthFirstSearch.ts b/src/DepthFirstSearch.ts index 5a9ee82..e397305 100644 --- a/src/DepthFirstSearch.ts +++ b/src/DepthFirstSearch.ts @@ -21,10 +21,10 @@ export const depthFirstSearch: Search = ( ): Promise => { const closed: HS.HashSet = HS.empty() - async function expandRecursively( + const expandRecursively = async ( open: PQ.PriorityQueue>, closed: HS.HashSet, - ): Promise { + ): Promise => { const [currentState, priorityQueue] = PQ.poll(open) if (currentState === null) { @@ -53,7 +53,7 @@ export const depthFirstSearch: Search = ( ) return expandRecursively(newStates, closed) - } + }; const t: T.Tree = T.fromRoot(0, initial) From e4f1e51069f67f69acf633049ebd00a3ee1bdd6b Mon Sep 17 00:00:00 2001 From: benjides Date: Sun, 23 Nov 2025 19:05:29 +0100 Subject: [PATCH 066/118] Move expandRecursively outside depthFirstSearch declaration --- src/DepthFirstSearch.ts | 69 +++++++++++++++++++++-------------------- 1 file changed, 36 insertions(+), 33 deletions(-) diff --git a/src/DepthFirstSearch.ts b/src/DepthFirstSearch.ts index e397305..fb1578c 100644 --- a/src/DepthFirstSearch.ts +++ b/src/DepthFirstSearch.ts @@ -13,7 +13,7 @@ export type Search = ( expand: Expand, ) => Promise -export const depthFirstSearch: Search = ( +export const depthFirstSearch: Search = async ( initial: S, goal: S, eq: Eq, @@ -21,41 +21,44 @@ export const depthFirstSearch: Search = ( ): Promise => { const closed: HS.HashSet = HS.empty() - const expandRecursively = async ( - open: PQ.PriorityQueue>, - closed: HS.HashSet, - ): Promise => { - const [currentState, priorityQueue] = PQ.poll(open) + const t: T.Tree = T.fromRoot(0, initial) - if (currentState === null) { - return [] - } - closed = HS.insert(currentState.value)(closed) + return expandRecursively(goal, PQ.of(0, t), closed, eq, expand) +} - if (eq(goal, currentState.value)) { - return T.toArray(currentState) - } +const expandRecursively = async ( + goal: S, + open: PQ.PriorityQueue>, + closed: HS.HashSet, + eq: Eq, + expand: Expand, +): Promise => { + const [currentState, priorityQueue] = PQ.poll(open) - const newStates: PQ.PriorityQueue> = ( - await Promise.all(expand(currentState.value)) - ) - .filter((state: S) => !HS.has(eq)(state)(closed)) - .map( - (state: S): T.Tree => - T.insert(currentState.key - 1, state)(currentState), - ) - .reduce( - ( - priorityQueue: PQ.PriorityQueue>, - treeBranch: T.Tree, - ) => PQ.insert(treeBranch.key, treeBranch)(priorityQueue), - priorityQueue, - ) - - return expandRecursively(newStates, closed) - }; + if (currentState === null) { + return [] + } + closed = HS.insert(currentState.value)(closed) - const t: T.Tree = T.fromRoot(0, initial) + if (eq(goal, currentState.value)) { + return T.toArray(currentState) + } + + const newStates: PQ.PriorityQueue> = ( + await Promise.all(expand(currentState.value)) + ) + .filter((state: S) => !HS.has(eq)(state)(closed)) + .map( + (state: S): T.Tree => + T.insert(currentState.key - 1, state)(currentState), + ) + .reduce( + ( + priorityQueue: PQ.PriorityQueue>, + treeBranch: T.Tree, + ) => PQ.insert(treeBranch.key, treeBranch)(priorityQueue), + priorityQueue, + ) - return expandRecursively(PQ.of(0, t), closed) + return expandRecursively(goal, newStates, closed, eq, expand) } From cb9ca48e2802682ccc2b3598ec1f21c380988c16 Mon Sep 17 00:00:00 2001 From: benjides Date: Sun, 23 Nov 2025 19:07:25 +0100 Subject: [PATCH 067/118] Inline constants --- src/DepthFirstSearch.ts | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/DepthFirstSearch.ts b/src/DepthFirstSearch.ts index fb1578c..4bc8c07 100644 --- a/src/DepthFirstSearch.ts +++ b/src/DepthFirstSearch.ts @@ -18,13 +18,14 @@ export const depthFirstSearch: Search = async ( goal: S, eq: Eq, expand: Expand, -): Promise => { - const closed: HS.HashSet = HS.empty() - - const t: T.Tree = T.fromRoot(0, initial) - - return expandRecursively(goal, PQ.of(0, t), closed, eq, expand) -} +): Promise => + expandRecursively( + goal, + PQ.of(0, T.fromRoot(0, initial)), + HS.empty(), + eq, + expand, + ) const expandRecursively = async ( goal: S, From 435b5fb9fb4b75f090013c7fbb676126da01b7d9 Mon Sep 17 00:00:00 2001 From: benjides Date: Sat, 29 Nov 2025 01:09:52 +0100 Subject: [PATCH 068/118] Add State type to represent a Search exploration status and usage --- src/DepthFirstSearch.ts | 48 ++++++++++++++++++++++++++++++++--------- 1 file changed, 38 insertions(+), 10 deletions(-) diff --git a/src/DepthFirstSearch.ts b/src/DepthFirstSearch.ts index 4bc8c07..e7802f4 100644 --- a/src/DepthFirstSearch.ts +++ b/src/DepthFirstSearch.ts @@ -13,6 +13,30 @@ export type Search = ( expand: Expand, ) => Promise +type Node = T.Tree + +type State = { + current: Node + open: PQ.PriorityQueue> + closed: HS.HashSet +} + +export const poll: (state: State) => State | null = ( + state: State, +) => { + const [currentState, priorityQueue] = PQ.poll(state.open) + + if (currentState === null) { + return null + } + + return { + current: currentState, + closed: HS.insert(currentState.value)(state.closed), + open: priorityQueue, + } +} + export const depthFirstSearch: Search = async ( initial: S, goal: S, @@ -34,32 +58,36 @@ const expandRecursively = async ( eq: Eq, expand: Expand, ): Promise => { - const [currentState, priorityQueue] = PQ.poll(open) + const state: State = { + current: T.fromRoot(1, goal), + open: open, + closed: closed, + } + const ns = poll(state) - if (currentState === null) { + if (ns === null) { return [] } - closed = HS.insert(currentState.value)(closed) - if (eq(goal, currentState.value)) { - return T.toArray(currentState) + if (eq(goal, ns.current.value)) { + return T.toArray(ns.current) } const newStates: PQ.PriorityQueue> = ( - await Promise.all(expand(currentState.value)) + await Promise.all(expand(ns.current.value)) ) - .filter((state: S) => !HS.has(eq)(state)(closed)) + .filter((state: S) => !HS.has(eq)(state)(ns.closed)) .map( (state: S): T.Tree => - T.insert(currentState.key - 1, state)(currentState), + T.insert(ns.current.key - 1, state)(ns.current), ) .reduce( ( priorityQueue: PQ.PriorityQueue>, treeBranch: T.Tree, ) => PQ.insert(treeBranch.key, treeBranch)(priorityQueue), - priorityQueue, + ns.open, ) - return expandRecursively(goal, newStates, closed, eq, expand) + return expandRecursively(goal, newStates, ns.closed, eq, expand) } From 92b015c1f4f18eeb8f8f6d342048695a6578446f Mon Sep 17 00:00:00 2001 From: benjides Date: Sat, 29 Nov 2025 14:48:03 +0100 Subject: [PATCH 069/118] Add isDone function to check if State has reached goal --- src/DepthFirstSearch.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/DepthFirstSearch.ts b/src/DepthFirstSearch.ts index e7802f4..5d4dd39 100644 --- a/src/DepthFirstSearch.ts +++ b/src/DepthFirstSearch.ts @@ -37,6 +37,14 @@ export const poll: (state: State) => State | null = ( } } +export const isDone: ( + eq: Eq, +) => (node: T) => (state: State) => boolean = + (eq: Eq) => + (node: T) => + (state: State) => + eq(node, state.current.value) + export const depthFirstSearch: Search = async ( initial: S, goal: S, @@ -69,7 +77,7 @@ const expandRecursively = async ( return [] } - if (eq(goal, ns.current.value)) { + if (isDone(eq)(goal)(ns)) { return T.toArray(ns.current) } From d04b5f325ded5c4ed28b11c3d94ea641bac0eba5 Mon Sep 17 00:00:00 2001 From: benjides Date: Sat, 29 Nov 2025 16:33:46 +0100 Subject: [PATCH 070/118] Use state for expandRecursively --- src/DepthFirstSearch.ts | 39 +++++++++++++++++++++------------------ 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/src/DepthFirstSearch.ts b/src/DepthFirstSearch.ts index 5d4dd39..613868f 100644 --- a/src/DepthFirstSearch.ts +++ b/src/DepthFirstSearch.ts @@ -53,49 +53,52 @@ export const depthFirstSearch: Search = async ( ): Promise => expandRecursively( goal, - PQ.of(0, T.fromRoot(0, initial)), - HS.empty(), + { + current: T.fromRoot(1, goal), + open: PQ.of(0, T.fromRoot(0, initial)), + closed: HS.empty(), + }, eq, expand, ) const expandRecursively = async ( goal: S, - open: PQ.PriorityQueue>, - closed: HS.HashSet, + state: State, eq: Eq, expand: Expand, ): Promise => { - const state: State = { - current: T.fromRoot(1, goal), - open: open, - closed: closed, - } - const ns = poll(state) + const newState = poll(state) - if (ns === null) { + if (newState === null) { return [] } - if (isDone(eq)(goal)(ns)) { - return T.toArray(ns.current) + if (isDone(eq)(goal)(newState)) { + return T.toArray(newState.current) } const newStates: PQ.PriorityQueue> = ( - await Promise.all(expand(ns.current.value)) + await Promise.all(expand(newState.current.value)) ) - .filter((state: S) => !HS.has(eq)(state)(ns.closed)) + .filter((state: S) => !HS.has(eq)(state)(newState.closed)) .map( (state: S): T.Tree => - T.insert(ns.current.key - 1, state)(ns.current), + T.insert(newState.current.key - 1, state)(newState.current), ) .reduce( ( priorityQueue: PQ.PriorityQueue>, treeBranch: T.Tree, ) => PQ.insert(treeBranch.key, treeBranch)(priorityQueue), - ns.open, + newState.open, ) - return expandRecursively(goal, newStates, ns.closed, eq, expand) + const next: State = { + current: newState.current, + open: newStates, + closed: newState.closed, + } + + return expandRecursively(goal, next, eq, expand) } From 45a306a883b3dd375b0d0f729dcf2d7bf88911fe Mon Sep 17 00:00:00 2001 From: benjides Date: Sun, 18 Jan 2026 13:31:10 +0100 Subject: [PATCH 071/118] Extract expandNewStates constant --- src/DepthFirstSearch.ts | 35 ++++++++++++++++++++--------------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/src/DepthFirstSearch.ts b/src/DepthFirstSearch.ts index 613868f..4199554 100644 --- a/src/DepthFirstSearch.ts +++ b/src/DepthFirstSearch.ts @@ -62,6 +62,24 @@ export const depthFirstSearch: Search = async ( expand, ) +export const expandNewStates = + (expand: Expand) => + (eq: Eq) => + async (state: State) => + (await Promise.all(expand(state.current.value))) + .filter((vector: S) => !HS.has(eq)(vector)(state.closed)) + .map( + (vector: S): T.Tree => + T.insert(state.current.key - 1, vector)(state.current), + ) + .reduce( + ( + priorityQueue: PQ.PriorityQueue>, + treeBranch: T.Tree, + ) => PQ.insert(treeBranch.key, treeBranch)(priorityQueue), + state.open, + ) + const expandRecursively = async ( goal: S, state: State, @@ -78,21 +96,8 @@ const expandRecursively = async ( return T.toArray(newState.current) } - const newStates: PQ.PriorityQueue> = ( - await Promise.all(expand(newState.current.value)) - ) - .filter((state: S) => !HS.has(eq)(state)(newState.closed)) - .map( - (state: S): T.Tree => - T.insert(newState.current.key - 1, state)(newState.current), - ) - .reduce( - ( - priorityQueue: PQ.PriorityQueue>, - treeBranch: T.Tree, - ) => PQ.insert(treeBranch.key, treeBranch)(priorityQueue), - newState.open, - ) + const newStates: PQ.PriorityQueue> = + await expandNewStates(expand)(eq)(newState) const next: State = { current: newState.current, From 33574617b9c274beafb1464b74736f14fc1bb063 Mon Sep 17 00:00:00 2001 From: benjides Date: Mon, 19 Jan 2026 01:43:23 +0100 Subject: [PATCH 072/118] Extract isGoal parameter --- src/DepthFirstSearch.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/DepthFirstSearch.ts b/src/DepthFirstSearch.ts index 4199554..478db6f 100644 --- a/src/DepthFirstSearch.ts +++ b/src/DepthFirstSearch.ts @@ -60,6 +60,7 @@ export const depthFirstSearch: Search = async ( }, eq, expand, + isDone(eq)(goal), ) export const expandNewStates = @@ -85,6 +86,7 @@ const expandRecursively = async ( state: State, eq: Eq, expand: Expand, + isGoal: (state: State) => boolean, ): Promise => { const newState = poll(state) @@ -92,7 +94,7 @@ const expandRecursively = async ( return [] } - if (isDone(eq)(goal)(newState)) { + if (isGoal(newState)) { return T.toArray(newState.current) } @@ -105,5 +107,5 @@ const expandRecursively = async ( closed: newState.closed, } - return expandRecursively(goal, next, eq, expand) + return expandRecursively(goal, next, eq, expand, isGoal) } From 5856ec62be9a87b13328f6ceda87ab3ebca26e02 Mon Sep 17 00:00:00 2001 From: benjides Date: Mon, 19 Jan 2026 23:18:16 +0100 Subject: [PATCH 073/118] Introduce expandNewStates parameter --- src/DepthFirstSearch.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/DepthFirstSearch.ts b/src/DepthFirstSearch.ts index 478db6f..4f3f54d 100644 --- a/src/DepthFirstSearch.ts +++ b/src/DepthFirstSearch.ts @@ -61,6 +61,7 @@ export const depthFirstSearch: Search = async ( eq, expand, isDone(eq)(goal), + expandNewStates(expand)(eq), ) export const expandNewStates = @@ -87,6 +88,7 @@ const expandRecursively = async ( eq: Eq, expand: Expand, isGoal: (state: State) => boolean, + expandState: (state: State) => Promise>>, ): Promise => { const newState = poll(state) @@ -98,8 +100,7 @@ const expandRecursively = async ( return T.toArray(newState.current) } - const newStates: PQ.PriorityQueue> = - await expandNewStates(expand)(eq)(newState) + const newStates: PQ.PriorityQueue> = await expandState(newState) const next: State = { current: newState.current, @@ -107,5 +108,5 @@ const expandRecursively = async ( closed: newState.closed, } - return expandRecursively(goal, next, eq, expand, isGoal) + return expandRecursively(goal, next, eq, expand, isGoal, expandState) } From 536313802ac25505f1fa57f30954541a5795c5d4 Mon Sep 17 00:00:00 2001 From: benjides Date: Tue, 20 Jan 2026 00:23:46 +0100 Subject: [PATCH 074/118] Remove unused parameters --- src/DepthFirstSearch.ts | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/src/DepthFirstSearch.ts b/src/DepthFirstSearch.ts index 4f3f54d..b581fc1 100644 --- a/src/DepthFirstSearch.ts +++ b/src/DepthFirstSearch.ts @@ -52,14 +52,11 @@ export const depthFirstSearch: Search = async ( expand: Expand, ): Promise => expandRecursively( - goal, { current: T.fromRoot(1, goal), open: PQ.of(0, T.fromRoot(0, initial)), closed: HS.empty(), }, - eq, - expand, isDone(eq)(goal), expandNewStates(expand)(eq), ) @@ -83,12 +80,11 @@ export const expandNewStates = ) const expandRecursively = async ( - goal: S, state: State, - eq: Eq, - expand: Expand, isGoal: (state: State) => boolean, - expandState: (state: State) => Promise>>, + expandState: ( + state: State, + ) => Promise>>, ): Promise => { const newState = poll(state) @@ -100,7 +96,8 @@ const expandRecursively = async ( return T.toArray(newState.current) } - const newStates: PQ.PriorityQueue> = await expandState(newState) + const newStates: PQ.PriorityQueue> = + await expandState(newState) const next: State = { current: newState.current, @@ -108,5 +105,5 @@ const expandRecursively = async ( closed: newState.closed, } - return expandRecursively(goal, next, eq, expand, isGoal, expandState) + return expandRecursively(next, isGoal, expandState) } From ab4e5a14c27836678de976b996858f3b958b73ef Mon Sep 17 00:00:00 2001 From: benjides Date: Wed, 21 Jan 2026 08:43:23 +0100 Subject: [PATCH 075/118] Extract isGoal parameter --- src/DepthFirstSearch.ts | 7 ++-- src/PriorityQueue.ts | 64 ++++++++++++++++++------------------- tests/PriorityQueue.test.ts | 33 +++++++++---------- 3 files changed, 54 insertions(+), 50 deletions(-) diff --git a/src/DepthFirstSearch.ts b/src/DepthFirstSearch.ts index b581fc1..b981be8 100644 --- a/src/DepthFirstSearch.ts +++ b/src/DepthFirstSearch.ts @@ -54,7 +54,7 @@ export const depthFirstSearch: Search = async ( expandRecursively( { current: T.fromRoot(1, goal), - open: PQ.of(0, T.fromRoot(0, initial)), + open: PQ.of(T.fromRoot(0, initial)), closed: HS.empty(), }, isDone(eq)(goal), @@ -75,7 +75,10 @@ export const expandNewStates = ( priorityQueue: PQ.PriorityQueue>, treeBranch: T.Tree, - ) => PQ.insert(treeBranch.key, treeBranch)(priorityQueue), + ) => + PQ.insert( + (a: T.Tree, b: T.Tree) => b.key - a.key, + )(treeBranch)(priorityQueue), state.open, ) diff --git a/src/PriorityQueue.ts b/src/PriorityQueue.ts index 5466e72..6bd012f 100644 --- a/src/PriorityQueue.ts +++ b/src/PriorityQueue.ts @@ -7,12 +7,17 @@ * * @category model */ -export type PriorityQueue = Node[] +export type PriorityQueue = T[] -type Node = { - priority: number - data: T -} +/** + * Defines a total ordering over values of the same type + * If `a` precedes `b` -1 shall be returned + * If `b` precedes `c` 1 shall be returned + * If `a` and `b` are in same position 0 shall be returned + * + * @category model + */ +export type Ord = (a: T, b: T) => number /** * Creates an empty PriorityQueue @@ -27,19 +32,16 @@ type Node = { export const empty: () => PriorityQueue = () => [] /** - * Creates a PriorityQueue for a given priority and element + * Creates a PriorityQueue for a given element * * @category constructors * * @example * ```ts - * assert.deepStrictEqual(values(of(1, 'Hello World!'), ['Hello World!']) + * assert.deepStrictEqual(values(of('Hello World!')), ['Hello World!']) * ``` */ -export const of: (priority: number, data: T) => PriorityQueue = ( - priority: number, - data: T, -) => insert(priority, data)(empty()) +export const of: (data: T) => PriorityQueue = (data: T) => [data] /** * Converts a given PriorityQueue into an array containing current values maintaining priority order @@ -48,42 +50,40 @@ export const of: (priority: number, data: T) => PriorityQueue = ( * * @example * ```ts - * assert.deepStrictEqual(values(of(1, 'Hello World!'), ['Hello World!']) + * assert.deepStrictEqual(values(of('Hello World!')), ['Hello World!']) * ``` */ -export const values = (priorityQueue: PriorityQueue) => - priorityQueue.map((node: Node) => node.data) +export const values = (priorityQueue: PriorityQueue) => priorityQueue /** - * Inserts an element with priority for a given PriorityQueue + * Inserts an element for a given PriorityQueue * * @example * ```ts - * assert.deepStrictEqual(values(insert(1, 'Hello World!')(empty()), ['Hello World!']) + * const ord : Ord = (a: string, b: string) => b.length - a .length + * + * const priorityQueue : PriorityQueue = insert(ord)('Hello World!')(empty()) + * + * assert.deepStrictEqual(values(priorityQueue), ['Hello World!']) * ``` */ export const insert: ( - priority: number, - data: T, -) => (priorityQueue: PriorityQueue) => PriorityQueue = - (priority: number, data: T) => + ord: Ord, +) => (data: T) => (priorityQueue: PriorityQueue) => PriorityQueue = + (ord: Ord) => + (data: T) => (priorityQueue: PriorityQueue) => { - const nodeToInsert: Node = { - priority: priority, - data: data, - } - const index: number = priorityQueue.findIndex( - (node: Node) => nodeToInsert.priority < node.priority, + (node: T) => ord(data, node) > 0, ) if (index === -1) { - return [...priorityQueue, nodeToInsert] + return [...priorityQueue, data] } return [ ...priorityQueue.slice(0, index), - nodeToInsert, + data, ...priorityQueue.slice(index), ] } @@ -93,10 +93,10 @@ export const insert: ( * * @example * ```ts - * const [element, priorityQueue] = poll(of(1, 'Hello World!')) + * const [element, priorityQueue] = poll(of('Hello World!')) * * assert.deepStrictEqual(element, 'Hello World!') - * assert.deepStrictEqual(priorityQueue, []) + * assert.deepStrictEqual(values(priorityQueue), []) * ``` * * @example @@ -104,12 +104,12 @@ export const insert: ( * const [element, priorityQueue] = poll(empty()) * * assert.isNull(element) - * assert.deepStrictEqual(priorityQueue, []) + * assert.deepStrictEqual(values(priorityQueue), []) * ``` */ export const poll: ( priorityQueue: PriorityQueue, ) => [T | null, PriorityQueue] = (priorityQueue: PriorityQueue) => [ - priorityQueue[0]?.data ?? null, + priorityQueue[0] ?? null, priorityQueue.slice(1), ] diff --git a/tests/PriorityQueue.test.ts b/tests/PriorityQueue.test.ts index bfac55f..68d9cfe 100644 --- a/tests/PriorityQueue.test.ts +++ b/tests/PriorityQueue.test.ts @@ -3,6 +3,7 @@ import { empty, insert, of, + Ord, poll, type PriorityQueue, values, @@ -13,6 +14,8 @@ type Person = { name: string } +const ageOrd: Ord = (a: Person, b) => b.age - a.age + describe('PriorityQueue', () => { describe('constructor', () => { it('creates empty', () => { @@ -27,7 +30,7 @@ describe('PriorityQueue', () => { name: 'Jane', } - const priorityQueue: PriorityQueue = of(jane.age, jane) + const priorityQueue: PriorityQueue = of(jane) const expectedPriorityQueueValues: Person[] = [ { @@ -44,13 +47,13 @@ describe('PriorityQueue', () => { age: 27, name: 'Jane', } - const personsPriorityQueue = insert(jane.age, jane)(empty()) + const personsPriorityQueue = insert(ageOrd)(jane)(empty()) const john: Person = { age: 34, name: 'John', } - const priorityQueueValues = insert(john.age, john)(personsPriorityQueue) + const priorityQueueValues = insert(ageOrd)(john)(personsPriorityQueue) const expectedPriorityQueueValues: Person[] = [ { @@ -73,13 +76,13 @@ describe('PriorityQueue', () => { age: 34, name: 'John', } - const personsPriorityQueue = insert(john.age, john)(empty()) + const personsPriorityQueue = insert(ageOrd)(john)(empty()) const jane: Person = { age: 27, name: 'Jane', } - const priorityQueueValues = insert(jane.age, jane)(personsPriorityQueue) + const priorityQueueValues = insert(ageOrd)(jane)(personsPriorityQueue) const expectedPriorityQueueValues: Person[] = [ { @@ -106,16 +109,15 @@ describe('PriorityQueue', () => { age: 34, name: 'John', } - const personsPriorityQueue = insert( - jane.age, - jane, - )(insert(john.age, john)(empty())) + const personsPriorityQueue = insert(ageOrd)(jane)( + insert(ageOrd)(john)(empty()), + ) const sally: Person = { age: 28, name: 'Sally', } - const priorityQueueValues = insert(sally.age, sally)(personsPriorityQueue) + const priorityQueueValues = insert(ageOrd)(sally)(personsPriorityQueue) const expectedPriorityQueueValues: Person[] = [ { @@ -142,13 +144,13 @@ describe('PriorityQueue', () => { age: 27, name: 'Jane', } - const personsPriorityQueue = insert(jane.age, jane)(empty()) + const personsPriorityQueue = insert(ageOrd)(jane)(empty()) const sally = { age: 27, name: 'Sally', } - const priorityQueueValues = insert(sally.age, sally)(personsPriorityQueue) + const priorityQueueValues = insert(ageOrd)(sally)(personsPriorityQueue) const expectedPriorityQueueValues: Person[] = [ { @@ -176,10 +178,9 @@ describe('PriorityQueue', () => { age: 34, name: 'John', } - const personsPriorityQueue = insert( - jane.age, - jane, - )(insert(john.age, john)(empty())) + const personsPriorityQueue = insert(ageOrd)(jane)( + insert(ageOrd)(john)(empty()), + ) const [person, modifiedQueue] = poll(personsPriorityQueue) From 337848ca6b9256f4cb41d7ad2156fabf9f2ae351 Mon Sep 17 00:00:00 2001 From: benjides Date: Wed, 21 Jan 2026 08:48:03 +0100 Subject: [PATCH 076/118] Extract persons constants --- tests/PriorityQueue.test.ts | 132 +++++++----------------------------- 1 file changed, 26 insertions(+), 106 deletions(-) diff --git a/tests/PriorityQueue.test.ts b/tests/PriorityQueue.test.ts index 68d9cfe..3bd74e2 100644 --- a/tests/PriorityQueue.test.ts +++ b/tests/PriorityQueue.test.ts @@ -14,9 +14,26 @@ type Person = { name: string } -const ageOrd: Ord = (a: Person, b) => b.age - a.age - describe('PriorityQueue', () => { + const jane: Person = { + age: 27, + name: 'Jane', + } + const john: Person = { + age: 34, + name: 'John', + } + const sally: Person = { + age: 28, + name: 'Sally', + } + const jasmine: Person = { + age: 27, + name: 'Jasmine', + } + + const ageOrd: Ord = (a: Person, b) => b.age - a.age + describe('constructor', () => { it('creates empty', () => { const priorityQueueValues = values(empty()) @@ -25,11 +42,6 @@ describe('PriorityQueue', () => { }) it('creates for element', () => { - const jane: Person = { - age: 27, - name: 'Jane', - } - const priorityQueue: PriorityQueue = of(jane) const expectedPriorityQueueValues: Person[] = [ @@ -43,28 +55,11 @@ describe('PriorityQueue', () => { }) describe('insert', () => { it('inserts value at last position', () => { - const jane: Person = { - age: 27, - name: 'Jane', - } const personsPriorityQueue = insert(ageOrd)(jane)(empty()) - const john: Person = { - age: 34, - name: 'John', - } const priorityQueueValues = insert(ageOrd)(john)(personsPriorityQueue) - const expectedPriorityQueueValues: Person[] = [ - { - age: 27, - name: 'Jane', - }, - { - age: 34, - name: 'John', - }, - ] + const expectedPriorityQueueValues: Person[] = [jane, john] assert.deepStrictEqual( values(priorityQueueValues), expectedPriorityQueueValues, @@ -72,28 +67,11 @@ describe('PriorityQueue', () => { }) it('inserts value at first position', () => { - const john: Person = { - age: 34, - name: 'John', - } const personsPriorityQueue = insert(ageOrd)(john)(empty()) - const jane: Person = { - age: 27, - name: 'Jane', - } const priorityQueueValues = insert(ageOrd)(jane)(personsPriorityQueue) - const expectedPriorityQueueValues: Person[] = [ - { - age: 27, - name: 'Jane', - }, - { - age: 34, - name: 'John', - }, - ] + const expectedPriorityQueueValues: Person[] = [jane, john] assert.deepStrictEqual( values(priorityQueueValues), expectedPriorityQueueValues, @@ -101,38 +79,13 @@ describe('PriorityQueue', () => { }) it('inserts value at the middle position', () => { - const jane: Person = { - age: 27, - name: 'Jane', - } - const john: Person = { - age: 34, - name: 'John', - } const personsPriorityQueue = insert(ageOrd)(jane)( insert(ageOrd)(john)(empty()), ) - const sally: Person = { - age: 28, - name: 'Sally', - } const priorityQueueValues = insert(ageOrd)(sally)(personsPriorityQueue) - const expectedPriorityQueueValues: Person[] = [ - { - age: 27, - name: 'Jane', - }, - { - age: 28, - name: 'Sally', - }, - { - age: 34, - name: 'John', - }, - ] + const expectedPriorityQueueValues: Person[] = [jane, sally, john] assert.deepStrictEqual( values(priorityQueueValues), expectedPriorityQueueValues, @@ -140,28 +93,11 @@ describe('PriorityQueue', () => { }) it('inserts after in case of same priority', () => { - const jane: Person = { - age: 27, - name: 'Jane', - } const personsPriorityQueue = insert(ageOrd)(jane)(empty()) - const sally = { - age: 27, - name: 'Sally', - } - const priorityQueueValues = insert(ageOrd)(sally)(personsPriorityQueue) + const priorityQueueValues = insert(ageOrd)(jasmine)(personsPriorityQueue) - const expectedPriorityQueueValues: Person[] = [ - { - age: 27, - name: 'Jane', - }, - { - age: 27, - name: 'Sally', - }, - ] + const expectedPriorityQueueValues: Person[] = [jane, jasmine] assert.deepStrictEqual( values(priorityQueueValues), expectedPriorityQueueValues, @@ -170,30 +106,14 @@ describe('PriorityQueue', () => { }) describe('poll', () => { it('polls non empty PriorityQueue', () => { - const jane: Person = { - age: 27, - name: 'Jane', - } - const john: Person = { - age: 34, - name: 'John', - } const personsPriorityQueue = insert(ageOrd)(jane)( insert(ageOrd)(john)(empty()), ) const [person, modifiedQueue] = poll(personsPriorityQueue) - const expectedPerson: Person = { - age: 27, - name: 'Jane', - } - const expectedPriorityQueueValues: Person[] = [ - { - age: 34, - name: 'John', - }, - ] + const expectedPerson: Person = jane + const expectedPriorityQueueValues: Person[] = [john] assert.deepStrictEqual(person, expectedPerson) assert.deepStrictEqual(values(modifiedQueue), expectedPriorityQueueValues) }) From ae4b837bc96a63bb355bbfd924070d9d1b9b8e62 Mon Sep 17 00:00:00 2001 From: benjides Date: Wed, 21 Jan 2026 08:53:49 +0100 Subject: [PATCH 077/118] Introduce insertPerson constant --- tests/PriorityQueue.test.ts | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/tests/PriorityQueue.test.ts b/tests/PriorityQueue.test.ts index 3bd74e2..1f438b6 100644 --- a/tests/PriorityQueue.test.ts +++ b/tests/PriorityQueue.test.ts @@ -33,6 +33,11 @@ describe('PriorityQueue', () => { } const ageOrd: Ord = (a: Person, b) => b.age - a.age + const insertPerson: ( + person: Person, + ) => (priorityQueue: PriorityQueue) => PriorityQueue = ( + person: Person, + ) => insert(ageOrd)(person) describe('constructor', () => { it('creates empty', () => { @@ -55,9 +60,9 @@ describe('PriorityQueue', () => { }) describe('insert', () => { it('inserts value at last position', () => { - const personsPriorityQueue = insert(ageOrd)(jane)(empty()) + const personsPriorityQueue = insertPerson(jane)(empty()) - const priorityQueueValues = insert(ageOrd)(john)(personsPriorityQueue) + const priorityQueueValues = insertPerson(john)(personsPriorityQueue) const expectedPriorityQueueValues: Person[] = [jane, john] assert.deepStrictEqual( @@ -67,9 +72,9 @@ describe('PriorityQueue', () => { }) it('inserts value at first position', () => { - const personsPriorityQueue = insert(ageOrd)(john)(empty()) + const personsPriorityQueue = insertPerson(john)(empty()) - const priorityQueueValues = insert(ageOrd)(jane)(personsPriorityQueue) + const priorityQueueValues = insertPerson(jane)(personsPriorityQueue) const expectedPriorityQueueValues: Person[] = [jane, john] assert.deepStrictEqual( @@ -79,11 +84,11 @@ describe('PriorityQueue', () => { }) it('inserts value at the middle position', () => { - const personsPriorityQueue = insert(ageOrd)(jane)( - insert(ageOrd)(john)(empty()), + const personsPriorityQueue = insertPerson(jane)( + insertPerson(john)(empty()), ) - const priorityQueueValues = insert(ageOrd)(sally)(personsPriorityQueue) + const priorityQueueValues = insertPerson(sally)(personsPriorityQueue) const expectedPriorityQueueValues: Person[] = [jane, sally, john] assert.deepStrictEqual( @@ -93,9 +98,9 @@ describe('PriorityQueue', () => { }) it('inserts after in case of same priority', () => { - const personsPriorityQueue = insert(ageOrd)(jane)(empty()) + const personsPriorityQueue = insertPerson(jane)(empty()) - const priorityQueueValues = insert(ageOrd)(jasmine)(personsPriorityQueue) + const priorityQueueValues = insertPerson(jasmine)(personsPriorityQueue) const expectedPriorityQueueValues: Person[] = [jane, jasmine] assert.deepStrictEqual( @@ -106,8 +111,8 @@ describe('PriorityQueue', () => { }) describe('poll', () => { it('polls non empty PriorityQueue', () => { - const personsPriorityQueue = insert(ageOrd)(jane)( - insert(ageOrd)(john)(empty()), + const personsPriorityQueue = insertPerson(jane)( + insertPerson(john)(empty()), ) const [person, modifiedQueue] = poll(personsPriorityQueue) From 6b9c23269af80c313c0afab1bda8213352166827 Mon Sep 17 00:00:00 2001 From: benjides Date: Mon, 2 Feb 2026 00:50:04 +0100 Subject: [PATCH 078/118] Add Graph and tests --- src/Graph.ts | 92 +++++++++++++++++++++++++++++++++++++++++++++ tests/Graph.test.ts | 80 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 172 insertions(+) create mode 100644 src/Graph.ts create mode 100644 tests/Graph.test.ts diff --git a/src/Graph.ts b/src/Graph.ts new file mode 100644 index 0000000..71d0937 --- /dev/null +++ b/src/Graph.ts @@ -0,0 +1,92 @@ +export type Graph = Vertex[] + +export type Vertex = { + data: T + adjacentVertexes: number[] +} +export type Eq = (x: T, y: T) => boolean + +export const graph: () => Graph = (): Graph => [] + +export const vertex: ( + eq: Eq, + vertex: S, +) => (graph: Graph) => Graph = + (eq: Eq, vertex: S) => + (graph: Graph) => + hasVertex(eq, vertex)(graph) + ? graph + : [ + ...graph, + { + data: vertex, + adjacentVertexes: [], + }, + ] + +export const hasVertex: ( + eq: Eq, + vertex: S, +) => (graph: Graph) => boolean = + (eq: Eq, vertex: S) => + (graph: Graph) => + getVertex(eq, vertex)(graph) !== null + +const getVertex: ( + eq: Eq, + x: S, +) => (graph: Graph) => Vertex | null = + (eq: Eq, x: S) => + (graph: Graph) => + graph.find((vertex: Vertex) => eq(vertex.data, x)) ?? null + +const vertexIndex: (eq: Eq, x: S) => (graph: Graph) => number = + (eq: Eq, x: S) => + (graph: Graph) => + graph.findIndex((vertex: Vertex) => eq(vertex.data, x)) + +export const edge = + (eq: Eq, x: S, y: S) => + (graph: Graph): Graph => + graph.map((vertex: Vertex) => { + if (eq(vertex.data, x)) { + return { + data: vertex.data, + adjacentVertexes: [ + ...vertex.adjacentVertexes, + vertexIndex(eq, y)(graph), + ], + } + } + + if (eq(vertex.data, y)) { + return { + data: vertex.data, + adjacentVertexes: [ + ...vertex.adjacentVertexes, + vertexIndex(eq, x)(graph), + ], + } + } + return vertex + }) + +export const hasEdge = + (eq: Eq, x: S, y: S) => + (graph: Graph) => + getVertex( + eq, + x, + )(graph)?.adjacentVertexes.includes(vertexIndex(eq, y)(graph)) + +export const adjacent = + (eq: Eq, x: S, adjacent: S[]) => + (graph: Graph): Graph => + adjacent.reduce( + (accumulatedGraph: Graph, v: S) => + edge(eq, x, v)(vertex(eq, v)(accumulatedGraph)), + graph, + ) + +export const size: (graph: Graph) => number = (graph: Graph) => + graph.length diff --git a/tests/Graph.test.ts b/tests/Graph.test.ts new file mode 100644 index 0000000..7650682 --- /dev/null +++ b/tests/Graph.test.ts @@ -0,0 +1,80 @@ +import { assert, describe, it } from 'vitest' +import { + edge, + Eq, + Graph, + graph, + hasVertex, + hasEdge, + size, + vertex, + adjacent, +} from '../src/Graph' + +type Person = string + +const personEq: Eq = (a: Person, b: Person): boolean => a === b + +const person: (person: Person) => (graph: Graph) => Graph = ( + person: Person, +) => vertex(personEq, person) + +const friends: ( + a: Person, + b: Person, +) => (graph: Graph) => Graph = (a: Person, b: Person) => + edge(personEq, a, b) + +const areFriends = (a: Person, b: Person) => (graph: Graph) => + hasEdge(personEq, a, b)(graph) + +describe('Graph', () => { + describe('vertex', () => { + it('adds given vertex', () => { + const john: Person = 'John' + + const networkGraph = person(john)(graph()) + + assert.isTrue(hasVertex(personEq, john)(networkGraph)) + }) + + it('does not add existing vertex', () => { + const john: Person = 'John' + + const networkGraph = person(john)(person(john)(graph())) + + assert.deepStrictEqual(size(networkGraph), 1) + }) + }) + + describe('edge', () => { + it('adds edge for two vertexes', () => { + const john: Person = 'John' + const jane: Person = 'Jane' + + const networkGraph = friends( + john, + jane, + )(person(jane)(person(john)(graph()))) + + assert.isTrue(areFriends(jane, john)(networkGraph)) + assert.isTrue(areFriends(john, jane)(networkGraph)) + }) + }) + + describe('adjacent', () => { + it('adds all adjacent vertex for a given vertex', () => { + const john: Person = 'John' + const jane: Person = 'Jane' + const sally: Person = 'Sally' + + const networkGraph = adjacent(personEq, john, [jane, sally])( + person(john)(graph()), + ) + + assert.isTrue(areFriends(john, jane)(networkGraph)) + assert.isTrue(areFriends(john, sally)(networkGraph)) + assert.isFalse(areFriends(jane, sally)(networkGraph)) + }) + }) +}) From 2cf935cf5f2788ec46250fe118730f0550c1ae70 Mon Sep 17 00:00:00 2001 From: benjides Date: Mon, 2 Feb 2026 00:56:28 +0100 Subject: [PATCH 079/118] Cleanup --- src/Graph.ts | 29 ++++++++++------------------- tests/Graph.test.ts | 31 ++++++++++--------------------- 2 files changed, 20 insertions(+), 40 deletions(-) diff --git a/src/Graph.ts b/src/Graph.ts index 71d0937..8438ad2 100644 --- a/src/Graph.ts +++ b/src/Graph.ts @@ -8,21 +8,15 @@ export type Eq = (x: T, y: T) => boolean export const graph: () => Graph = (): Graph => [] -export const vertex: ( - eq: Eq, - vertex: S, -) => (graph: Graph) => Graph = - (eq: Eq, vertex: S) => - (graph: Graph) => - hasVertex(eq, vertex)(graph) - ? graph - : [ - ...graph, - { - data: vertex, - adjacentVertexes: [], - }, - ] +export const vertex: (vertex: S) => (graph: Graph) => Graph = + (vertex: S) => + (graph: Graph) => [ + ...graph, + { + data: vertex, + adjacentVertexes: [], + }, + ] export const hasVertex: ( eq: Eq, @@ -84,9 +78,6 @@ export const adjacent = (graph: Graph): Graph => adjacent.reduce( (accumulatedGraph: Graph, v: S) => - edge(eq, x, v)(vertex(eq, v)(accumulatedGraph)), + edge(eq, x, v)(vertex(v)(accumulatedGraph)), graph, ) - -export const size: (graph: Graph) => number = (graph: Graph) => - graph.length diff --git a/tests/Graph.test.ts b/tests/Graph.test.ts index 7650682..bd0e070 100644 --- a/tests/Graph.test.ts +++ b/tests/Graph.test.ts @@ -1,25 +1,20 @@ import { assert, describe, it } from 'vitest' import { + adjacent, edge, Eq, - Graph, graph, - hasVertex, + Graph, hasEdge, - size, + hasVertex, vertex, - adjacent, } from '../src/Graph' type Person = string const personEq: Eq = (a: Person, b: Person): boolean => a === b -const person: (person: Person) => (graph: Graph) => Graph = ( - person: Person, -) => vertex(personEq, person) - -const friends: ( +const addFriends: ( a: Person, b: Person, ) => (graph: Graph) => Graph = (a: Person, b: Person) => @@ -33,17 +28,11 @@ describe('Graph', () => { it('adds given vertex', () => { const john: Person = 'John' - const networkGraph = person(john)(graph()) + const networkGraph = vertex(john)(graph()) assert.isTrue(hasVertex(personEq, john)(networkGraph)) - }) - - it('does not add existing vertex', () => { - const john: Person = 'John' - - const networkGraph = person(john)(person(john)(graph())) - - assert.deepStrictEqual(size(networkGraph), 1) + const sally: Person = 'sally' + assert.isFalse(hasVertex(personEq, sally)(networkGraph)) }) }) @@ -52,10 +41,10 @@ describe('Graph', () => { const john: Person = 'John' const jane: Person = 'Jane' - const networkGraph = friends( + const networkGraph = addFriends( john, jane, - )(person(jane)(person(john)(graph()))) + )(vertex(jane)(vertex(john)(graph()))) assert.isTrue(areFriends(jane, john)(networkGraph)) assert.isTrue(areFriends(john, jane)(networkGraph)) @@ -69,7 +58,7 @@ describe('Graph', () => { const sally: Person = 'Sally' const networkGraph = adjacent(personEq, john, [jane, sally])( - person(john)(graph()), + vertex(john)(graph()), ) assert.isTrue(areFriends(john, jane)(networkGraph)) From 3a0d780ba7596279731620abb71597fbf0699565 Mon Sep 17 00:00:00 2001 From: benjides Date: Mon, 2 Feb 2026 10:20:36 +0100 Subject: [PATCH 080/118] Use Node alias --- src/DepthFirstSearch.ts | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/src/DepthFirstSearch.ts b/src/DepthFirstSearch.ts index b981be8..6a9d52e 100644 --- a/src/DepthFirstSearch.ts +++ b/src/DepthFirstSearch.ts @@ -68,26 +68,21 @@ export const expandNewStates = (await Promise.all(expand(state.current.value))) .filter((vector: S) => !HS.has(eq)(vector)(state.closed)) .map( - (vector: S): T.Tree => + (vector: S): Node => T.insert(state.current.key - 1, vector)(state.current), ) .reduce( - ( - priorityQueue: PQ.PriorityQueue>, - treeBranch: T.Tree, - ) => - PQ.insert( - (a: T.Tree, b: T.Tree) => b.key - a.key, - )(treeBranch)(priorityQueue), + (priorityQueue: PQ.PriorityQueue>, treeBranch: Node) => + PQ.insert((a: Node, b: Node) => b.key - a.key)(treeBranch)( + priorityQueue, + ), state.open, ) const expandRecursively = async ( state: State, isGoal: (state: State) => boolean, - expandState: ( - state: State, - ) => Promise>>, + expandState: (state: State) => Promise>>, ): Promise => { const newState = poll(state) @@ -99,8 +94,7 @@ const expandRecursively = async ( return T.toArray(newState.current) } - const newStates: PQ.PriorityQueue> = - await expandState(newState) + const newStates: PQ.PriorityQueue> = await expandState(newState) const next: State = { current: newState.current, From 380206ae112686fe3007fe4c986b8b5b5236f4c6 Mon Sep 17 00:00:00 2001 From: benjides Date: Mon, 2 Feb 2026 10:22:56 +0100 Subject: [PATCH 081/118] Add OpenSet type alias of PriorityQueue> --- src/DepthFirstSearch.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/DepthFirstSearch.ts b/src/DepthFirstSearch.ts index 6a9d52e..dd4d1c1 100644 --- a/src/DepthFirstSearch.ts +++ b/src/DepthFirstSearch.ts @@ -1,6 +1,7 @@ import * as PQ from './PriorityQueue' import * as T from './Tree' import * as HS from './HashSet' +import { PriorityQueue } from './PriorityQueue' export type Eq = (a: S, b: S) => boolean @@ -15,9 +16,11 @@ export type Search = ( type Node = T.Tree +type OpenSet = PriorityQueue> + type State = { current: Node - open: PQ.PriorityQueue> + open: OpenSet closed: HS.HashSet } @@ -72,7 +75,7 @@ export const expandNewStates = T.insert(state.current.key - 1, vector)(state.current), ) .reduce( - (priorityQueue: PQ.PriorityQueue>, treeBranch: Node) => + (priorityQueue: OpenSet, treeBranch: Node) => PQ.insert((a: Node, b: Node) => b.key - a.key)(treeBranch)( priorityQueue, ), @@ -82,7 +85,7 @@ export const expandNewStates = const expandRecursively = async ( state: State, isGoal: (state: State) => boolean, - expandState: (state: State) => Promise>>, + expandState: (state: State) => Promise>, ): Promise => { const newState = poll(state) @@ -94,7 +97,7 @@ const expandRecursively = async ( return T.toArray(newState.current) } - const newStates: PQ.PriorityQueue> = await expandState(newState) + const newStates: OpenSet = await expandState(newState) const next: State = { current: newState.current, From 18c0f9c911d0a0d984a96e3a04bca4afc7f86e7a Mon Sep 17 00:00:00 2001 From: benjides Date: Mon, 2 Feb 2026 10:45:57 +0100 Subject: [PATCH 082/118] Do not export isDone --- src/DepthFirstSearch.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/DepthFirstSearch.ts b/src/DepthFirstSearch.ts index dd4d1c1..f9beb13 100644 --- a/src/DepthFirstSearch.ts +++ b/src/DepthFirstSearch.ts @@ -40,7 +40,7 @@ export const poll: (state: State) => State | null = ( } } -export const isDone: ( +const isDone: ( eq: Eq, ) => (node: T) => (state: State) => boolean = (eq: Eq) => From ce48fad3a754d340945156768cb5e6d7ba17cbc6 Mon Sep 17 00:00:00 2001 From: benjides Date: Mon, 2 Feb 2026 10:57:22 +0100 Subject: [PATCH 083/118] Use isDone with S type directly --- src/DepthFirstSearch.ts | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/src/DepthFirstSearch.ts b/src/DepthFirstSearch.ts index f9beb13..dfe13b9 100644 --- a/src/DepthFirstSearch.ts +++ b/src/DepthFirstSearch.ts @@ -40,13 +40,10 @@ export const poll: (state: State) => State | null = ( } } -const isDone: ( - eq: Eq, -) => (node: T) => (state: State) => boolean = - (eq: Eq) => - (node: T) => - (state: State) => - eq(node, state.current.value) +const isDone: (eq: Eq, node: T) => (state: T) => boolean = + (eq: Eq, node: T) => + (state: T) => + eq(node, state) export const depthFirstSearch: Search = async ( initial: S, @@ -60,7 +57,7 @@ export const depthFirstSearch: Search = async ( open: PQ.of(T.fromRoot(0, initial)), closed: HS.empty(), }, - isDone(eq)(goal), + isDone(eq, goal), expandNewStates(expand)(eq), ) @@ -84,7 +81,7 @@ export const expandNewStates = const expandRecursively = async ( state: State, - isGoal: (state: State) => boolean, + isGoal: (node: S) => boolean, expandState: (state: State) => Promise>, ): Promise => { const newState = poll(state) @@ -93,7 +90,7 @@ const expandRecursively = async ( return [] } - if (isGoal(newState)) { + if (isGoal(newState.current.value)) { return T.toArray(newState.current) } From a600fd0d18bc3765e29e7a66bc8120904d7457fb Mon Sep 17 00:00:00 2001 From: benjides Date: Mon, 2 Feb 2026 11:02:24 +0100 Subject: [PATCH 084/118] Replace Tree usages with Node --- src/DepthFirstSearch.ts | 35 +++++++++++++++++++++++++---------- 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/src/DepthFirstSearch.ts b/src/DepthFirstSearch.ts index dfe13b9..3ec5146 100644 --- a/src/DepthFirstSearch.ts +++ b/src/DepthFirstSearch.ts @@ -1,7 +1,6 @@ import * as PQ from './PriorityQueue' -import * as T from './Tree' -import * as HS from './HashSet' import { PriorityQueue } from './PriorityQueue' +import * as HS from './HashSet' export type Eq = (a: S, b: S) => boolean @@ -14,7 +13,11 @@ export type Search = ( expand: Expand, ) => Promise -type Node = T.Tree +type Node = { + key: number + value: S + parent: Node | null +} type OpenSet = PriorityQueue> @@ -45,6 +48,15 @@ const isDone: (eq: Eq, node: T) => (state: T) => boolean = (state: T) => eq(node, state) +const initialNode: (value: S) => Node = (value: S): Node => ({ + key: 0, + value: value, + parent: null, +}) + +export const toArray: (node: Node) => S[] = (node: Node) => + node.parent === null ? [node.value] : [...toArray(node.parent), node.value] + export const depthFirstSearch: Search = async ( initial: S, goal: S, @@ -53,8 +65,8 @@ export const depthFirstSearch: Search = async ( ): Promise => expandRecursively( { - current: T.fromRoot(1, goal), - open: PQ.of(T.fromRoot(0, initial)), + current: initialNode(initial), + open: PQ.of(initialNode(initial)), closed: HS.empty(), }, isDone(eq, goal), @@ -68,12 +80,15 @@ export const expandNewStates = (await Promise.all(expand(state.current.value))) .filter((vector: S) => !HS.has(eq)(vector)(state.closed)) .map( - (vector: S): Node => - T.insert(state.current.key - 1, vector)(state.current), + (vector: S): Node => ({ + key: state.current.key - 1, + value: vector, + parent: state.current, + }), ) .reduce( - (priorityQueue: OpenSet, treeBranch: Node) => - PQ.insert((a: Node, b: Node) => b.key - a.key)(treeBranch)( + (priorityQueue: OpenSet, node: Node) => + PQ.insert((a: Node, b: Node) => b.key - a.key)(node)( priorityQueue, ), state.open, @@ -91,7 +106,7 @@ const expandRecursively = async ( } if (isGoal(newState.current.value)) { - return T.toArray(newState.current) + return toArray(newState.current) } const newStates: OpenSet = await expandState(newState) From 15e6260c570370c91612aa68c5bda18a07e91cea Mon Sep 17 00:00:00 2001 From: benjides Date: Mon, 2 Feb 2026 11:14:55 +0100 Subject: [PATCH 085/118] Extract nodeOrd const --- src/DepthFirstSearch.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/DepthFirstSearch.ts b/src/DepthFirstSearch.ts index 3ec5146..eb52daa 100644 --- a/src/DepthFirstSearch.ts +++ b/src/DepthFirstSearch.ts @@ -73,6 +73,8 @@ export const depthFirstSearch: Search = async ( expandNewStates(expand)(eq), ) +const nodeOrd = (a: Node, b: Node) => b.key - a.key + export const expandNewStates = (expand: Expand) => (eq: Eq) => @@ -88,9 +90,7 @@ export const expandNewStates = ) .reduce( (priorityQueue: OpenSet, node: Node) => - PQ.insert((a: Node, b: Node) => b.key - a.key)(node)( - priorityQueue, - ), + PQ.insert(nodeOrd)(node)(priorityQueue), state.open, ) From 559af70f6fadf3b4aa0e64fc58f35618fda826f0 Mon Sep 17 00:00:00 2001 From: benjides Date: Mon, 2 Feb 2026 11:15:14 +0100 Subject: [PATCH 086/118] Remove exported toArray const --- src/DepthFirstSearch.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/DepthFirstSearch.ts b/src/DepthFirstSearch.ts index eb52daa..53531b2 100644 --- a/src/DepthFirstSearch.ts +++ b/src/DepthFirstSearch.ts @@ -54,7 +54,7 @@ const initialNode: (value: S) => Node = (value: S): Node => ({ parent: null, }) -export const toArray: (node: Node) => S[] = (node: Node) => +const toArray: (node: Node) => S[] = (node: Node) => node.parent === null ? [node.value] : [...toArray(node.parent), node.value] export const depthFirstSearch: Search = async ( From a2811803e673021d736d1e763bcdbf5134b4ff31 Mon Sep 17 00:00:00 2001 From: benjides Date: Mon, 2 Feb 2026 11:19:43 +0100 Subject: [PATCH 087/118] Extract nodeInsert const --- src/DepthFirstSearch.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/DepthFirstSearch.ts b/src/DepthFirstSearch.ts index 53531b2..bd51a7c 100644 --- a/src/DepthFirstSearch.ts +++ b/src/DepthFirstSearch.ts @@ -75,6 +75,11 @@ export const depthFirstSearch: Search = async ( const nodeOrd = (a: Node, b: Node) => b.key - a.key +const nodeInsert: (node: Node) => (openSet: OpenSet) => OpenSet = + (node: Node) => + (openSet: OpenSet) => + PQ.insert(nodeOrd)(node)(openSet) + export const expandNewStates = (expand: Expand) => (eq: Eq) => @@ -90,7 +95,7 @@ export const expandNewStates = ) .reduce( (priorityQueue: OpenSet, node: Node) => - PQ.insert(nodeOrd)(node)(priorityQueue), + nodeInsert(node)(priorityQueue), state.open, ) From 0258f510d55e9484b0b4b002f67b86513c8f04bf Mon Sep 17 00:00:00 2001 From: benjides Date: Mon, 2 Feb 2026 12:00:12 +0100 Subject: [PATCH 088/118] Extract hasBeenVisited const --- src/DepthFirstSearch.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/DepthFirstSearch.ts b/src/DepthFirstSearch.ts index bd51a7c..dbacb73 100644 --- a/src/DepthFirstSearch.ts +++ b/src/DepthFirstSearch.ts @@ -1,6 +1,7 @@ import * as PQ from './PriorityQueue' import { PriorityQueue } from './PriorityQueue' import * as HS from './HashSet' +import { HashSet } from './HashSet' export type Eq = (a: S, b: S) => boolean @@ -80,12 +81,18 @@ const nodeInsert: (node: Node) => (openSet: OpenSet) => OpenSet = (openSet: OpenSet) => PQ.insert(nodeOrd)(node)(openSet) +const hasBeenVisited: (eq: Eq) => (s: S) => (hashSet: HashSet) => boolean = + (eq: Eq) => + (s: S) => + (hashSet: HashSet) => + HS.has(eq)(s)(hashSet) + export const expandNewStates = (expand: Expand) => (eq: Eq) => async (state: State) => (await Promise.all(expand(state.current.value))) - .filter((vector: S) => !HS.has(eq)(vector)(state.closed)) + .filter((vector: S) => !hasBeenVisited(eq)(vector)(state.closed)) .map( (vector: S): Node => ({ key: state.current.key - 1, From 29ab53a4db902c144ef578d2112593634f525201 Mon Sep 17 00:00:00 2001 From: benjides Date: Mon, 2 Feb 2026 12:08:51 +0100 Subject: [PATCH 089/118] Use hasBeenVisited const --- src/DepthFirstSearch.ts | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/DepthFirstSearch.ts b/src/DepthFirstSearch.ts index dbacb73..7a041d2 100644 --- a/src/DepthFirstSearch.ts +++ b/src/DepthFirstSearch.ts @@ -71,7 +71,7 @@ export const depthFirstSearch: Search = async ( closed: HS.empty(), }, isDone(eq, goal), - expandNewStates(expand)(eq), + expandNewStates(expand, hasBeenVisited(eq)), ) const nodeOrd = (a: Node, b: Node) => b.key - a.key @@ -81,18 +81,22 @@ const nodeInsert: (node: Node) => (openSet: OpenSet) => OpenSet = (openSet: OpenSet) => PQ.insert(nodeOrd)(node)(openSet) -const hasBeenVisited: (eq: Eq) => (s: S) => (hashSet: HashSet) => boolean = +const hasBeenVisited: ( + eq: Eq, +) => (s: S) => (hashSet: HashSet) => boolean = (eq: Eq) => (s: S) => (hashSet: HashSet) => HS.has(eq)(s)(hashSet) export const expandNewStates = - (expand: Expand) => - (eq: Eq) => + ( + expand: Expand, + hasBeenVisited: (s: S) => (hashSet: HashSet) => boolean, + ) => async (state: State) => (await Promise.all(expand(state.current.value))) - .filter((vector: S) => !hasBeenVisited(eq)(vector)(state.closed)) + .filter((vector: S) => !hasBeenVisited(vector)(state.closed)) .map( (vector: S): Node => ({ key: state.current.key - 1, From 7f0aed2a9040eacf92decb7e2cc6aca7745536ef Mon Sep 17 00:00:00 2001 From: benjides Date: Mon, 2 Feb 2026 14:58:54 +0100 Subject: [PATCH 090/118] Inline expandNewStates constant --- src/DepthFirstSearch.ts | 53 ++++++++++++++++++----------------------- 1 file changed, 23 insertions(+), 30 deletions(-) diff --git a/src/DepthFirstSearch.ts b/src/DepthFirstSearch.ts index 7a041d2..85ca26a 100644 --- a/src/DepthFirstSearch.ts +++ b/src/DepthFirstSearch.ts @@ -71,7 +71,8 @@ export const depthFirstSearch: Search = async ( closed: HS.empty(), }, isDone(eq, goal), - expandNewStates(expand, hasBeenVisited(eq)), + eq, + expand, ) const nodeOrd = (a: Node, b: Node) => b.key - a.key @@ -81,39 +82,16 @@ const nodeInsert: (node: Node) => (openSet: OpenSet) => OpenSet = (openSet: OpenSet) => PQ.insert(nodeOrd)(node)(openSet) -const hasBeenVisited: ( - eq: Eq, -) => (s: S) => (hashSet: HashSet) => boolean = - (eq: Eq) => +const hasBeenVisited = + (eq: Eq, hashSet: HashSet) => (s: S) => - (hashSet: HashSet) => HS.has(eq)(s)(hashSet) -export const expandNewStates = - ( - expand: Expand, - hasBeenVisited: (s: S) => (hashSet: HashSet) => boolean, - ) => - async (state: State) => - (await Promise.all(expand(state.current.value))) - .filter((vector: S) => !hasBeenVisited(vector)(state.closed)) - .map( - (vector: S): Node => ({ - key: state.current.key - 1, - value: vector, - parent: state.current, - }), - ) - .reduce( - (priorityQueue: OpenSet, node: Node) => - nodeInsert(node)(priorityQueue), - state.open, - ) - const expandRecursively = async ( state: State, isGoal: (node: S) => boolean, - expandState: (state: State) => Promise>, + eq: Eq, + expand: Expand, ): Promise => { const newState = poll(state) @@ -125,7 +103,22 @@ const expandRecursively = async ( return toArray(newState.current) } - const newStates: OpenSet = await expandState(newState) + const newStates: OpenSet = ( + await Promise.all(expand(newState.current.value)) + ) + .filter((vector: S) => !hasBeenVisited(eq, newState.closed)(vector)) + .map( + (vector: S): Node => ({ + key: newState.current.key - 1, + value: vector, + parent: newState.current, + }), + ) + .reduce( + (priorityQueue: OpenSet, node: Node) => + nodeInsert(node)(priorityQueue), + newState.open, + ) const next: State = { current: newState.current, @@ -133,5 +126,5 @@ const expandRecursively = async ( closed: newState.closed, } - return expandRecursively(next, isGoal, expandState) + return expandRecursively(next, isGoal, eq, expand) } From 83214d4cd71d97979ff3c569b5ff479409d724d2 Mon Sep 17 00:00:00 2001 From: benjides Date: Mon, 2 Feb 2026 15:28:12 +0100 Subject: [PATCH 091/118] refactoring --- src/DepthFirstSearch.ts | 83 +++++++++++++---------------------------- 1 file changed, 26 insertions(+), 57 deletions(-) diff --git a/src/DepthFirstSearch.ts b/src/DepthFirstSearch.ts index 85ca26a..b27ab51 100644 --- a/src/DepthFirstSearch.ts +++ b/src/DepthFirstSearch.ts @@ -16,47 +16,25 @@ export type Search = ( type Node = { key: number - value: S + state: S parent: Node | null } type OpenSet = PriorityQueue> -type State = { - current: Node - open: OpenSet - closed: HS.HashSet -} - -export const poll: (state: State) => State | null = ( - state: State, -) => { - const [currentState, priorityQueue] = PQ.poll(state.open) - - if (currentState === null) { - return null - } - - return { - current: currentState, - closed: HS.insert(currentState.value)(state.closed), - open: priorityQueue, - } -} - -const isDone: (eq: Eq, node: T) => (state: T) => boolean = +const isDone: (eq: Eq, node: S) => (state: S) => boolean = (eq: Eq, node: T) => (state: T) => eq(node, state) -const initialNode: (value: S) => Node = (value: S): Node => ({ +const initialNode: (state: S) => Node = (state: S): Node => ({ key: 0, - value: value, + state: state, parent: null, }) const toArray: (node: Node) => S[] = (node: Node) => - node.parent === null ? [node.value] : [...toArray(node.parent), node.value] + node.parent === null ? [node.state] : [...toArray(node.parent), node.state] export const depthFirstSearch: Search = async ( initial: S, @@ -65,11 +43,8 @@ export const depthFirstSearch: Search = async ( expand: Expand, ): Promise => expandRecursively( - { - current: initialNode(initial), - open: PQ.of(initialNode(initial)), - closed: HS.empty(), - }, + PQ.of(initialNode(initial)), + HS.empty(), isDone(eq, goal), eq, expand, @@ -84,47 +59,41 @@ const nodeInsert: (node: Node) => (openSet: OpenSet) => OpenSet = const hasBeenVisited = (eq: Eq, hashSet: HashSet) => - (s: S) => - HS.has(eq)(s)(hashSet) + (state: S) => + HS.has(eq)(state)(hashSet) const expandRecursively = async ( - state: State, + openSet: OpenSet, + closedSet: HS.HashSet, isGoal: (node: S) => boolean, eq: Eq, expand: Expand, ): Promise => { - const newState = poll(state) + const [node, priorityQueue] = PQ.poll(openSet) - if (newState === null) { + if (node === null) { return [] } - if (isGoal(newState.current.value)) { - return toArray(newState.current) + if (isGoal(node.state)) { + return toArray(node) } - const newStates: OpenSet = ( - await Promise.all(expand(newState.current.value)) - ) - .filter((vector: S) => !hasBeenVisited(eq, newState.closed)(vector)) + const closed: HS.HashSet = HS.insert(node.state)(closedSet) + + const newStates: OpenSet = (await Promise.all(expand(node.state))) + .filter((state: S) => !hasBeenVisited(eq, closed)(state)) .map( - (vector: S): Node => ({ - key: newState.current.key - 1, - value: vector, - parent: newState.current, + (state: S): Node => ({ + key: node.key - 1, + state: state, + parent: node, }), ) .reduce( - (priorityQueue: OpenSet, node: Node) => - nodeInsert(node)(priorityQueue), - newState.open, + (openSet: OpenSet, node: Node) => nodeInsert(node)(openSet), + priorityQueue, ) - const next: State = { - current: newState.current, - open: newStates, - closed: newState.closed, - } - - return expandRecursively(next, isGoal, eq, expand) + return expandRecursively(newStates, closed, isGoal, eq, expand) } From 67d01b69966f30b9d4a9c91abdebd7d3c9dec1c6 Mon Sep 17 00:00:00 2001 From: benjides Date: Mon, 2 Feb 2026 15:29:03 +0100 Subject: [PATCH 092/118] Extract ClosedSet type alias --- src/DepthFirstSearch.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/DepthFirstSearch.ts b/src/DepthFirstSearch.ts index b27ab51..9889b15 100644 --- a/src/DepthFirstSearch.ts +++ b/src/DepthFirstSearch.ts @@ -22,6 +22,8 @@ type Node = { type OpenSet = PriorityQueue> +type ClosedSet = HashSet + const isDone: (eq: Eq, node: S) => (state: S) => boolean = (eq: Eq, node: T) => (state: T) => @@ -64,7 +66,7 @@ const hasBeenVisited = const expandRecursively = async ( openSet: OpenSet, - closedSet: HS.HashSet, + closedSet: ClosedSet, isGoal: (node: S) => boolean, eq: Eq, expand: Expand, From a932c0fb357a3b5424f51ccf99787abbcdf750bc Mon Sep 17 00:00:00 2001 From: benjides Date: Mon, 2 Feb 2026 15:30:47 +0100 Subject: [PATCH 093/118] Extract openSet constructor --- src/DepthFirstSearch.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/DepthFirstSearch.ts b/src/DepthFirstSearch.ts index 9889b15..b92206c 100644 --- a/src/DepthFirstSearch.ts +++ b/src/DepthFirstSearch.ts @@ -38,19 +38,23 @@ const initialNode: (state: S) => Node = (state: S): Node => ({ const toArray: (node: Node) => S[] = (node: Node) => node.parent === null ? [node.state] : [...toArray(node.parent), node.state] +const openSet: (initial: S) => OpenSet = (initial: S) => + PQ.of(initialNode(initial)) + export const depthFirstSearch: Search = async ( initial: S, goal: S, eq: Eq, expand: Expand, -): Promise => - expandRecursively( - PQ.of(initialNode(initial)), +): Promise => { + return expandRecursively( + openSet(initial), HS.empty(), isDone(eq, goal), eq, expand, ) +} const nodeOrd = (a: Node, b: Node) => b.key - a.key From 676e06d984d141183b3fc1a02c63756905d4e69c Mon Sep 17 00:00:00 2001 From: benjides Date: Mon, 2 Feb 2026 15:34:56 +0100 Subject: [PATCH 094/118] Extract closedSet constructor --- src/DepthFirstSearch.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/DepthFirstSearch.ts b/src/DepthFirstSearch.ts index b92206c..61f5ce4 100644 --- a/src/DepthFirstSearch.ts +++ b/src/DepthFirstSearch.ts @@ -41,6 +41,8 @@ const toArray: (node: Node) => S[] = (node: Node) => const openSet: (initial: S) => OpenSet = (initial: S) => PQ.of(initialNode(initial)) +const closedSet: () => ClosedSet = () => HS.empty() + export const depthFirstSearch: Search = async ( initial: S, goal: S, @@ -49,7 +51,7 @@ export const depthFirstSearch: Search = async ( ): Promise => { return expandRecursively( openSet(initial), - HS.empty(), + closedSet(), isDone(eq, goal), eq, expand, From 58f69f1819cdbff0c43f06b459229328cf1ba77f Mon Sep 17 00:00:00 2001 From: benjides Date: Mon, 2 Feb 2026 15:35:15 +0100 Subject: [PATCH 095/118] Rename var --- src/DepthFirstSearch.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/DepthFirstSearch.ts b/src/DepthFirstSearch.ts index 61f5ce4..750b88f 100644 --- a/src/DepthFirstSearch.ts +++ b/src/DepthFirstSearch.ts @@ -73,7 +73,7 @@ const hasBeenVisited = const expandRecursively = async ( openSet: OpenSet, closedSet: ClosedSet, - isGoal: (node: S) => boolean, + isGoal: (state: S) => boolean, eq: Eq, expand: Expand, ): Promise => { From 5a552bdae9f6337797e5f1bb62db9192473cd169 Mon Sep 17 00:00:00 2001 From: benjides Date: Mon, 2 Feb 2026 15:43:15 +0100 Subject: [PATCH 096/118] Add toNode constructor --- src/DepthFirstSearch.ts | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/DepthFirstSearch.ts b/src/DepthFirstSearch.ts index 750b88f..9f1620f 100644 --- a/src/DepthFirstSearch.ts +++ b/src/DepthFirstSearch.ts @@ -70,6 +70,14 @@ const hasBeenVisited = (state: S) => HS.has(eq)(state)(hashSet) +const toNode: (node: Node) => (state: S) => Node = + (node: Node) => + (state: S) => ({ + key: node.key - 1, + state: state, + parent: node, + }) + const expandRecursively = async ( openSet: OpenSet, closedSet: ClosedSet, @@ -91,13 +99,7 @@ const expandRecursively = async ( const newStates: OpenSet = (await Promise.all(expand(node.state))) .filter((state: S) => !hasBeenVisited(eq, closed)(state)) - .map( - (state: S): Node => ({ - key: node.key - 1, - state: state, - parent: node, - }), - ) + .map(toNode(node)) .reduce( (openSet: OpenSet, node: Node) => nodeInsert(node)(openSet), priorityQueue, From 321c5615f6edd2996ec2eb54f816b4316f2ea466 Mon Sep 17 00:00:00 2001 From: benjides Date: Mon, 2 Feb 2026 15:55:16 +0100 Subject: [PATCH 097/118] Extract Node class --- src/DepthFirstSearch.ts | 46 ++++++++++------------------------------- src/Node.ts | 29 ++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 35 deletions(-) create mode 100644 src/Node.ts diff --git a/src/DepthFirstSearch.ts b/src/DepthFirstSearch.ts index 9f1620f..2e26386 100644 --- a/src/DepthFirstSearch.ts +++ b/src/DepthFirstSearch.ts @@ -2,6 +2,7 @@ import * as PQ from './PriorityQueue' import { PriorityQueue } from './PriorityQueue' import * as HS from './HashSet' import { HashSet } from './HashSet' +import { initial, node, Node, nodeOrd, toArray } from './Node' export type Eq = (a: S, b: S) => boolean @@ -14,12 +15,6 @@ export type Search = ( expand: Expand, ) => Promise -type Node = { - key: number - state: S - parent: Node | null -} - type OpenSet = PriorityQueue> type ClosedSet = HashSet @@ -29,17 +24,8 @@ const isDone: (eq: Eq, node: S) => (state: S) => boolean = (state: T) => eq(node, state) -const initialNode: (state: S) => Node = (state: S): Node => ({ - key: 0, - state: state, - parent: null, -}) - -const toArray: (node: Node) => S[] = (node: Node) => - node.parent === null ? [node.state] : [...toArray(node.parent), node.state] - -const openSet: (initial: S) => OpenSet = (initial: S) => - PQ.of(initialNode(initial)) +const openSet: (state: S) => OpenSet = (state: S) => + PQ.of(initial(state)) const closedSet: () => ClosedSet = () => HS.empty() @@ -58,26 +44,16 @@ export const depthFirstSearch: Search = async ( ) } -const nodeOrd = (a: Node, b: Node) => b.key - a.key - const nodeInsert: (node: Node) => (openSet: OpenSet) => OpenSet = (node: Node) => (openSet: OpenSet) => - PQ.insert(nodeOrd)(node)(openSet) + PQ.insert(nodeOrd())(node)(openSet) const hasBeenVisited = (eq: Eq, hashSet: HashSet) => (state: S) => HS.has(eq)(state)(hashSet) -const toNode: (node: Node) => (state: S) => Node = - (node: Node) => - (state: S) => ({ - key: node.key - 1, - state: state, - parent: node, - }) - const expandRecursively = async ( openSet: OpenSet, closedSet: ClosedSet, @@ -85,21 +61,21 @@ const expandRecursively = async ( eq: Eq, expand: Expand, ): Promise => { - const [node, priorityQueue] = PQ.poll(openSet) + const [n, priorityQueue] = PQ.poll(openSet) - if (node === null) { + if (n === null) { return [] } - if (isGoal(node.state)) { - return toArray(node) + if (isGoal(n.state)) { + return toArray(n) } - const closed: HS.HashSet = HS.insert(node.state)(closedSet) + const closed: HS.HashSet = HS.insert(n.state)(closedSet) - const newStates: OpenSet = (await Promise.all(expand(node.state))) + const newStates: OpenSet = (await Promise.all(expand(n.state))) .filter((state: S) => !hasBeenVisited(eq, closed)(state)) - .map(toNode(node)) + .map(node(n)) .reduce( (openSet: OpenSet, node: Node) => nodeInsert(node)(openSet), priorityQueue, diff --git a/src/Node.ts b/src/Node.ts new file mode 100644 index 0000000..5793b57 --- /dev/null +++ b/src/Node.ts @@ -0,0 +1,29 @@ +import { Ord } from './PriorityQueue' + +export type Node = { + key: number + state: S + parent: Node | null +} + +export const node: (node: Node) => (state: S) => Node = + (node: Node) => + (state: S) => ({ + key: node.key - 1, + state: state, + parent: node, + }) + +export const initial: (state: S) => Node = (state: S): Node => ({ + key: 0, + state: state, + parent: null, +}) + +export const toArray: (node: Node) => S[] = (node: Node) => + node.parent === null ? [node.state] : [...toArray(node.parent), node.state] + +export const nodeOrd: () => Ord> = + () => + (a: Node, b: Node) => + b.key - a.key From 41b7608f51c41e0615455acd26acca94c51827f3 Mon Sep 17 00:00:00 2001 From: benjides Date: Mon, 2 Feb 2026 15:57:00 +0100 Subject: [PATCH 098/118] Replace * imports for HashSet --- src/DepthFirstSearch.ts | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/DepthFirstSearch.ts b/src/DepthFirstSearch.ts index 2e26386..fed882e 100644 --- a/src/DepthFirstSearch.ts +++ b/src/DepthFirstSearch.ts @@ -1,7 +1,6 @@ import * as PQ from './PriorityQueue' import { PriorityQueue } from './PriorityQueue' -import * as HS from './HashSet' -import { HashSet } from './HashSet' +import { empty, has, HashSet, insert } from './HashSet' import { initial, node, Node, nodeOrd, toArray } from './Node' export type Eq = (a: S, b: S) => boolean @@ -27,7 +26,7 @@ const isDone: (eq: Eq, node: S) => (state: S) => boolean = const openSet: (state: S) => OpenSet = (state: S) => PQ.of(initial(state)) -const closedSet: () => ClosedSet = () => HS.empty() +const closedSet: () => ClosedSet = () => empty() export const depthFirstSearch: Search = async ( initial: S, @@ -52,7 +51,7 @@ const nodeInsert: (node: Node) => (openSet: OpenSet) => OpenSet = const hasBeenVisited = (eq: Eq, hashSet: HashSet) => (state: S) => - HS.has(eq)(state)(hashSet) + has(eq)(state)(hashSet) const expandRecursively = async ( openSet: OpenSet, @@ -71,7 +70,7 @@ const expandRecursively = async ( return toArray(n) } - const closed: HS.HashSet = HS.insert(n.state)(closedSet) + const closed: HashSet = insert(n.state)(closedSet) const newStates: OpenSet = (await Promise.all(expand(n.state))) .filter((state: S) => !hasBeenVisited(eq, closed)(state)) From aeddd8e1a7964a2b0d0f7e124919b0385f74a76e Mon Sep 17 00:00:00 2001 From: benjides Date: Mon, 2 Feb 2026 15:57:44 +0100 Subject: [PATCH 099/118] Rename HashSet.empty to hashSet --- src/DepthFirstSearch.ts | 4 ++-- src/HashSet.ts | 4 ++-- tests/HashSet.test.ts | 14 +++++++------- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/DepthFirstSearch.ts b/src/DepthFirstSearch.ts index fed882e..fd668bb 100644 --- a/src/DepthFirstSearch.ts +++ b/src/DepthFirstSearch.ts @@ -1,6 +1,6 @@ import * as PQ from './PriorityQueue' import { PriorityQueue } from './PriorityQueue' -import { empty, has, HashSet, insert } from './HashSet' +import { hashSet, has, HashSet, insert } from './HashSet' import { initial, node, Node, nodeOrd, toArray } from './Node' export type Eq = (a: S, b: S) => boolean @@ -26,7 +26,7 @@ const isDone: (eq: Eq, node: S) => (state: S) => boolean = const openSet: (state: S) => OpenSet = (state: S) => PQ.of(initial(state)) -const closedSet: () => ClosedSet = () => empty() +const closedSet: () => ClosedSet = () => hashSet() export const depthFirstSearch: Search = async ( initial: S, diff --git a/src/HashSet.ts b/src/HashSet.ts index da34a19..0c93276 100644 --- a/src/HashSet.ts +++ b/src/HashSet.ts @@ -19,10 +19,10 @@ export type Eq = (x: T, y: T) => boolean * * @example * ```ts - * assert.deepStrictEqual(empty(), [2]) + * assert.deepStrictEqual(hashSet(), [2]) * ``` */ -export const empty: () => HashSet = () => [] +export const hashSet: () => HashSet = () => [] /** * Inserts an element for a given HashSet. Does not check if the element is already present diff --git a/tests/HashSet.test.ts b/tests/HashSet.test.ts index 9775558..0918b35 100644 --- a/tests/HashSet.test.ts +++ b/tests/HashSet.test.ts @@ -1,6 +1,6 @@ import { assert, describe, it } from 'vitest' -import { empty, has, insert } from '../src/HashSet' +import { hashSet, has, insert } from '../src/HashSet' type Vector = { x: number @@ -10,7 +10,7 @@ type Vector = { describe('HashSet', () => { describe('constructor', () => { it('creates empty', () => { - assert.deepStrictEqual(empty(), []) + assert.deepStrictEqual(hashSet(), []) }) }) @@ -21,7 +21,7 @@ describe('HashSet', () => { y: 8, } - const hashSet = insert(vector)(empty()) + const hashSet = insert(vector)(hashSet()) const expectedHashSetValues: Vector[] = [ { @@ -35,7 +35,7 @@ describe('HashSet', () => { describe('has', () => { it('does not have element for an empty HashSet', () => { - const hashSet = empty() + const hashSet = hashSet() const hasVector = has( (a: Vector, b: Vector) => a.x === b.x && b.y === b.y, @@ -54,7 +54,7 @@ describe('HashSet', () => { y: 8, } - const hashSet = insert(vector)(empty()) + const hashSet = insert(vector)(hashSet()) const hasVector = has( (a: Vector, b: Vector) => a.x === b.x && b.y === b.y, @@ -69,7 +69,7 @@ describe('HashSet', () => { y: 8, } - const hashSet = insert(vector)(empty()) + const hashSet = insert(vector)(hashSet()) const hasVector = has( (a: Vector, b: Vector) => a.x === b.x && b.y === b.y, @@ -88,7 +88,7 @@ describe('HashSet', () => { y: 8, } - const hashSet = insert(vector)(empty()) + const hashSet = insert(vector)(hashSet()) const hasVector = has( (a: Vector, b: Vector) => a.x === b.x && b.y === b.y, From 1229c4ccecfa8be26a0c432cbbc3f689eb387231 Mon Sep 17 00:00:00 2001 From: benjides Date: Mon, 2 Feb 2026 15:59:45 +0100 Subject: [PATCH 100/118] Rename PriorityQueue.of to priorityQueue --- src/DepthFirstSearch.ts | 2 +- src/PriorityQueue.ts | 12 +++++++----- tests/PriorityQueue.test.ts | 6 +++--- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/DepthFirstSearch.ts b/src/DepthFirstSearch.ts index fd668bb..5424aae 100644 --- a/src/DepthFirstSearch.ts +++ b/src/DepthFirstSearch.ts @@ -24,7 +24,7 @@ const isDone: (eq: Eq, node: S) => (state: S) => boolean = eq(node, state) const openSet: (state: S) => OpenSet = (state: S) => - PQ.of(initial(state)) + PQ.priorityQueue(initial(state)) const closedSet: () => ClosedSet = () => hashSet() diff --git a/src/PriorityQueue.ts b/src/PriorityQueue.ts index 6bd012f..858947c 100644 --- a/src/PriorityQueue.ts +++ b/src/PriorityQueue.ts @@ -38,10 +38,12 @@ export const empty: () => PriorityQueue = () => [] * * @example * ```ts - * assert.deepStrictEqual(values(of('Hello World!')), ['Hello World!']) + * assert.deepStrictEqual(values(priorityQueue('Hello World!')), ['Hello World!']) * ``` */ -export const of: (data: T) => PriorityQueue = (data: T) => [data] +export const priorityQueue: (data: T) => PriorityQueue = (data: T) => [ + data, +] /** * Converts a given PriorityQueue into an array containing current values maintaining priority order @@ -50,7 +52,7 @@ export const of: (data: T) => PriorityQueue = (data: T) => [data] * * @example * ```ts - * assert.deepStrictEqual(values(of('Hello World!')), ['Hello World!']) + * assert.deepStrictEqual(values(priorityQueue('Hello World!')), ['Hello World!']) * ``` */ export const values = (priorityQueue: PriorityQueue) => priorityQueue @@ -93,10 +95,10 @@ export const insert: ( * * @example * ```ts - * const [element, priorityQueue] = poll(of('Hello World!')) + * const [element, queue] = poll(priorityQueue('Hello World!')) * * assert.deepStrictEqual(element, 'Hello World!') - * assert.deepStrictEqual(values(priorityQueue), []) + * assert.deepStrictEqual(values(queue), []) * ``` * * @example diff --git a/tests/PriorityQueue.test.ts b/tests/PriorityQueue.test.ts index 1f438b6..a9e30b4 100644 --- a/tests/PriorityQueue.test.ts +++ b/tests/PriorityQueue.test.ts @@ -2,7 +2,7 @@ import { assert, describe, it } from 'vitest' import { empty, insert, - of, + priorityQueue, Ord, poll, type PriorityQueue, @@ -47,7 +47,7 @@ describe('PriorityQueue', () => { }) it('creates for element', () => { - const priorityQueue: PriorityQueue = of(jane) + const queue: PriorityQueue = priorityQueue(jane) const expectedPriorityQueueValues: Person[] = [ { @@ -55,7 +55,7 @@ describe('PriorityQueue', () => { name: 'Jane', }, ] - assert.deepStrictEqual(values(priorityQueue), expectedPriorityQueueValues) + assert.deepStrictEqual(values(queue), expectedPriorityQueueValues) }) }) describe('insert', () => { From 78c703532154a4b7d75e4cfe967734861ba32a89 Mon Sep 17 00:00:00 2001 From: benjides Date: Mon, 2 Feb 2026 16:03:29 +0100 Subject: [PATCH 101/118] Fix hashSet references --- src/HashSet.ts | 6 +++--- tests/HashSet.test.ts | 20 ++++++++++---------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/HashSet.ts b/src/HashSet.ts index 0c93276..35ae6e0 100644 --- a/src/HashSet.ts +++ b/src/HashSet.ts @@ -29,7 +29,7 @@ export const hashSet: () => HashSet = () => [] * * @example * ```ts - * assert.deepStrictEqual(insert(2)(empty()), [2]) + * assert.deepStrictEqual(insert(2)(hashSet()), [2]) * ``` */ export const insert: (element: T) => (hashSet: HashSet) => HashSet = @@ -41,14 +41,14 @@ export const insert: (element: T) => (hashSet: HashSet) => HashSet = * * @example * ```ts - * const hashSet : hashSet = insert(2)(empty()) + * const hashSet : hashSet = insert(2)(hashSet()) * const eq : Eq = (x: number, y: number) => x === y * * assert.isTrue(has(eq)(2)(hashSet)) * ``` * @example * ```ts - * const hashSet : hashSet = insert(2)(empty()) + * const hashSet : hashSet = insert(2)(hashSet()) * const eq : Eq = (x: number, y: number) => x === y * * assert.isFalse(has(eq)(87557)(hashSet)) diff --git a/tests/HashSet.test.ts b/tests/HashSet.test.ts index 0918b35..a4d21c8 100644 --- a/tests/HashSet.test.ts +++ b/tests/HashSet.test.ts @@ -21,7 +21,7 @@ describe('HashSet', () => { y: 8, } - const hashSet = insert(vector)(hashSet()) + const vectorHashSet = insert(vector)(hashSet()) const expectedHashSetValues: Vector[] = [ { @@ -29,13 +29,13 @@ describe('HashSet', () => { y: 8, }, ] - assert.deepStrictEqual(hashSet, expectedHashSetValues) + assert.deepStrictEqual(vectorHashSet, expectedHashSetValues) }) }) describe('has', () => { it('does not have element for an empty HashSet', () => { - const hashSet = hashSet() + const vectorHashSet = hashSet() const hasVector = has( (a: Vector, b: Vector) => a.x === b.x && b.y === b.y, @@ -45,7 +45,7 @@ describe('HashSet', () => { x: -3, y: 8, } - assert.isFalse(hasVector(vector)(hashSet)) + assert.isFalse(hasVector(vector)(vectorHashSet)) }) it('has element for an element present in the HashSet', () => { @@ -54,13 +54,13 @@ describe('HashSet', () => { y: 8, } - const hashSet = insert(vector)(hashSet()) + const vectorHashSet = insert(vector)(hashSet()) const hasVector = has( (a: Vector, b: Vector) => a.x === b.x && b.y === b.y, ) - assert.isTrue(hasVector(vector)(hashSet)) + assert.isTrue(hasVector(vector)(vectorHashSet)) }) it('has element for an element present in the HashSet with swapped properties', () => { @@ -69,7 +69,7 @@ describe('HashSet', () => { y: 8, } - const hashSet = insert(vector)(hashSet()) + const vectorHashSet = insert(vector)(hashSet()) const hasVector = has( (a: Vector, b: Vector) => a.x === b.x && b.y === b.y, @@ -79,7 +79,7 @@ describe('HashSet', () => { y: 8, x: -3, } - assert.isTrue(hasVector(swappedPropertiesVector)(hashSet)) + assert.isTrue(hasVector(swappedPropertiesVector)(vectorHashSet)) }) it('not present HashSet', () => { @@ -88,13 +88,13 @@ describe('HashSet', () => { y: 8, } - const hashSet = insert(vector)(hashSet()) + const vectorHashSet = insert(vector)(hashSet()) const hasVector = has( (a: Vector, b: Vector) => a.x === b.x && b.y === b.y, ) - assert.isFalse(hasVector({ x: 1, y: -18 })(hashSet)) + assert.isFalse(hasVector({ x: 1, y: -18 })(vectorHashSet)) }) }) }) From 7b7414559a937b4cadc258491bca4e81bda28780 Mon Sep 17 00:00:00 2001 From: benjides Date: Mon, 2 Feb 2026 16:05:41 +0100 Subject: [PATCH 102/118] Replace * imports for PriorityQueue --- src/DepthFirstSearch.ts | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/DepthFirstSearch.ts b/src/DepthFirstSearch.ts index 5424aae..5f2bf60 100644 --- a/src/DepthFirstSearch.ts +++ b/src/DepthFirstSearch.ts @@ -1,6 +1,10 @@ -import * as PQ from './PriorityQueue' -import { PriorityQueue } from './PriorityQueue' -import { hashSet, has, HashSet, insert } from './HashSet' +import { + poll, + priorityQueue, + PriorityQueue, + insert as priorityQueueInsert, +} from './PriorityQueue' +import { hashSet, has, HashSet, insert as hashSetInsert } from './HashSet' import { initial, node, Node, nodeOrd, toArray } from './Node' export type Eq = (a: S, b: S) => boolean @@ -24,7 +28,7 @@ const isDone: (eq: Eq, node: S) => (state: S) => boolean = eq(node, state) const openSet: (state: S) => OpenSet = (state: S) => - PQ.priorityQueue(initial(state)) + priorityQueue(initial(state)) const closedSet: () => ClosedSet = () => hashSet() @@ -46,7 +50,7 @@ export const depthFirstSearch: Search = async ( const nodeInsert: (node: Node) => (openSet: OpenSet) => OpenSet = (node: Node) => (openSet: OpenSet) => - PQ.insert(nodeOrd())(node)(openSet) + priorityQueueInsert(nodeOrd())(node)(openSet) const hasBeenVisited = (eq: Eq, hashSet: HashSet) => @@ -60,7 +64,7 @@ const expandRecursively = async ( eq: Eq, expand: Expand, ): Promise => { - const [n, priorityQueue] = PQ.poll(openSet) + const [n, priorityQueue] = poll(openSet) if (n === null) { return [] @@ -70,7 +74,7 @@ const expandRecursively = async ( return toArray(n) } - const closed: HashSet = insert(n.state)(closedSet) + const closed: HashSet = hashSetInsert(n.state)(closedSet) const newStates: OpenSet = (await Promise.all(expand(n.state))) .filter((state: S) => !hasBeenVisited(eq, closed)(state)) From 5241def9ffd0dbba6960d43e782bb4a0421e3de3 Mon Sep 17 00:00:00 2001 From: benjides Date: Mon, 2 Feb 2026 22:17:38 +0100 Subject: [PATCH 103/118] Rename variables --- src/DepthFirstSearch.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/DepthFirstSearch.ts b/src/DepthFirstSearch.ts index 5f2bf60..f5e652a 100644 --- a/src/DepthFirstSearch.ts +++ b/src/DepthFirstSearch.ts @@ -53,9 +53,9 @@ const nodeInsert: (node: Node) => (openSet: OpenSet) => OpenSet = priorityQueueInsert(nodeOrd())(node)(openSet) const hasBeenVisited = - (eq: Eq, hashSet: HashSet) => + (eq: Eq, closedSet: ClosedSet) => (state: S) => - has(eq)(state)(hashSet) + has(eq)(state)(closedSet) const expandRecursively = async ( openSet: OpenSet, @@ -74,7 +74,7 @@ const expandRecursively = async ( return toArray(n) } - const closed: HashSet = hashSetInsert(n.state)(closedSet) + const closed: ClosedSet = hashSetInsert(n.state)(closedSet) const newStates: OpenSet = (await Promise.all(expand(n.state))) .filter((state: S) => !hasBeenVisited(eq, closed)(state)) From a2d71f806641eeedbb82290af04946e4bf389557 Mon Sep 17 00:00:00 2001 From: benjides Date: Mon, 2 Feb 2026 23:59:03 +0100 Subject: [PATCH 104/118] Add evaluationFunction for Node creation --- src/DepthFirstSearch.ts | 9 ++++++++- src/Node.ts | 7 ++++--- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/DepthFirstSearch.ts b/src/DepthFirstSearch.ts index f5e652a..02b3ff3 100644 --- a/src/DepthFirstSearch.ts +++ b/src/DepthFirstSearch.ts @@ -32,6 +32,11 @@ const openSet: (state: S) => OpenSet = (state: S) => const closedSet: () => ClosedSet = () => hashSet() +const depthFirstSearchEvaluationFunction: () => (node: Node) => number = + () => + (node: Node) => + node.key - 1 + export const depthFirstSearch: Search = async ( initial: S, goal: S, @@ -42,6 +47,7 @@ export const depthFirstSearch: Search = async ( openSet(initial), closedSet(), isDone(eq, goal), + node(depthFirstSearchEvaluationFunction()), eq, expand, ) @@ -61,6 +67,7 @@ const expandRecursively = async ( openSet: OpenSet, closedSet: ClosedSet, isGoal: (state: S) => boolean, + node: (node: Node) => (state: S) => Node, eq: Eq, expand: Expand, ): Promise => { @@ -84,5 +91,5 @@ const expandRecursively = async ( priorityQueue, ) - return expandRecursively(newStates, closed, isGoal, eq, expand) + return expandRecursively(newStates, closed, isGoal, node, eq, expand) } diff --git a/src/Node.ts b/src/Node.ts index 5793b57..a87afaf 100644 --- a/src/Node.ts +++ b/src/Node.ts @@ -6,10 +6,11 @@ export type Node = { parent: Node | null } -export const node: (node: Node) => (state: S) => Node = - (node: Node) => +export const node = + (evaluationFunction: (node: Node) => number) => + (node: Node) => (state: S) => ({ - key: node.key - 1, + key: evaluationFunction(node), state: state, parent: node, }) From 42cf7ecd2c4f68d5ef81019ea4b2d1d8efef29ad Mon Sep 17 00:00:00 2001 From: benjides Date: Tue, 3 Feb 2026 00:06:50 +0100 Subject: [PATCH 105/118] Extract evaluate type --- src/DepthFirstSearch.ts | 6 ++++-- src/Node.ts | 5 +++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/DepthFirstSearch.ts b/src/DepthFirstSearch.ts index 02b3ff3..291e3d5 100644 --- a/src/DepthFirstSearch.ts +++ b/src/DepthFirstSearch.ts @@ -22,6 +22,8 @@ type OpenSet = PriorityQueue> type ClosedSet = HashSet +export type Evaluate = (node: Node) => number + const isDone: (eq: Eq, node: S) => (state: S) => boolean = (eq: Eq, node: T) => (state: T) => @@ -32,7 +34,7 @@ const openSet: (state: S) => OpenSet = (state: S) => const closedSet: () => ClosedSet = () => hashSet() -const depthFirstSearchEvaluationFunction: () => (node: Node) => number = +const depthFirstSearchEvaluate: () => Evaluate = () => (node: Node) => node.key - 1 @@ -47,7 +49,7 @@ export const depthFirstSearch: Search = async ( openSet(initial), closedSet(), isDone(eq, goal), - node(depthFirstSearchEvaluationFunction()), + node(depthFirstSearchEvaluate()), eq, expand, ) diff --git a/src/Node.ts b/src/Node.ts index a87afaf..672342c 100644 --- a/src/Node.ts +++ b/src/Node.ts @@ -1,4 +1,5 @@ import { Ord } from './PriorityQueue' +import { Evaluate } from './DepthFirstSearch' export type Node = { key: number @@ -7,10 +8,10 @@ export type Node = { } export const node = - (evaluationFunction: (node: Node) => number) => + (evaluate: Evaluate) => (node: Node) => (state: S) => ({ - key: evaluationFunction(node), + key: evaluate(node), state: state, parent: node, }) From 3020b26988ca6a357032e2a5813d4bce81b8fa04 Mon Sep 17 00:00:00 2001 From: benjides Date: Tue, 3 Feb 2026 00:13:41 +0100 Subject: [PATCH 106/118] Extract search constant --- src/DepthFirstSearch.ts | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/src/DepthFirstSearch.ts b/src/DepthFirstSearch.ts index 291e3d5..23e7624 100644 --- a/src/DepthFirstSearch.ts +++ b/src/DepthFirstSearch.ts @@ -39,21 +39,19 @@ const depthFirstSearchEvaluate: () => Evaluate = (node: Node) => node.key - 1 -export const depthFirstSearch: Search = async ( - initial: S, - goal: S, - eq: Eq, - expand: Expand, -): Promise => { - return expandRecursively( - openSet(initial), - closedSet(), - isDone(eq, goal), - node(depthFirstSearchEvaluate()), - eq, - expand, - ) -} +export const search = + (evaluate: Evaluate) => + async (initial: S, goal: S, eq: Eq, expand: Expand): Promise => + expandRecursively( + openSet(initial), + closedSet(), + isDone(eq, goal), + node(evaluate), + eq, + expand, + ) + +export const depthFirstSearch: Search = search(depthFirstSearchEvaluate()) const nodeInsert: (node: Node) => (openSet: OpenSet) => OpenSet = (node: Node) => From c725ba522b8e971bf21e33093ebec952371bd707 Mon Sep 17 00:00:00 2001 From: benjides Date: Tue, 3 Feb 2026 00:19:56 +0100 Subject: [PATCH 107/118] Extract Search file --- src/DepthFirstSearch.ts | 90 +---------------------------------------- src/Node.ts | 3 +- src/Search.ts | 88 ++++++++++++++++++++++++++++++++++++++++ tests/Grid.ts | 2 +- 4 files changed, 93 insertions(+), 90 deletions(-) create mode 100644 src/Search.ts diff --git a/src/DepthFirstSearch.ts b/src/DepthFirstSearch.ts index 23e7624..d62abbd 100644 --- a/src/DepthFirstSearch.ts +++ b/src/DepthFirstSearch.ts @@ -1,95 +1,9 @@ -import { - poll, - priorityQueue, - PriorityQueue, - insert as priorityQueueInsert, -} from './PriorityQueue' -import { hashSet, has, HashSet, insert as hashSetInsert } from './HashSet' -import { initial, node, Node, nodeOrd, toArray } from './Node' - -export type Eq = (a: S, b: S) => boolean - -export type Expand = (s: S) => Promise[] - -export type Search = ( - initial: S, - goal: S, - eq: Eq, - expand: Expand, -) => Promise - -type OpenSet = PriorityQueue> - -type ClosedSet = HashSet - -export type Evaluate = (node: Node) => number - -const isDone: (eq: Eq, node: S) => (state: S) => boolean = - (eq: Eq, node: T) => - (state: T) => - eq(node, state) - -const openSet: (state: S) => OpenSet = (state: S) => - priorityQueue(initial(state)) - -const closedSet: () => ClosedSet = () => hashSet() +import { Node } from './Node' +import { Evaluate, Search, search } from './Search' const depthFirstSearchEvaluate: () => Evaluate = () => (node: Node) => node.key - 1 -export const search = - (evaluate: Evaluate) => - async (initial: S, goal: S, eq: Eq, expand: Expand): Promise => - expandRecursively( - openSet(initial), - closedSet(), - isDone(eq, goal), - node(evaluate), - eq, - expand, - ) - export const depthFirstSearch: Search = search(depthFirstSearchEvaluate()) - -const nodeInsert: (node: Node) => (openSet: OpenSet) => OpenSet = - (node: Node) => - (openSet: OpenSet) => - priorityQueueInsert(nodeOrd())(node)(openSet) - -const hasBeenVisited = - (eq: Eq, closedSet: ClosedSet) => - (state: S) => - has(eq)(state)(closedSet) - -const expandRecursively = async ( - openSet: OpenSet, - closedSet: ClosedSet, - isGoal: (state: S) => boolean, - node: (node: Node) => (state: S) => Node, - eq: Eq, - expand: Expand, -): Promise => { - const [n, priorityQueue] = poll(openSet) - - if (n === null) { - return [] - } - - if (isGoal(n.state)) { - return toArray(n) - } - - const closed: ClosedSet = hashSetInsert(n.state)(closedSet) - - const newStates: OpenSet = (await Promise.all(expand(n.state))) - .filter((state: S) => !hasBeenVisited(eq, closed)(state)) - .map(node(n)) - .reduce( - (openSet: OpenSet, node: Node) => nodeInsert(node)(openSet), - priorityQueue, - ) - - return expandRecursively(newStates, closed, isGoal, node, eq, expand) -} diff --git a/src/Node.ts b/src/Node.ts index 672342c..b5fd2d3 100644 --- a/src/Node.ts +++ b/src/Node.ts @@ -1,5 +1,6 @@ import { Ord } from './PriorityQueue' -import { Evaluate } from './DepthFirstSearch' + +import { Evaluate } from './Search' export type Node = { key: number diff --git a/src/Search.ts b/src/Search.ts new file mode 100644 index 0000000..d1e6d69 --- /dev/null +++ b/src/Search.ts @@ -0,0 +1,88 @@ +import { initial, node, Node, nodeOrd, toArray } from './Node' +import { + insert as priorityQueueInsert, + poll, + priorityQueue, + PriorityQueue, +} from './PriorityQueue' +import { has, hashSet, HashSet, insert as hashSetInsert } from './HashSet' + +export type Eq = (a: S, b: S) => boolean + +export type Expand = (s: S) => Promise[] + +export type Search = ( + initial: S, + goal: S, + eq: Eq, + expand: Expand, +) => Promise + +type OpenSet = PriorityQueue> + +const openSet: (state: S) => OpenSet = (state: S) => + priorityQueue(initial(state)) + +type ClosedSet = HashSet + +const closedSet: () => ClosedSet = () => hashSet() + +export type Evaluate = (node: Node) => number + +const isDone: (eq: Eq, node: S) => (state: S) => boolean = + (eq: Eq, node: T) => + (state: T) => + eq(node, state) + +export const search = + (evaluate: Evaluate) => + async (initial: S, goal: S, eq: Eq, expand: Expand): Promise => + expandRecursively( + openSet(initial), + closedSet(), + isDone(eq, goal), + node(evaluate), + eq, + expand, + ) + +const nodeInsert: (node: Node) => (openSet: OpenSet) => OpenSet = + (node: Node) => + (openSet: OpenSet) => + priorityQueueInsert(nodeOrd())(node)(openSet) + +const hasBeenVisited: (eq: Eq, closedSet: ClosedSet) => (state: S) => boolean = + (eq: Eq, closedSet: ClosedSet) => + (state: S) => + has(eq)(state)(closedSet) + +const expandRecursively = async ( + openSet: OpenSet, + closedSet: ClosedSet, + isGoal: (state: S) => boolean, + node: (node: Node) => (state: S) => Node, + eq: Eq, + expand: Expand, +): Promise => { + const [n, priorityQueue] = poll(openSet) + + if (n === null) { + return [] + } + + if (isGoal(n.state)) { + return toArray(n) + } + + const closed: ClosedSet = hashSetInsert(n.state)(closedSet) + + const newStates: OpenSet = (await Promise.all(expand(n.state))) + .filter((state: S) => !hasBeenVisited(eq, closed)(state)) + .map(node(n)) + .reduce( + (openSet: OpenSet, node: Node) => nodeInsert(node)(openSet), + priorityQueue, + ) + + return expandRecursively(newStates, closed, isGoal, node, eq, expand) +} diff --git a/tests/Grid.ts b/tests/Grid.ts index 62641dc..5cf92ea 100644 --- a/tests/Grid.ts +++ b/tests/Grid.ts @@ -1,4 +1,4 @@ -import type { Eq, Expand } from '../src/DepthFirstSearch' +import { Eq, Expand } from '../src/Search' export type Grid = { rows: number From a36067b5140f400810635342f183080d8fe9f495 Mon Sep 17 00:00:00 2001 From: benjides Date: Tue, 3 Feb 2026 01:24:32 +0100 Subject: [PATCH 108/118] Add BestFirstSearch and tests --- src/BestFirstSearch.ts | 9 ++ tests/BestFirstSearch.test.ts | 218 ++++++++++++++++++++++++++++++++++ 2 files changed, 227 insertions(+) create mode 100644 src/BestFirstSearch.ts create mode 100644 tests/BestFirstSearch.test.ts diff --git a/src/BestFirstSearch.ts b/src/BestFirstSearch.ts new file mode 100644 index 0000000..15ef8db --- /dev/null +++ b/src/BestFirstSearch.ts @@ -0,0 +1,9 @@ +import { Node } from './Node' +import { Evaluate, Search, search } from './Search' + +const bestFirstSearchEvaluate: () => Evaluate = + () => + (node: Node) => + node.key + 1 + +export const bestFirstSearch: Search = search(bestFirstSearchEvaluate()) diff --git a/tests/BestFirstSearch.test.ts b/tests/BestFirstSearch.test.ts new file mode 100644 index 0000000..bc33ba0 --- /dev/null +++ b/tests/BestFirstSearch.test.ts @@ -0,0 +1,218 @@ +import { describe, it, assert } from 'vitest' +import { bestFirstSearch } from '../src/BestFirstSearch' +import { + down, + expand, + type Grid, + left, + type Position, + positionEquality, + right, + stall, + up, +} from './Grid' + +describe('BestFirstSearch', () => { + it('solves when initial and goal are equals', async () => { + const grid: Grid = { rows: 1, columns: 1 } + const initial: Position = { + x: 0, + y: 0, + } + const goal: Position = { + x: 0, + y: 0, + } + + const actualSolution = await bestFirstSearch( + initial, + goal, + positionEquality, + expand(grid)([]), + ) + + const expectedSolution: Position[] = [{ x: 0, y: 0 }] + assert.deepStrictEqual(actualSolution, expectedSolution) + }) + + it('solves after expanding once', async () => { + const corridor: Grid = { rows: 1, columns: 2 } + const initial: Position = { + x: 0, + y: 0, + } + const goal: Position = { + x: 1, + y: 0, + } + + const actualSolution = await bestFirstSearch( + initial, + goal, + positionEquality, + expand(corridor)([right]), + ) + + const expectedSolution: Position[] = [ + { x: 0, y: 0 }, + { x: 1, y: 0 }, + ] + assert.deepStrictEqual(actualSolution, expectedSolution) + }) + + it('solves after expanding twice', async () => { + const corridor: Grid = { rows: 1, columns: 3 } + const initial: Position = { + x: 0, + y: 0, + } + const goal: Position = { + x: 2, + y: 0, + } + + const actualSolution = await bestFirstSearch( + initial, + goal, + positionEquality, + expand(corridor)([right]), + ) + + const expectedSolution: Position[] = [ + { x: 0, y: 0 }, + { x: 1, y: 0 }, + { x: 2, y: 0 }, + ] + assert.deepStrictEqual(actualSolution, expectedSolution) + }) + + it('solves after expanding n times', async () => { + const grid: Grid = { rows: 3, columns: 2 } + const initial: Position = { + x: 0, + y: 1, + } + const goal: Position = { + x: 1, + y: 2, + } + + const actualSolution = await bestFirstSearch( + initial, + goal, + positionEquality, + expand(grid)([up, down, right, left]), + ) + + const expectedSolution: Position[] = [ + { x: 0, y: 1 }, + { x: 0, y: 2 }, + { x: 1, y: 2 }, + ] + assert.deepStrictEqual(actualSolution, expectedSolution) + }) + + it('solves best solution', async () => { + const grid: Grid = { rows: 3, columns: 3 } + const initial: Position = { + x: 0, + y: 0, + } + const goal: Position = { + x: 2, + y: 2, + } + + const actualSolution = await bestFirstSearch( + initial, + goal, + positionEquality, + expand(grid)([up, down, right, left]), + ) + + const expectedSolution: Position[] = [ + { x: 0, y: 0 }, + { x: 0, y: 1 }, + { x: 0, y: 2 }, + { x: 1, y: 2 }, + { x: 2, y: 2 }, + ] + assert.deepStrictEqual(actualSolution, expectedSolution) + }) + + it('solves worst solution', async () => { + const grid: Grid = { rows: 3, columns: 3 } + const initial: Position = { + x: 0, + y: 0, + } + const goal: Position = { + x: 2, + y: 2, + } + + const actualSolution = await bestFirstSearch( + initial, + goal, + positionEquality, + expand(grid)([up, down, right, left]), + ) + + const expectedSolution: Position[] = [ + { x: 0, y: 0 }, + { x: 0, y: 1 }, + { x: 0, y: 2 }, + { x: 1, y: 2 }, + { x: 2, y: 2 }, + ] + assert.deepStrictEqual(actualSolution, expectedSolution) + }) + + it('return empty array for unsolvable searches', async () => { + const grid: Grid = { rows: 2, columns: 2 } + const initial: Position = { + x: 0, + y: 1, + } + const goal: Position = { + x: 100, + y: 100, + } + + const actualSolution = await bestFirstSearch( + initial, + goal, + positionEquality, + expand(grid)([up]), + ) + + const expectedSolution: Position[] = [] + assert.deepStrictEqual(actualSolution, expectedSolution) + }) + + it('filters already visited states on expanding', async () => { + const corridor: Grid = { rows: 1, columns: 3 } + const initial: Position = { + x: 0, + y: 0, + } + const goal: Position = { + x: 2, + y: 0, + } + + const actualSolution = await bestFirstSearch( + initial, + goal, + positionEquality, + expand(corridor)([stall, right]), + ) + + const expectedSolution: Position[] = [ + { x: 0, y: 0 }, + { x: 1, y: 0 }, + { x: 2, y: 0 }, + ] + assert.deepStrictEqual(actualSolution, expectedSolution) + }) +}) From b8a8365d1ce5c3934edb1e1de421a95d5c2215b6 Mon Sep 17 00:00:00 2001 From: benjides Date: Tue, 3 Feb 2026 01:24:49 +0100 Subject: [PATCH 109/118] Reformat --- src/Search.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Search.ts b/src/Search.ts index d1e6d69..38a76b6 100644 --- a/src/Search.ts +++ b/src/Search.ts @@ -51,7 +51,10 @@ const nodeInsert: (node: Node) => (openSet: OpenSet) => OpenSet = (openSet: OpenSet) => priorityQueueInsert(nodeOrd())(node)(openSet) -const hasBeenVisited: (eq: Eq, closedSet: ClosedSet) => (state: S) => boolean = +const hasBeenVisited: ( + eq: Eq, + closedSet: ClosedSet, +) => (state: S) => boolean = (eq: Eq, closedSet: ClosedSet) => (state: S) => has(eq)(state)(closedSet) From cb0870d406f2e0df25ce03671fa96a46bb364b34 Mon Sep 17 00:00:00 2001 From: benjides Date: Tue, 3 Feb 2026 23:50:11 +0100 Subject: [PATCH 110/118] Add return type --- src/Node.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Node.ts b/src/Node.ts index b5fd2d3..2e2edc5 100644 --- a/src/Node.ts +++ b/src/Node.ts @@ -11,7 +11,7 @@ export type Node = { export const node = (evaluate: Evaluate) => (node: Node) => - (state: S) => ({ + (state: S): Node => ({ key: evaluate(node), state: state, parent: node, From 4ceee43427a92ce8f66bc04c300ab9e73c866565 Mon Sep 17 00:00:00 2001 From: benjides Date: Tue, 3 Feb 2026 23:51:11 +0100 Subject: [PATCH 111/118] Add Node.depth property --- src/Node.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Node.ts b/src/Node.ts index 2e2edc5..5f02b04 100644 --- a/src/Node.ts +++ b/src/Node.ts @@ -6,6 +6,7 @@ export type Node = { key: number state: S parent: Node | null + depth: number } export const node = @@ -15,12 +16,14 @@ export const node = key: evaluate(node), state: state, parent: node, + depth: node.depth + 1, }) export const initial: (state: S) => Node = (state: S): Node => ({ key: 0, state: state, parent: null, + depth: 0, }) export const toArray: (node: Node) => S[] = (node: Node) => From 345f4121d6904bcfd8a4b65ab196c18bc0774287 Mon Sep 17 00:00:00 2001 From: benjides Date: Wed, 4 Feb 2026 00:12:05 +0100 Subject: [PATCH 112/118] Use Evaluate for Ord> --- src/BestFirstSearch.ts | 2 +- src/DepthFirstSearch.ts | 2 +- src/Node.ts | 6 +++--- src/Search.ts | 23 ++++++++++++++++++----- 4 files changed, 23 insertions(+), 10 deletions(-) diff --git a/src/BestFirstSearch.ts b/src/BestFirstSearch.ts index 15ef8db..085bb6e 100644 --- a/src/BestFirstSearch.ts +++ b/src/BestFirstSearch.ts @@ -4,6 +4,6 @@ import { Evaluate, Search, search } from './Search' const bestFirstSearchEvaluate: () => Evaluate = () => (node: Node) => - node.key + 1 + node.depth export const bestFirstSearch: Search = search(bestFirstSearchEvaluate()) diff --git a/src/DepthFirstSearch.ts b/src/DepthFirstSearch.ts index d62abbd..d126138 100644 --- a/src/DepthFirstSearch.ts +++ b/src/DepthFirstSearch.ts @@ -4,6 +4,6 @@ import { Evaluate, Search, search } from './Search' const depthFirstSearchEvaluate: () => Evaluate = () => (node: Node) => - node.key - 1 + -node.depth export const depthFirstSearch: Search = search(depthFirstSearchEvaluate()) diff --git a/src/Node.ts b/src/Node.ts index 5f02b04..2c35e0a 100644 --- a/src/Node.ts +++ b/src/Node.ts @@ -29,7 +29,7 @@ export const initial: (state: S) => Node = (state: S): Node => ({ export const toArray: (node: Node) => S[] = (node: Node) => node.parent === null ? [node.state] : [...toArray(node.parent), node.state] -export const nodeOrd: () => Ord> = - () => +export const nodeOrd: (evaluate: Evaluate) => Ord> = + (evaluate: Evaluate) => (a: Node, b: Node) => - b.key - a.key + evaluate(b) - evaluate(a) diff --git a/src/Search.ts b/src/Search.ts index 38a76b6..7dc3e61 100644 --- a/src/Search.ts +++ b/src/Search.ts @@ -40,16 +40,20 @@ export const search = expandRecursively( openSet(initial), closedSet(), + nodeInsert(evaluate), isDone(eq, goal), node(evaluate), eq, expand, ) -const nodeInsert: (node: Node) => (openSet: OpenSet) => OpenSet = - (node: Node) => +const nodeInsert: ( + evaluate: Evaluate, +) => (node: Node) => (openSet: OpenSet) => OpenSet = + (evaluate: Evaluate) => + (node: Node) => (openSet: OpenSet) => - priorityQueueInsert(nodeOrd())(node)(openSet) + priorityQueueInsert(nodeOrd(evaluate))(node)(openSet) const hasBeenVisited: ( eq: Eq, @@ -62,6 +66,7 @@ const hasBeenVisited: ( const expandRecursively = async ( openSet: OpenSet, closedSet: ClosedSet, + openSetInsert: (node: Node) => (openSet: OpenSet) => OpenSet, isGoal: (state: S) => boolean, node: (node: Node) => (state: S) => Node, eq: Eq, @@ -83,9 +88,17 @@ const expandRecursively = async ( .filter((state: S) => !hasBeenVisited(eq, closed)(state)) .map(node(n)) .reduce( - (openSet: OpenSet, node: Node) => nodeInsert(node)(openSet), + (openSet: OpenSet, node: Node) => openSetInsert(node)(openSet), priorityQueue, ) - return expandRecursively(newStates, closed, isGoal, node, eq, expand) + return expandRecursively( + newStates, + closed, + openSetInsert, + isGoal, + node, + eq, + expand, + ) } From ecb7ec0b426fd9279bcc31e49866f789635b0a67 Mon Sep 17 00:00:00 2001 From: benjides Date: Wed, 4 Feb 2026 00:20:21 +0100 Subject: [PATCH 113/118] Remove Node key field --- src/Node.ts | 5 +---- src/Search.ts | 2 +- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/Node.ts b/src/Node.ts index 2c35e0a..d5aa166 100644 --- a/src/Node.ts +++ b/src/Node.ts @@ -3,24 +3,21 @@ import { Ord } from './PriorityQueue' import { Evaluate } from './Search' export type Node = { - key: number state: S parent: Node | null depth: number } export const node = - (evaluate: Evaluate) => + () => (node: Node) => (state: S): Node => ({ - key: evaluate(node), state: state, parent: node, depth: node.depth + 1, }) export const initial: (state: S) => Node = (state: S): Node => ({ - key: 0, state: state, parent: null, depth: 0, diff --git a/src/Search.ts b/src/Search.ts index 7dc3e61..6d8a161 100644 --- a/src/Search.ts +++ b/src/Search.ts @@ -42,7 +42,7 @@ export const search = closedSet(), nodeInsert(evaluate), isDone(eq, goal), - node(evaluate), + node(), eq, expand, ) From b7b250436afe8ddc9ac75842d0204035b09f118b Mon Sep 17 00:00:00 2001 From: benjides Date: Wed, 4 Feb 2026 00:35:07 +0100 Subject: [PATCH 114/118] Simplify node function --- src/Node.ts | 3 +-- src/Search.ts | 12 +----------- 2 files changed, 2 insertions(+), 13 deletions(-) diff --git a/src/Node.ts b/src/Node.ts index d5aa166..9586c67 100644 --- a/src/Node.ts +++ b/src/Node.ts @@ -9,8 +9,7 @@ export type Node = { } export const node = - () => - (node: Node) => + (node: Node) => (state: S): Node => ({ state: state, parent: node, diff --git a/src/Search.ts b/src/Search.ts index 6d8a161..2996043 100644 --- a/src/Search.ts +++ b/src/Search.ts @@ -42,7 +42,6 @@ export const search = closedSet(), nodeInsert(evaluate), isDone(eq, goal), - node(), eq, expand, ) @@ -68,7 +67,6 @@ const expandRecursively = async ( closedSet: ClosedSet, openSetInsert: (node: Node) => (openSet: OpenSet) => OpenSet, isGoal: (state: S) => boolean, - node: (node: Node) => (state: S) => Node, eq: Eq, expand: Expand, ): Promise => { @@ -92,13 +90,5 @@ const expandRecursively = async ( priorityQueue, ) - return expandRecursively( - newStates, - closed, - openSetInsert, - isGoal, - node, - eq, - expand, - ) + return expandRecursively(newStates, closed, openSetInsert, isGoal, eq, expand) } From fcf79f184075f4df1270d844dfa902bbec1fe81f Mon Sep 17 00:00:00 2001 From: benjides Date: Wed, 4 Feb 2026 11:04:48 +0100 Subject: [PATCH 115/118] Add generic typings to Search --- src/Search.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Search.ts b/src/Search.ts index 2996043..9a951b5 100644 --- a/src/Search.ts +++ b/src/Search.ts @@ -11,7 +11,7 @@ export type Eq = (a: S, b: S) => boolean export type Expand = (s: S) => Promise[] -export type Search = ( +export type Search = ( initial: S, goal: S, eq: Eq, @@ -34,7 +34,7 @@ const isDone: (eq: Eq, node: S) => (state: S) => boolean = (state: T) => eq(node, state) -export const search = +export const search: (evaluate: Evaluate) => Search = (evaluate: Evaluate) => async (initial: S, goal: S, eq: Eq, expand: Expand): Promise => expandRecursively( From df14e796ef42dee57234cf53a0d32cccbda69d10 Mon Sep 17 00:00:00 2001 From: benjides Date: Wed, 4 Feb 2026 13:22:58 +0100 Subject: [PATCH 116/118] Add AStar and tests --- src/AStar.ts | 13 +++ tests/AStar.test.ts | 223 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 236 insertions(+) create mode 100644 src/AStar.ts create mode 100644 tests/AStar.test.ts diff --git a/src/AStar.ts b/src/AStar.ts new file mode 100644 index 0000000..dbea52d --- /dev/null +++ b/src/AStar.ts @@ -0,0 +1,13 @@ +import { Node } from './Node' +import { Evaluate, Search, search } from './Search' + +const aStarEvaluate: (heuristics: Heuristics) => Evaluate = + (heuristics: Heuristics) => + (node: Node) => + node.depth + heuristics(node.state) + +export type Heuristics = (currentState: S) => number + +export const aStar: (heuristics: Heuristics) => Search = ( + heuristics: Heuristics, +) => search(aStarEvaluate(heuristics)) diff --git a/tests/AStar.test.ts b/tests/AStar.test.ts new file mode 100644 index 0000000..1685bda --- /dev/null +++ b/tests/AStar.test.ts @@ -0,0 +1,223 @@ +import { describe, it, assert } from 'vitest' +import { bestFirstSearch } from '../src/BestFirstSearch' +import { + down, + expand, + type Grid, + left, + type Position, + positionEquality, + right, + stall, + up, +} from './Grid' +import { aStar, Heuristics } from '../src/AStar' + +const euclideanDistance: (goal: Position) => Heuristics = + (goal: Position) => (position: Position) => + Math.sqrt(Math.pow(goal.x - position.x, 2) + Math.pow(goal.y - goal.y, 2)) + +describe('AStar', () => { + it('solves when initial and goal are equals', async () => { + const grid: Grid = { rows: 1, columns: 1 } + const initial: Position = { + x: 0, + y: 0, + } + const goal: Position = { + x: 0, + y: 0, + } + + const actualSolution = await aStar(euclideanDistance(goal))( + initial, + goal, + positionEquality, + expand(grid)([]), + ) + + const expectedSolution: Position[] = [{ x: 0, y: 0 }] + assert.deepStrictEqual(actualSolution, expectedSolution) + }) + + it('solves after expanding once', async () => { + const corridor: Grid = { rows: 1, columns: 2 } + const initial: Position = { + x: 0, + y: 0, + } + const goal: Position = { + x: 1, + y: 0, + } + + const actualSolution = await aStar(euclideanDistance(goal))( + initial, + goal, + positionEquality, + expand(corridor)([right]), + ) + + const expectedSolution: Position[] = [ + { x: 0, y: 0 }, + { x: 1, y: 0 }, + ] + assert.deepStrictEqual(actualSolution, expectedSolution) + }) + + it('solves after expanding twice', async () => { + const corridor: Grid = { rows: 1, columns: 3 } + const initial: Position = { + x: 0, + y: 0, + } + const goal: Position = { + x: 2, + y: 0, + } + + const actualSolution = await aStar(euclideanDistance(goal))( + initial, + goal, + positionEquality, + expand(corridor)([right]), + ) + + const expectedSolution: Position[] = [ + { x: 0, y: 0 }, + { x: 1, y: 0 }, + { x: 2, y: 0 }, + ] + assert.deepStrictEqual(actualSolution, expectedSolution) + }) + + it('solves after expanding n times', async () => { + const grid: Grid = { rows: 3, columns: 2 } + const initial: Position = { + x: 0, + y: 1, + } + const goal: Position = { + x: 1, + y: 2, + } + + const actualSolution = await bestFirstSearch( + initial, + goal, + positionEquality, + expand(grid)([up, down, right, left]), + ) + + const expectedSolution: Position[] = [ + { x: 0, y: 1 }, + { x: 0, y: 2 }, + { x: 1, y: 2 }, + ] + assert.deepStrictEqual(actualSolution, expectedSolution) + }) + + it('solves best solution', async () => { + const grid: Grid = { rows: 3, columns: 3 } + const initial: Position = { + x: 0, + y: 0, + } + const goal: Position = { + x: 2, + y: 2, + } + + const actualSolution = await aStar(euclideanDistance(goal))( + initial, + goal, + positionEquality, + expand(grid)([up, down, right, left]), + ) + + const expectedSolution: Position[] = [ + { x: 0, y: 0 }, + { x: 1, y: 0 }, + { x: 2, y: 0 }, + { x: 2, y: 1 }, + { x: 2, y: 2 }, + ] + assert.deepStrictEqual(actualSolution, expectedSolution) + }) + + it('solves worst solution', async () => { + const grid: Grid = { rows: 3, columns: 3 } + const initial: Position = { + x: 0, + y: 0, + } + const goal: Position = { + x: 2, + y: 2, + } + + const actualSolution = await aStar(euclideanDistance(goal))( + initial, + goal, + positionEquality, + expand(grid)([up, down, right, left]), + ) + + const expectedSolution: Position[] = [ + { x: 0, y: 0 }, + { x: 1, y: 0 }, + { x: 2, y: 0 }, + { x: 2, y: 1 }, + { x: 2, y: 2 }, + ] + assert.deepStrictEqual(actualSolution, expectedSolution) + }) + + it('return empty array for unsolvable searches', async () => { + const grid: Grid = { rows: 2, columns: 2 } + const initial: Position = { + x: 0, + y: 1, + } + const goal: Position = { + x: 100, + y: 100, + } + + const actualSolution = await aStar(euclideanDistance(goal))( + initial, + goal, + positionEquality, + expand(grid)([up]), + ) + + const expectedSolution: Position[] = [] + assert.deepStrictEqual(actualSolution, expectedSolution) + }) + + it('filters already visited states on expanding', async () => { + const corridor: Grid = { rows: 1, columns: 3 } + const initial: Position = { + x: 0, + y: 0, + } + const goal: Position = { + x: 2, + y: 0, + } + + const actualSolution = await aStar(euclideanDistance(goal))( + initial, + goal, + positionEquality, + expand(corridor)([stall, right]), + ) + + const expectedSolution: Position[] = [ + { x: 0, y: 0 }, + { x: 1, y: 0 }, + { x: 2, y: 0 }, + ] + assert.deepStrictEqual(actualSolution, expectedSolution) + }) +}) From 8ba1c5e1d47f0acbc883f3168a4b7d196a4d3cbc Mon Sep 17 00:00:00 2001 From: benjides Date: Wed, 4 Feb 2026 13:45:35 +0100 Subject: [PATCH 117/118] Fix AStar tests --- tests/AStar.test.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/AStar.test.ts b/tests/AStar.test.ts index 1685bda..36023fc 100644 --- a/tests/AStar.test.ts +++ b/tests/AStar.test.ts @@ -1,5 +1,4 @@ import { describe, it, assert } from 'vitest' -import { bestFirstSearch } from '../src/BestFirstSearch' import { down, expand, @@ -102,7 +101,7 @@ describe('AStar', () => { y: 2, } - const actualSolution = await bestFirstSearch( + const actualSolution = await aStar(euclideanDistance(goal))( initial, goal, positionEquality, @@ -111,7 +110,7 @@ describe('AStar', () => { const expectedSolution: Position[] = [ { x: 0, y: 1 }, - { x: 0, y: 2 }, + { x: 1, y: 1 }, { x: 1, y: 2 }, ] assert.deepStrictEqual(actualSolution, expectedSolution) From 3add4c2b67330cbe1428c22dc27ccc19dd3cba98 Mon Sep 17 00:00:00 2001 From: benjides Date: Fri, 6 Feb 2026 09:28:43 +0100 Subject: [PATCH 118/118] Introduce solver and Search type --- src/AStar.ts | 7 ++-- src/BestFirstSearch.ts | 6 ++- src/DepthFirstSearch.ts | 6 ++- src/Search.ts | 40 +++++++++++++------ tests/AStar.test.ts | 72 +++++++++++++++------------------- tests/BestFirstSearch.test.ts | 72 +++++++++++++++------------------- tests/DepthFirstSearch.test.ts | 60 ++++++++++++---------------- 7 files changed, 128 insertions(+), 135 deletions(-) diff --git a/src/AStar.ts b/src/AStar.ts index dbea52d..9a2ecbb 100644 --- a/src/AStar.ts +++ b/src/AStar.ts @@ -1,5 +1,5 @@ import { Node } from './Node' -import { Evaluate, Search, search } from './Search' +import { Evaluate, Search, solver } from './Search' const aStarEvaluate: (heuristics: Heuristics) => Evaluate = (heuristics: Heuristics) => @@ -8,6 +8,7 @@ const aStarEvaluate: (heuristics: Heuristics) => Evaluate = export type Heuristics = (currentState: S) => number -export const aStar: (heuristics: Heuristics) => Search = ( +export const aStar: ( heuristics: Heuristics, -) => search(aStarEvaluate(heuristics)) +) => (search: Search) => Promise = (heuristics: Heuristics) => + solver(aStarEvaluate(heuristics)) diff --git a/src/BestFirstSearch.ts b/src/BestFirstSearch.ts index 085bb6e..0407a81 100644 --- a/src/BestFirstSearch.ts +++ b/src/BestFirstSearch.ts @@ -1,9 +1,11 @@ import { Node } from './Node' -import { Evaluate, Search, search } from './Search' +import { Evaluate, Search, solver } from './Search' const bestFirstSearchEvaluate: () => Evaluate = () => (node: Node) => node.depth -export const bestFirstSearch: Search = search(bestFirstSearchEvaluate()) +export const bestFirstSearch: (search: Search) => Promise = solver( + bestFirstSearchEvaluate(), +) diff --git a/src/DepthFirstSearch.ts b/src/DepthFirstSearch.ts index d126138..48ffbbc 100644 --- a/src/DepthFirstSearch.ts +++ b/src/DepthFirstSearch.ts @@ -1,9 +1,11 @@ import { Node } from './Node' -import { Evaluate, Search, search } from './Search' +import { Evaluate, Search, solver } from './Search' const depthFirstSearchEvaluate: () => Evaluate = () => (node: Node) => -node.depth -export const depthFirstSearch: Search = search(depthFirstSearchEvaluate()) +export const depthFirstSearch: (search: Search) => Promise = solver( + depthFirstSearchEvaluate(), +) diff --git a/src/Search.ts b/src/Search.ts index 9a951b5..f443151 100644 --- a/src/Search.ts +++ b/src/Search.ts @@ -11,12 +11,24 @@ export type Eq = (a: S, b: S) => boolean export type Expand = (s: S) => Promise[] -export type Search = ( +export type Search = { + initial: S + goal: S + eq: Eq + expand: Expand +} + +export const search: ( initial: S, goal: S, eq: Eq, expand: Expand, -) => Promise +) => Search = (initial: S, goal: S, eq: Eq, expand: Expand) => ({ + initial: initial, + goal: goal, + eq: eq, + expand: expand, +}) type OpenSet = PriorityQueue> @@ -29,23 +41,25 @@ const closedSet: () => ClosedSet = () => hashSet() export type Evaluate = (node: Node) => number -const isDone: (eq: Eq, node: S) => (state: S) => boolean = - (eq: Eq, node: T) => - (state: T) => - eq(node, state) - -export const search: (evaluate: Evaluate) => Search = +export const solver: ( + evaluate: Evaluate, +) => (search: Search) => Promise = (evaluate: Evaluate) => - async (initial: S, goal: S, eq: Eq, expand: Expand): Promise => + (search: Search) => expandRecursively( - openSet(initial), + openSet(search.initial), closedSet(), nodeInsert(evaluate), - isDone(eq, goal), - eq, - expand, + isDone(search.eq, search.goal), + search.eq, + search.expand, ) +const isDone: (eq: Eq, node: S) => (state: S) => boolean = + (eq: Eq, node: T) => + (state: T) => + eq(node, state) + const nodeInsert: ( evaluate: Evaluate, ) => (node: Node) => (openSet: OpenSet) => OpenSet = diff --git a/tests/AStar.test.ts b/tests/AStar.test.ts index 36023fc..0aef0db 100644 --- a/tests/AStar.test.ts +++ b/tests/AStar.test.ts @@ -11,6 +11,7 @@ import { up, } from './Grid' import { aStar, Heuristics } from '../src/AStar' +import { search } from '../src/Search' const euclideanDistance: (goal: Position) => Heuristics = (goal: Position) => (position: Position) => @@ -28,11 +29,8 @@ describe('AStar', () => { y: 0, } - const actualSolution = await aStar(euclideanDistance(goal))( - initial, - goal, - positionEquality, - expand(grid)([]), + const actualSolution: Position[] = await aStar(euclideanDistance(goal))( + search(initial, goal, positionEquality, expand(grid)([])), ) const expectedSolution: Position[] = [{ x: 0, y: 0 }] @@ -50,11 +48,8 @@ describe('AStar', () => { y: 0, } - const actualSolution = await aStar(euclideanDistance(goal))( - initial, - goal, - positionEquality, - expand(corridor)([right]), + const actualSolution: Position[] = await aStar(euclideanDistance(goal))( + search(initial, goal, positionEquality, expand(corridor)([right])), ) const expectedSolution: Position[] = [ @@ -75,11 +70,8 @@ describe('AStar', () => { y: 0, } - const actualSolution = await aStar(euclideanDistance(goal))( - initial, - goal, - positionEquality, - expand(corridor)([right]), + const actualSolution: Position[] = await aStar(euclideanDistance(goal))( + search(initial, goal, positionEquality, expand(corridor)([right])), ) const expectedSolution: Position[] = [ @@ -101,11 +93,13 @@ describe('AStar', () => { y: 2, } - const actualSolution = await aStar(euclideanDistance(goal))( - initial, - goal, - positionEquality, - expand(grid)([up, down, right, left]), + const actualSolution: Position[] = await aStar(euclideanDistance(goal))( + search( + initial, + goal, + positionEquality, + expand(grid)([up, down, right, left]), + ), ) const expectedSolution: Position[] = [ @@ -127,11 +121,13 @@ describe('AStar', () => { y: 2, } - const actualSolution = await aStar(euclideanDistance(goal))( - initial, - goal, - positionEquality, - expand(grid)([up, down, right, left]), + const actualSolution: Position[] = await aStar(euclideanDistance(goal))( + search( + initial, + goal, + positionEquality, + expand(grid)([up, down, right, left]), + ), ) const expectedSolution: Position[] = [ @@ -155,11 +151,13 @@ describe('AStar', () => { y: 2, } - const actualSolution = await aStar(euclideanDistance(goal))( - initial, - goal, - positionEquality, - expand(grid)([up, down, right, left]), + const actualSolution: Position[] = await aStar(euclideanDistance(goal))( + search( + initial, + goal, + positionEquality, + expand(grid)([up, down, right, left]), + ), ) const expectedSolution: Position[] = [ @@ -183,11 +181,8 @@ describe('AStar', () => { y: 100, } - const actualSolution = await aStar(euclideanDistance(goal))( - initial, - goal, - positionEquality, - expand(grid)([up]), + const actualSolution: Position[] = await aStar(euclideanDistance(goal))( + search(initial, goal, positionEquality, expand(grid)([up])), ) const expectedSolution: Position[] = [] @@ -205,11 +200,8 @@ describe('AStar', () => { y: 0, } - const actualSolution = await aStar(euclideanDistance(goal))( - initial, - goal, - positionEquality, - expand(corridor)([stall, right]), + const actualSolution: Position[] = await aStar(euclideanDistance(goal))( + search(initial, goal, positionEquality, expand(corridor)([stall, right])), ) const expectedSolution: Position[] = [ diff --git a/tests/BestFirstSearch.test.ts b/tests/BestFirstSearch.test.ts index bc33ba0..681d32a 100644 --- a/tests/BestFirstSearch.test.ts +++ b/tests/BestFirstSearch.test.ts @@ -11,6 +11,7 @@ import { stall, up, } from './Grid' +import { search } from '../src/Search' describe('BestFirstSearch', () => { it('solves when initial and goal are equals', async () => { @@ -24,11 +25,8 @@ describe('BestFirstSearch', () => { y: 0, } - const actualSolution = await bestFirstSearch( - initial, - goal, - positionEquality, - expand(grid)([]), + const actualSolution: Position[] = await bestFirstSearch( + search(initial, goal, positionEquality, expand(grid)([])), ) const expectedSolution: Position[] = [{ x: 0, y: 0 }] @@ -46,11 +44,8 @@ describe('BestFirstSearch', () => { y: 0, } - const actualSolution = await bestFirstSearch( - initial, - goal, - positionEquality, - expand(corridor)([right]), + const actualSolution: Position[] = await bestFirstSearch( + search(initial, goal, positionEquality, expand(corridor)([right])), ) const expectedSolution: Position[] = [ @@ -71,11 +66,8 @@ describe('BestFirstSearch', () => { y: 0, } - const actualSolution = await bestFirstSearch( - initial, - goal, - positionEquality, - expand(corridor)([right]), + const actualSolution: Position[] = await bestFirstSearch( + search(initial, goal, positionEquality, expand(corridor)([right])), ) const expectedSolution: Position[] = [ @@ -97,11 +89,13 @@ describe('BestFirstSearch', () => { y: 2, } - const actualSolution = await bestFirstSearch( - initial, - goal, - positionEquality, - expand(grid)([up, down, right, left]), + const actualSolution: Position[] = await bestFirstSearch( + search( + initial, + goal, + positionEquality, + expand(grid)([up, down, right, left]), + ), ) const expectedSolution: Position[] = [ @@ -123,11 +117,13 @@ describe('BestFirstSearch', () => { y: 2, } - const actualSolution = await bestFirstSearch( - initial, - goal, - positionEquality, - expand(grid)([up, down, right, left]), + const actualSolution: Position[] = await bestFirstSearch( + search( + initial, + goal, + positionEquality, + expand(grid)([up, down, right, left]), + ), ) const expectedSolution: Position[] = [ @@ -151,11 +147,13 @@ describe('BestFirstSearch', () => { y: 2, } - const actualSolution = await bestFirstSearch( - initial, - goal, - positionEquality, - expand(grid)([up, down, right, left]), + const actualSolution: Position[] = await bestFirstSearch( + search( + initial, + goal, + positionEquality, + expand(grid)([up, down, right, left]), + ), ) const expectedSolution: Position[] = [ @@ -179,11 +177,8 @@ describe('BestFirstSearch', () => { y: 100, } - const actualSolution = await bestFirstSearch( - initial, - goal, - positionEquality, - expand(grid)([up]), + const actualSolution: Position[] = await bestFirstSearch( + search(initial, goal, positionEquality, expand(grid)([up])), ) const expectedSolution: Position[] = [] @@ -201,11 +196,8 @@ describe('BestFirstSearch', () => { y: 0, } - const actualSolution = await bestFirstSearch( - initial, - goal, - positionEquality, - expand(corridor)([stall, right]), + const actualSolution: Position[] = await bestFirstSearch( + search(initial, goal, positionEquality, expand(corridor)([stall, right])), ) const expectedSolution: Position[] = [ diff --git a/tests/DepthFirstSearch.test.ts b/tests/DepthFirstSearch.test.ts index 0ff45b4..49b7830 100644 --- a/tests/DepthFirstSearch.test.ts +++ b/tests/DepthFirstSearch.test.ts @@ -11,6 +11,7 @@ import { stall, up, } from './Grid' +import { search } from '../src/Search' describe('DepthFirstSearch', () => { it('solves when initial and goal are equals', async () => { @@ -24,11 +25,8 @@ describe('DepthFirstSearch', () => { y: 0, } - const actualSolution = await depthFirstSearch( - initial, - goal, - positionEquality, - expand(grid)([]), + const actualSolution: Position[] = await depthFirstSearch( + search(initial, goal, positionEquality, expand(grid)([])), ) const expectedSolution: Position[] = [{ x: 0, y: 0 }] @@ -46,11 +44,8 @@ describe('DepthFirstSearch', () => { y: 0, } - const actualSolution = await depthFirstSearch( - initial, - goal, - positionEquality, - expand(corridor)([right]), + const actualSolution: Position[] = await depthFirstSearch( + search(initial, goal, positionEquality, expand(corridor)([right])), ) const expectedSolution: Position[] = [ @@ -71,11 +66,8 @@ describe('DepthFirstSearch', () => { y: 0, } - const actualSolution = await depthFirstSearch( - initial, - goal, - positionEquality, - expand(corridor)([right]), + const actualSolution: Position[] = await depthFirstSearch( + search(initial, goal, positionEquality, expand(corridor)([right])), ) const expectedSolution: Position[] = [ @@ -97,11 +89,13 @@ describe('DepthFirstSearch', () => { y: 2, } - const actualSolution = await depthFirstSearch( - initial, - goal, - positionEquality, - expand(grid)([up, down, right, left]), + const actualSolution: Position[] = await depthFirstSearch( + search( + initial, + goal, + positionEquality, + expand(grid)([up, down, right, left]), + ), ) const expectedSolution: Position[] = [ @@ -123,11 +117,13 @@ describe('DepthFirstSearch', () => { y: 2, } - const actualSolution = await depthFirstSearch( - initial, - goal, - positionEquality, - expand(grid)([up, down, right, left]), + const actualSolution: Position[] = await depthFirstSearch( + search( + initial, + goal, + positionEquality, + expand(grid)([up, down, right, left]), + ), ) const expectedSolution: Position[] = [ @@ -155,11 +151,8 @@ describe('DepthFirstSearch', () => { y: 100, } - const actualSolution = await depthFirstSearch( - initial, - goal, - positionEquality, - expand(grid)([up]), + const actualSolution: Position[] = await depthFirstSearch( + search(initial, goal, positionEquality, expand(grid)([up])), ) const expectedSolution: Position[] = [] @@ -177,11 +170,8 @@ describe('DepthFirstSearch', () => { y: 0, } - const actualSolution = await depthFirstSearch( - initial, - goal, - positionEquality, - expand(corridor)([stall, right]), + const actualSolution: Position[] = await depthFirstSearch( + search(initial, goal, positionEquality, expand(corridor)([stall, right])), ) const expectedSolution: Position[] = [