From aa569c5d6bf0fb00a42dbd8eaf66f65dd2577c20 Mon Sep 17 00:00:00 2001 From: Martin Karkowski Date: Thu, 25 Nov 2021 08:43:02 +0100 Subject: [PATCH] Adding Merging Data --- lib/helpers/mapMethods.spec.ts | 67 +++++++++++++ lib/helpers/mapMethods.ts | 103 ++++++++++++++++++++ lib/helpers/mergedData.spec.ts | 86 +++++++++++++++++ lib/helpers/mergedData.ts | 121 ++++++++++++++++++++++++ lib/types/nope/nopeHelpers.interface.ts | 25 +++++ 5 files changed, 402 insertions(+) create mode 100644 lib/helpers/mapMethods.spec.ts create mode 100644 lib/helpers/mapMethods.ts create mode 100644 lib/helpers/mergedData.spec.ts create mode 100644 lib/helpers/mergedData.ts create mode 100644 lib/types/nope/nopeHelpers.interface.ts diff --git a/lib/helpers/mapMethods.spec.ts b/lib/helpers/mapMethods.spec.ts new file mode 100644 index 0000000..e325200 --- /dev/null +++ b/lib/helpers/mapMethods.spec.ts @@ -0,0 +1,67 @@ +/** + * @author Martin Karkowski + * @email m.karkowski@zema.de + * @create date 2021-11-13 08:17:19 + * @modify date 2021-11-13 09:44:51 + * @desc [description] + */ + +import { assert } from "chai"; +import "chai/register-should"; +import { describe, it } from "mocha"; +import { extractUniqueValues } from "./mapMethods"; + +describe("mapMethods", function () { + // Describe the required Test: + + describe("extractUniqueValues", function () { + it("simple-map", function () { + const m = new Map(); + m.set("a", "b"); + m.set("b", "b"); + + const result = extractUniqueValues(m); + assert.isTrue( + result.size === 1, + "Elements have the same identity, but should be differend" + ); + assert.isTrue([...result][0] === "b", "Element is element"); + }); + it("nested-map", function () { + const m = new Map(); + m.set("a", { a: "b" }); + m.set("b", { a: "b" }); + const result = extractUniqueValues(m, "a"); + + assert.isTrue( + result.size === 1, + "Elements have the same identity, but should be differend" + ); + assert.isTrue([...result][0] === "b", "Element is element"); + }); + it("nested-array", function () { + const m = new Map(); + m.set("a", { a: ["b"] }); + m.set("b", { a: ["b"] }); + const result = extractUniqueValues(m, "a"); + + assert.isTrue( + result.size === 1, + "Elements have the same identity, but should be differend" + ); + assert.isTrue([...result][0] === "b", "Element is element"); + }); + it("nested-array multiple elements", function () { + const m = new Map(); + m.set("a", { a: ["b"] }); + m.set("b", { a: ["c", "d"] }); + const result = extractUniqueValues(m, "a"); + + assert.isTrue( + result.size === 3, + "Elements have the same identity, but should be differend" + ); + assert.deepEqual(["b", "c", "d"], [...result], "Items are missing"); + }); + }); +}); diff --git a/lib/helpers/mapMethods.ts b/lib/helpers/mapMethods.ts new file mode 100644 index 0000000..128111e --- /dev/null +++ b/lib/helpers/mapMethods.ts @@ -0,0 +1,103 @@ +/** + * @author Martin Karkowski + * @email m.karkowski@zema.de + * @create date 2021-11-23 11:21:57 + * @modify date 2021-11-23 11:21:57 + * @desc [description] + */ + +import { rgetattr } from "./objectMethods"; + +const __sentinal = { + unique: "value", +}; + +/** + * Extracts the unique values of an map. + * + * @author M.Karkowski + * @export + * @template K + * @template V + * @param {Map} map + * @return {*} {Set} + */ +export function extractUniqueValues(map: Map, path = ""): Set { + return new Set(extractValues(map, path)); +} + +export function extractValues(map: Map, path = ""): Array { + const s = new Array(); + + for (const v of map.values()) { + if (path) { + const data: D | typeof __sentinal = rgetattr(v, path, __sentinal); + if (data !== __sentinal) { + if (Array.isArray(data)) { + data.map((item) => s.push(item)); + } else { + s.push(data as D); + } + } + } else { + s.push(v as any as D); + } + } + + return s; +} + +/** + * + * + * @author M.Karkowski + * @export + * @template D + * @template K + * @param {Map} map + * @param {string} [path=""] + * @return {*} {Map} + */ +export function transformValues( + map: Map, + path = "" +): Map { + const m = new Map(); + + for (const [k, v] of map.entries()) { + if (path) { + const data: D | typeof __sentinal = rgetattr(v, path, __sentinal); + if (data !== __sentinal) { + m.set(k, data as D); + } + } else { + m.set(k, v as any as D); + } + } + + return m; +} + +/** + * Reverses the given map. + * + * @author M.Karkowski + * @export + * @template K + * @template V + * @param {Map} map + * @return {*} {Map>} + */ +export function reverse(map: Map): Map> { + const m = new Map>(); + + for (const [k, v] of map.entries()) { + if (!m.has(v)) { + m.set(v, new Set()); + } + + m.get(v).add(k); + } + + return m; +} diff --git a/lib/helpers/mergedData.spec.ts b/lib/helpers/mergedData.spec.ts new file mode 100644 index 0000000..f9ac26c --- /dev/null +++ b/lib/helpers/mergedData.spec.ts @@ -0,0 +1,86 @@ +/** + * @author Martin Karkowski + * @email m.karkowski@zema.de + * @create date 2021-11-13 08:17:19 + * @modify date 2021-11-13 09:44:51 + * @desc [description] + */ + +import { assert } from "chai"; +import "chai/register-should"; +import { describe, it } from "mocha"; +import { extractUniqueValues } from "./mapMethods"; +import { MergeData } from "./mergedData"; + +describe("mergedData", function () { + // Describe the required Test: + it("data subscription", function (done) { + const m = new Map(); + const d = new MergeData(m, (m) => extractUniqueValues(m)); + m.set("a", "b"); + m.set("b", "b"); + + d.update(); + + d.data.subscribe((result) => { + assert.isTrue( + result.length === 1, + "Elements have the same identity, but should be differend" + ); + assert.isTrue([...result][0] === "b", "Element is element"); + done(); + }); + }); + + it("data subscription. Update called twice", function (done) { + const m = new Map(); + const d = new MergeData(m, (m) => extractUniqueValues(m)); + m.set("a", "b"); + m.set("b", "b"); + d.update(m); + d.data.subscribe((result) => { + assert.isTrue( + result.length === 1, + "Elements have the same identity, but should be differend" + ); + assert.isTrue([...result][0] === "b", "Element is element"); + done(); + }); + d.update(m); + }); + + it("onchange subscription: added", function (done) { + const m = new Map(); + const d = new MergeData(m, (m) => extractUniqueValues(m)); + m.set("a", "b"); + m.set("b", "b"); + + d.onChange.subscribe((result) => { + assert.isTrue( + result.added.length === 1, + "Elements have the same identity, but should be differend" + ); + assert.isTrue([...result.added][0] === "b", "Element is element"); + done(); + }); + + d.update(m); + }); + + it("onchange subscription: removed", function (done) { + const m = new Map(); + const d = new MergeData(m, (m) => extractUniqueValues(m)); + m.set("a", "b"); + m.set("b", "b"); + + d.onChange.subscribe((result) => { + assert.isTrue( + result.removed.length === 0, + "Elements have the same identity, but should be differend" + ); + done(); + }); + + d.update(m); + }); +}); diff --git a/lib/helpers/mergedData.ts b/lib/helpers/mergedData.ts new file mode 100644 index 0000000..a438a56 --- /dev/null +++ b/lib/helpers/mergedData.ts @@ -0,0 +1,121 @@ +/** + * @author Martin Karkowski + * @email m.karkowski@zema.de + * @create date 2021-11-23 11:04:49 + * @modify date 2021-11-23 11:04:49 + * @desc [description] + */ + +import { NopeEventEmitter } from "../eventEmitter/nopeEventEmitter"; +import { determineDifference } from "../helpers/setMethods"; +import { NopeObservable } from "../observables/nopeObservable"; +import { INopeEventEmitter, INopeObservable } from "../types/nope"; +import { IMergeData } from "../types/nope/nopeHelpers.interface"; +import { countElements } from "./arrayMethods"; +import { + extractUniqueValues, + extractValues, + reverse, + transformValues, +} from "./mapMethods"; + +export class MergeData implements IMergeData { + /** + * Element which will trig implements IMergeDatager an event containing the changes + * + * @author M.Karkowski + * @type {INopeEventEmitter<{ + * added: T[], + * removed: T[] + * }>} + * @memberof MergeData + */ + readonly onChange: INopeEventEmitter<{ + added: T[]; + removed: T[]; + }>; + + /** + * Contains the current data. + * + * @author M.Karkowski + * @type {INopeObservable} + * @memberof MergeData + */ + readonly data: INopeObservable; + + constructor( + public originalData: D, + protected _extractData: (data: D) => Set + ) { + this.onChange = new NopeEventEmitter(); + this.data = new NopeObservable(); + this.data.setContent([]); + } + + /** + * Update the underlying data. + * + * @author M.Karkowski + * @param {*} [data=this.originalData] + * @memberof MergeData + */ + public update(data: D = null): void { + if (data !== null) { + this.originalData = data; + } + + const afterAdding = this._extractData(this.originalData); + const diff = determineDifference( + new Set(this.data.getContent()), + afterAdding + ); + + if (diff.removed.size > 0 || diff.added.size > 0) { + // Update the currently used subscriptions + this.data.setContent(Array.from(afterAdding)); + // Now emit, that there is a new subscription. + this.onChange.emit({ + added: Array.from(diff.added), + removed: Array.from(diff.removed), + }); + } + } +} + +export class MapBasedMergeData + extends MergeData> + implements IMergeData> +{ + public amountOf: Map; + public simplified: Map; + public reverseSimplified: Map>; + + constructor(originalData: Map, protected _path = "") { + super(originalData, (m) => { + return extractUniqueValues(m, _path); + }); + + this.amountOf = new Map(); + } + + /** + * Update the underlying data. + * + * @author M.Karkowski + * @param {*} [data=this.originalData] + * @memberof MergeData + */ + public update(data: Map = null): void { + if (data !== null) { + this.originalData = data; + } + + // Now lets update the amount of the data: + this.amountOf = countElements(extractValues(data, this._path)); + this.simplified = transformValues(this.originalData, this._path); + this.reverseSimplified = reverse(this.simplified); + + super.update(data); + } +} diff --git a/lib/types/nope/nopeHelpers.interface.ts b/lib/types/nope/nopeHelpers.interface.ts new file mode 100644 index 0000000..ee4bae2 --- /dev/null +++ b/lib/types/nope/nopeHelpers.interface.ts @@ -0,0 +1,25 @@ +import { INopeEventEmitter } from "./nopeEventEmitter.interface"; +import { INopeObservable } from "./nopeObservable.interface"; + +/** + * @author Martin Karkowski + * @email m.karkowski@zema.de + * @create date 2021-11-23 12:31:01 + * @modify date 2021-11-23 12:31:01 + * @desc [description] + */ +export interface IMergeData { + onChange: INopeEventEmitter<{ + added: T[]; + removed: T[]; + }>; + data: INopeObservable; + update(data?: K): void; +} + +export interface IMapBasedMergeData + extends IMergeData> { + amountOf: Map; + simplified: Map; + reverseSimplified: Map>; +}