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.
|
2022-10-04 06:06:03 +00:00
|
|
|
let commonSegment = getLeastCommonPathSegment([path, pathKey], {
|
2022-08-16 14:45:30 +00:00
|
|
|
considerSingleLevel: false,
|
|
|
|
considerMultiLevel: false,
|
|
|
|
});
|
|
|
|
|
|
|
|
if (commonSegment == false) {
|
2022-10-04 06:06:03 +00:00
|
|
|
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;
|
2022-08-23 07:50:45 +00:00
|
|
|
} 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;
|
2022-08-23 07:50:45 +00:00
|
|
|
} 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,
|
|
|
|
};
|
|
|
|
|
2022-10-04 06:06:03 +00:00
|
|
|
// 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) {
|
2022-01-25 19:43:17 +00:00
|
|
|
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
|
|
|
}
|
2022-01-25 19:43:17 +00:00
|
|
|
} else {
|
2022-07-07 07:14:22 +00:00
|
|
|
if (!m.has(valueToUse)) {
|
|
|
|
m.set(valueToUse, new Set());
|
2022-01-25 19:43:17 +00:00
|
|
|
}
|
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;
|
|
|
|
}
|