From e9940edec2d35fd768481e994612819b4ff33f17 Mon Sep 17 00:00:00 2001 From: Karsten Schmidt Date: Tue, 24 Dec 2024 14:46:19 +0100 Subject: [PATCH] refactor(transducers-stats): replace thi.ng/dcons dependency - switch to use more lightweight & GC-friendly ringbuffer impl from thi.ng/buffers - update the following transducers: - `momentum()` - `roc()` - `sma()` - add tests --- packages/transducers-stats/README.md | 4 +- packages/transducers-stats/package.json | 2 +- packages/transducers-stats/src/momentum.ts | 13 +++---- packages/transducers-stats/src/roc.ts | 15 ++++---- packages/transducers-stats/src/sma.ts | 15 ++++---- packages/transducers-stats/test/main.test.ts | 39 +++++++++++++++++++- yarn.lock | 2 +- 7 files changed, 64 insertions(+), 26 deletions(-) diff --git a/packages/transducers-stats/README.md b/packages/transducers-stats/README.md index e600bac9d2..cf28c077a8 100644 --- a/packages/transducers-stats/README.md +++ b/packages/transducers-stats/README.md @@ -91,13 +91,13 @@ For Node.js REPL: const ts = await import("@thi.ng/transducers-stats"); ``` -Package sizes (brotli'd, pre-treeshake): ESM: 1.75 KB +Package sizes (brotli'd, pre-treeshake): ESM: 1.74 KB ## Dependencies - [@thi.ng/api](https://github.com/thi-ng/umbrella/tree/develop/packages/api) +- [@thi.ng/buffers](https://github.com/thi-ng/umbrella/tree/develop/packages/buffers) - [@thi.ng/checks](https://github.com/thi-ng/umbrella/tree/develop/packages/checks) -- [@thi.ng/dcons](https://github.com/thi-ng/umbrella/tree/develop/packages/dcons) - [@thi.ng/errors](https://github.com/thi-ng/umbrella/tree/develop/packages/errors) - [@thi.ng/transducers](https://github.com/thi-ng/umbrella/tree/develop/packages/transducers) diff --git a/packages/transducers-stats/package.json b/packages/transducers-stats/package.json index 7722083768..0677e6e68f 100644 --- a/packages/transducers-stats/package.json +++ b/packages/transducers-stats/package.json @@ -37,8 +37,8 @@ }, "dependencies": { "@thi.ng/api": "^8.11.14", + "@thi.ng/buffers": "^0.1.17", "@thi.ng/checks": "^3.6.16", - "@thi.ng/dcons": "^3.2.134", "@thi.ng/errors": "^2.5.20", "@thi.ng/transducers": "^9.2.10" }, diff --git a/packages/transducers-stats/src/momentum.ts b/packages/transducers-stats/src/momentum.ts index bf203ad65c..a54d25ca45 100644 --- a/packages/transducers-stats/src/momentum.ts +++ b/packages/transducers-stats/src/momentum.ts @@ -1,4 +1,4 @@ -import { DCons } from "@thi.ng/dcons/dcons"; +import { sliding } from "@thi.ng/buffers/sliding"; import { illegalArgs } from "@thi.ng/errors/illegal-arguments"; import type { Reducer, Transducer } from "@thi.ng/transducers"; import { compR } from "@thi.ng/transducers/compr"; @@ -27,14 +27,13 @@ export function momentum(period: number, src?: Iterable): any { period < 1 && illegalArgs("period must be >= 1"); return (rfn: Reducer) => { const reduce = rfn[2]; - const window = new DCons(); + const window = sliding(period); return compR(rfn, (acc, x: number) => { - window.push(x); - if (window.length <= period) { - return acc; + if (window.length === period) { + acc = reduce(acc, x - window.read()); } - const prev = window.drop()!; - return reduce(acc, x - prev); + window.write(x); + return acc; }); }; } diff --git a/packages/transducers-stats/src/roc.ts b/packages/transducers-stats/src/roc.ts index 82c8334134..99862d6839 100644 --- a/packages/transducers-stats/src/roc.ts +++ b/packages/transducers-stats/src/roc.ts @@ -1,4 +1,4 @@ -import { DCons } from "@thi.ng/dcons/dcons"; +import { sliding } from "@thi.ng/buffers/sliding"; import { illegalArgs } from "@thi.ng/errors/illegal-arguments"; import type { Reducer, Transducer } from "@thi.ng/transducers"; import { compR } from "@thi.ng/transducers/compr"; @@ -24,17 +24,18 @@ export function roc(period: number, src?: Iterable): any { if (src) { return iterator1(roc(period), src); } + period |= 0; period < 1 && illegalArgs("period must be >= 1"); return (rfn: Reducer) => { const reduce = rfn[2]; - const window = new DCons(); + const window = sliding(period); return compR(rfn, (acc, x: number) => { - window.push(x); - if (window.length <= period) { - return acc; + if (window.length === period) { + const prev = window.read(); + acc = reduce(acc, (x - prev) / prev); } - const prev = window.drop()!; - return reduce(acc, (x - prev) / prev); + window.write(x); + return acc; }); }; } diff --git a/packages/transducers-stats/src/sma.ts b/packages/transducers-stats/src/sma.ts index 5e37152a77..2f08dba678 100644 --- a/packages/transducers-stats/src/sma.ts +++ b/packages/transducers-stats/src/sma.ts @@ -1,4 +1,4 @@ -import { DCons } from "@thi.ng/dcons/dcons"; +import { sliding } from "@thi.ng/buffers/sliding"; import { illegalArgs } from "@thi.ng/errors/illegal-arguments"; import type { Reducer, Transducer } from "@thi.ng/transducers"; import { compR } from "@thi.ng/transducers/compr"; @@ -24,17 +24,18 @@ export function sma(period: number, src?: Iterable): any { return iterator1(sma(period), src); } period |= 0; - period < 2 && illegalArgs("period must be >= 2"); + period < 1 && illegalArgs("period must be >= 1"); return (rfn: Reducer) => { const reduce = rfn[2]; - const window = new DCons(); + const window = sliding(period); let sum = 0; return compR(rfn, (acc, x: number) => { - window.push(x); - const n = window.length; + if (window.length === period) { + sum -= window.read(); + } + window.write(x); sum += x; - n > period && (sum -= window.drop()!); - return n >= period ? reduce(acc, sum / period) : acc; + return window.length === period ? reduce(acc, sum / period) : acc; }); }; } diff --git a/packages/transducers-stats/test/main.test.ts b/packages/transducers-stats/test/main.test.ts index 04a2d4ed45..fa0bafa05f 100644 --- a/packages/transducers-stats/test/main.test.ts +++ b/packages/transducers-stats/test/main.test.ts @@ -1,5 +1,12 @@ import { expect, test } from "bun:test"; -import { donchian, movingMaximum, movingMinimum } from "../src/index.js"; +import { + donchian, + momentum, + movingMaximum, + movingMinimum, + roc, + sma, +} from "../src/index.js"; test("movingMaxium", () => { expect([...movingMaximum(3, [1, 3, 1, 1, 4, 1, 1, 2, 5, 6])]).toEqual([ @@ -25,3 +32,33 @@ test("donchian", () => { [2, 6], ]); }); + +test("momentum", () => { + expect(() => momentum(0)).toThrow(); + expect([...momentum(4, [1, 2, 3, 4, 5, 6, 7, 8, 9])]).toEqual([ + 4, 4, 4, 4, 4, + ]); +}); + +test("roc", () => { + expect(() => roc(0)).toThrow(); + expect([...roc(4, [1, 2, 3, 4, 5, 6, 7, 8, 9])]).toEqual([ + 4 / 1, + 4 / 2, + 4 / 3, + 4 / 4, + 4 / 5, + ]); +}); + +test("sma", () => { + expect(() => sma(0)).toThrow(); + expect([...sma(4, [1, 2, 3, 4, 5, 6, 7, 8, 9])]).toEqual([ + (1 + 2 + 3 + 4) / 4, + (2 + 3 + 4 + 5) / 4, + (3 + 4 + 5 + 6) / 4, + (4 + 5 + 6 + 7) / 4, + (5 + 6 + 7 + 8) / 4, + (6 + 7 + 8 + 9) / 4, + ]); +}); diff --git a/yarn.lock b/yarn.lock index 2215ab0743..6c6db881f7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6780,8 +6780,8 @@ __metadata: dependencies: "@microsoft/api-extractor": "npm:^7.48.0" "@thi.ng/api": "npm:^8.11.14" + "@thi.ng/buffers": "npm:^0.1.17" "@thi.ng/checks": "npm:^3.6.16" - "@thi.ng/dcons": "npm:^3.2.134" "@thi.ng/errors": "npm:^2.5.20" "@thi.ng/transducers": "npm:^9.2.10" esbuild: "npm:^0.24.0"