nope/lib/helpers/mapMethods.ts

335 lines
8.1 KiB
TypeScript
Raw Normal View History

2021-11-25 07:43:02 +00:00
/**
* @author Martin Karkowski
* @email m.karkowski@zema.de
* @desc [description]
*/
2022-08-16 14:45:30 +00:00
import {
convertData,
deepEqual,
rgetattr,
rqueryAttr,
SPLITCHAR,
} from "./objectMethods";
import { getLeastCommonPathSegment } from "./path";
2021-11-25 07:43:02 +00:00
const __sentinal = {
unique: "value",
};
/**
* Extracts the unique values of an map.
*
* @author M.Karkowski
* @export
2022-07-07 07:14:22 +00:00
* @template D Return Type
* @template K The Key of the Map
* @template V The Value of the Map
* @param {Map<K, V>} map The Map
* @param {string} [path=""] The Path of the Data to extract.
* @param {string} [pathKey=null] The Path of the unique key. If set to `null` -> The Item is selected directly.
2022-08-16 14:45:30 +00:00
* @return {Set<D>}
2021-11-25 07:43:02 +00:00
*/
2022-07-07 07:14:22 +00:00
export function extractUniqueValues<D, K = any, V = any>(
map: Map<K, V>,
path = "",
pathKey: string = null
): Set<D> {
if (pathKey === null) {
pathKey = path;
}
if (path !== pathKey) {
2022-08-16 14:45:30 +00:00
// Get the Common segment of the item.
let commonSegment = getLeastCommonPathSegment([path, pathKey], {
2022-08-16 14:45:30 +00:00
considerSingleLevel: false,
considerMultiLevel: false,
});
if (commonSegment == false) {
commonSegment = "";
2022-08-16 14:45:30 +00:00
}
const commonSegmentLength = commonSegment.split(SPLITCHAR).length;
const _relPathContent = path
.split(SPLITCHAR)
.slice(commonSegmentLength)
.join(SPLITCHAR);
const _relPathKey = pathKey
.split(SPLITCHAR)
.slice(commonSegmentLength)
.join(SPLITCHAR);
// Now use that segment to extract the common data.
const items = extractValues(map, commonSegment) as D[];
2022-07-07 07:14:22 +00:00
const itemKeys = new Set();
const ret: D[] = [];
for (const item of items) {
2022-08-16 14:45:30 +00:00
const key = _relPathKey ? rgetattr(item, _relPathKey) : item;
const data = _relPathContent ? rgetattr(item, _relPathContent) : item;
2022-07-07 07:14:22 +00:00
if (!itemKeys.has(key)) {
itemKeys.add(key);
2022-08-16 14:45:30 +00:00
ret.push(data);
2022-07-07 07:14:22 +00:00
}
}
2022-08-16 14:45:30 +00:00
return new Set(ret);
2022-07-07 07:14:22 +00:00
}
2021-11-25 07:43:02 +00:00
return new Set(extractValues(map, path));
}
2022-08-16 14:45:30 +00:00
/**
* Helper to extract values of the map. Therefore the path must be provided.
* @param map
* @param path
* @returns
*/
2021-11-25 07:43:02 +00:00
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) {
2022-08-16 14:45:30 +00:00
const data: { path: string; data: D }[] = rqueryAttr(v, path);
data.map((item) => {
return s.push(item.data);
});
2021-11-25 07:43:02 +00:00
} else {
2022-08-16 14:45:30 +00:00
// Add the item.
s.push(v);
2021-11-25 07:43:02 +00:00
}
}
return s;
}
/**
2022-07-07 07:14:22 +00:00
* Transform the values.
2021-11-25 07:43:02 +00:00
*
*
* @author M.Karkowski
* @export
2022-07-07 07:14:22 +00:00
* @template ExtractedValue
* @template ExtractedKey
* @template OriginalKey
* @param {Map<OriginalKey, any>} map
* @param {string} [pathExtractedValue=""]
* @param {string} [pathExtractedKey=null] Additional Path of a Key.
* @return {*} {Map<ExtractedKey, ExtractedData>}
2021-11-25 07:43:02 +00:00
*/
2022-07-07 07:14:22 +00:00
export function tranformMap<
ExtractedKey = string,
ExtractedValue = any,
OriginalKey = string
>(
map: Map<OriginalKey, any>,
pathExtractedValue: string,
pathExtractedKey: string,
equals: (a: ExtractedValue, b: ExtractedValue) => boolean = deepEqual
) {
const keyMapping = new Map<OriginalKey, Set<ExtractedKey>>();
const reverseKeyMapping = new Map<ExtractedKey, Set<OriginalKey>>();
const conflicts = new Map<ExtractedKey, Set<ExtractedValue>>();
const extractedMap = new Map<ExtractedKey, ExtractedValue>();
const orgKeyToExtractedValue = new Map<OriginalKey, Set<ExtractedValue>>();
const amountOf = new Map<ExtractedKey, number>();
const props: {
query: string;
key: string;
}[] = [];
2021-11-25 07:43:02 +00:00
2022-08-16 14:45:30 +00:00
let onlyValidProps = true;
if (typeof pathExtractedKey === "string") {
2022-07-07 07:14:22 +00:00
props.push({
key: "key",
query: pathExtractedKey,
});
2022-08-16 14:45:30 +00:00
onlyValidProps = onlyValidProps && pathExtractedKey.length > 0;
} else {
onlyValidProps = false;
2022-07-07 07:14:22 +00:00
}
2022-08-16 14:45:30 +00:00
if (typeof pathExtractedValue === "string") {
2022-07-07 07:14:22 +00:00
props.push({
key: "value",
query: pathExtractedValue,
});
2022-08-16 14:45:30 +00:00
onlyValidProps = onlyValidProps && pathExtractedValue.length > 0;
} else {
onlyValidProps = false;
2022-07-07 07:14:22 +00:00
}
// Iterate over the Entries of the Map.
// then we will extract the data stored in the Value.
2021-11-25 07:43:02 +00:00
for (const [k, v] of map.entries()) {
2022-08-16 14:45:30 +00:00
let extracted: {
key: ExtractedKey;
value: ExtractedValue;
}[] = [];
if (onlyValidProps) {
extracted = convertData<{ key: ExtractedKey; value: ExtractedValue }>(
v,
props
);
} else {
const data: {
2022-11-05 21:22:06 +00:00
key: ExtractedKey[];
value: ExtractedValue[];
2022-08-16 14:45:30 +00:00
} = {
key: null,
value: null,
};
// We migt adapt the key and the Value. Therefore we will use
// the next if statements
2022-08-16 14:45:30 +00:00
if (typeof pathExtractedKey === "string") {
if (pathExtractedKey.length > 0) {
2022-11-05 21:22:06 +00:00
data.key = rqueryAttr<ExtractedKey>(v, pathExtractedKey).map(
(item) => item.data
);
2022-08-16 14:45:30 +00:00
} else {
2022-11-05 21:22:06 +00:00
data.key = [v];
2022-08-16 14:45:30 +00:00
}
} else {
2022-11-05 21:22:06 +00:00
data.key = [k] as any;
2022-08-16 14:45:30 +00:00
}
if (typeof pathExtractedValue === "string") {
if (pathExtractedValue.length > 0) {
2022-11-05 21:22:06 +00:00
data.value = rqueryAttr<ExtractedValue>(v, pathExtractedValue).map(
(item) => item.data
);
2022-08-16 14:45:30 +00:00
} else {
2022-11-05 21:22:06 +00:00
data.value = [v];
2022-08-16 14:45:30 +00:00
}
} else {
2022-11-05 21:22:06 +00:00
data.value = [v] as Array<ExtractedValue>;
2022-08-16 14:45:30 +00:00
}
2022-11-05 21:22:06 +00:00
// For every key push the data.
for (const key of data.key) {
data.value.map((item) =>
extracted.push({
key: key,
value: item,
})
);
}
2022-08-16 14:45:30 +00:00
}
2022-07-07 07:14:22 +00:00
// Store the Key.
keyMapping.set(k, new Set());
orgKeyToExtractedValue.set(k, new Set());
for (const item of extracted) {
if (extractedMap.has(item.key)) {
// If the extracted new key has already been defined,
// we have to determine whether the stored item matches
// the allready provided definition.
if (!equals(extractedMap.get(item.key), item.value)) {
// Conflict detected
if (!conflicts.has(item.key)) {
conflicts.set(item.key, new Set());
}
// Store the conflict.
conflicts.get(item.key).add(item.value);
conflicts.get(item.key).add(extractedMap.get(item.key));
} else {
// Store the determined amount.
amountOf.set(item.key, (amountOf.get(item.key) || 0) + 1);
}
} else {
// Store the item.
extractedMap.set(item.key, item.value);
// Store the determined amount.
amountOf.set(item.key, (amountOf.get(item.key) || 0) + 1);
2021-11-25 07:43:02 +00:00
}
2022-07-07 07:14:22 +00:00
// If the reverse haven't been set ==> create it.
if (!reverseKeyMapping.has(item.key)) {
reverseKeyMapping.set(item.key, new Set());
}
// Store the mapping of new-key --> org-key.
reverseKeyMapping.get(item.key).add(k);
// Store the mapping of org-key --> new-key.
keyMapping.get(k).add(item.key);
orgKeyToExtractedValue.get(k).add(item.value);
2021-11-25 07:43:02 +00:00
}
}
2022-07-07 07:14:22 +00:00
return {
extractedMap,
keyMapping,
conflicts,
keyMappingReverse: reverseKeyMapping,
orgKeyToExtractedValue,
amountOf,
};
2021-11-25 07:43:02 +00:00
}
/**
* Reverses the given map.
*
2022-07-07 07:14:22 +00:00
* If the path is provided, the Data is extracted based on the given path.
* If the `pathKey`, a different Key is used.
*
2021-11-25 07:43:02 +00:00
* @author M.Karkowski
* @export
* @template K
* @template V
2022-07-07 07:14:22 +00:00
* @param {Map<any,any>} map
* @param {string} [path=""]
* @param {string} [pathKey=null]
2021-11-25 07:43:02 +00:00
* @return {*} {Map<V, Set<K>>}
*/
2022-07-07 07:14:22 +00:00
export function reverse<K, V>(
map: Map<any, any>,
path: string = "",
pathKey: string = null
): Map<V, Set<K>> {
2021-11-25 07:43:02 +00:00
const m = new Map<V, Set<K>>();
2022-07-07 07:14:22 +00:00
if (pathKey === null) {
pathKey = path;
}
2021-11-25 07:43:02 +00:00
for (const [k, v] of map.entries()) {
2022-07-07 07:14:22 +00:00
let keyToUse = k;
if (pathKey) {
keyToUse = rgetattr(v, pathKey, __sentinal);
}
let valueToUse = v;
if (path) {
valueToUse = rgetattr(v, path, __sentinal);
}
if (Array.isArray(valueToUse)) {
for (const _v of valueToUse) {
if (!m.has(_v)) {
m.set(_v, new Set());
}
2022-07-07 07:14:22 +00:00
m.get(_v).add(keyToUse);
2022-01-25 19:44:42 +00:00
}
} else {
2022-07-07 07:14:22 +00:00
if (!m.has(valueToUse)) {
m.set(valueToUse, new Set());
}
2022-07-07 07:14:22 +00:00
m.get(valueToUse).add(keyToUse);
2022-01-25 19:44:42 +00:00
}
2021-11-25 07:43:02 +00:00
}
return m;
}