Adding Merging Data

This commit is contained in:
Martin Karkowski 2021-11-25 08:43:02 +01:00
parent 63491a5bef
commit aa569c5d6b
5 changed files with 402 additions and 0 deletions

View File

@ -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<string, string>();
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<string, { a: string }>();
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<string, { a: string[] }>();
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<string, { a: string[] }>();
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");
});
});
});

103
lib/helpers/mapMethods.ts Normal file
View File

@ -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<K, V>} map
* @return {*} {Set<V>}
*/
export function extractUniqueValues<D>(map: Map<any, any>, path = ""): Set<D> {
return new Set(extractValues(map, path));
}
export function extractValues<D, K>(map: Map<K, any>, path = ""): Array<D> {
const s = new Array<D>();
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<K, any>} map
* @param {string} [path=""]
* @return {*} {Map<K, D>}
*/
export function transformValues<D, K = any>(
map: Map<K, any>,
path = ""
): Map<K, D> {
const m = new Map<K, D>();
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<K, V>} map
* @return {*} {Map<V, Set<K>>}
*/
export function reverse<K, V>(map: Map<K, V>): Map<V, Set<K>> {
const m = new Map<V, Set<K>>();
for (const [k, v] of map.entries()) {
if (!m.has(v)) {
m.set(v, new Set());
}
m.get(v).add(k);
}
return m;
}

View File

@ -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<string, string>();
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<string, string>();
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<string, string>();
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<string, string>();
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);
});
});

121
lib/helpers/mergedData.ts Normal file
View File

@ -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<T, D = any> implements IMergeData<T, D> {
/**
* 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<T[]>}
* @memberof MergeData
*/
readonly data: INopeObservable<T[]>;
constructor(
public originalData: D,
protected _extractData: (data: D) => Set<T>
) {
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<T, K, V>
extends MergeData<T, Map<K, V>>
implements IMergeData<T, Map<K, V>>
{
public amountOf: Map<T, number>;
public simplified: Map<K, T>;
public reverseSimplified: Map<T, Set<K>>;
constructor(originalData: Map<K, V>, 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<K, V> = 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);
}
}

View File

@ -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<T = any, K = any> {
onChange: INopeEventEmitter<{
added: T[];
removed: T[];
}>;
data: INopeObservable<T[]>;
update(data?: K): void;
}
export interface IMapBasedMergeData<T = any, K = any, V = any>
extends IMergeData<T, Map<K, V>> {
amountOf: Map<T, number>;
simplified: Map<K, T>;
reverseSimplified: Map<T, Set<K>>;
}