Skip to content

Commit

Permalink
feat(associative): add new functions, update arg & return types
Browse files Browse the repository at this point in the history
- add commonKeys*()
- add mergeMapWith() / mergeObjWith()
- add mergeDeepObj()
- rename mapKeys*() => mergeApply*()
- update renameKeys*() arg/return types
- update indexed() arg types
- update join() & joinWith() arg/return types
- update re-exports
  • Loading branch information
postspectacular committed May 9, 2018
1 parent c0950d6 commit 5991be6
Show file tree
Hide file tree
Showing 9 changed files with 177 additions and 77 deletions.
39 changes: 39 additions & 0 deletions packages/associative/src/common-keys.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { IObjectOf } from "@thi.ng/api/api";

/**
* Like `commonKeysObj()`, but for ES6 Maps.
*
* @param a
* @param b
*/
export function commonKeysMap<K>(a: Map<K, any>, b: Map<K, any>) {
const res: K[] = [];
for (let k of a.keys()) {
if (b.has(k)) {
res.push(k);
}
}
return res;
}

/**
* Returns array of keys present in both args, i.e. the set intersection
* of the given objects' key / property sets.
*
* ```
* commonKeys({ a: 1, b: 2 }, { c: 10, b: 20, a: 30 })
* // [ "a", "b" ]
* ```
*
* @param a
* @param b
*/
export function commonKeysObj(a: IObjectOf<any>, b: IObjectOf<any>) {
const res: string[] = [];
for (let k in a) {
if (b.hasOwnProperty(k)) {
res.push(k);
}
}
return res;
}
18 changes: 10 additions & 8 deletions packages/associative/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
export * from "./array-set";
export * from "./equiv-map";
export * from "./ll-set";
export * from "./sorted-map";
export * from "./sorted-set";

export * from "./common-keys";
export * from "./difference";
export * from "./intersection";
export * from "./union";

export * from "./equiv-map";
export * from "./indexed";
export * from "./intersection";
export * from "./invert";
export * from "./join";
export * from "./ll-set";
export * from "./merge-apply";
export * from "./merge-deep";
export * from "./merge-with";
export * from "./merge";
export * from "./rename-keys";
export * from "./select-keys";
export * from "./sorted-map";
export * from "./sorted-set";
export * from "./union";
22 changes: 10 additions & 12 deletions packages/associative/src/indexed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ import { selectKeysObj } from "./select-keys";
import { empty } from "./utils";

/**
* Takes a set of objects and array of indexing keys. Calls
* `selectKeysObj` on each set value and used returned objects as new
* keys to group original values. Returns a map of sets.
* Takes an iterable of plain objects and array of indexing keys. Calls
* `selectKeysObj` on each value and uses returned objects as new keys
* to group original values. Returns a new `EquivMap` of sets.
*
* ```
* indexed(
Expand All @@ -17,19 +17,17 @@ import { empty } from "./utils";
* // { a: 1, b: 2 } => Set { { a: 1, b: 2 } } }
* ```
*
* @param records set of objects to index
* @param records objects to index
* @param ks keys used for indexing
*/
export function indexed<T>(records: Set<T>, ks: PropertyKey[]) {
export function indexed<T>(records: Iterable<T>, ks: PropertyKey[]) {
const res = new EquivMap<any, Set<T>>();
let m, ik, rv;
for (m of records) {
ik = selectKeysObj(m, ks);
let x, ik, rv;
for (x of records) {
ik = selectKeysObj(x, ks);
rv = res.get(ik);
if (!rv) {
res.set(ik, rv = empty(records, Set));
}
rv.add(m);
!rv && res.set(ik, rv = empty(records, Set));
rv.add(x);
}
return res;
}
42 changes: 22 additions & 20 deletions packages/associative/src/join.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { IObjectOf } from "@thi.ng/api/api";

import { commonKeysObj } from "./common-keys";
import { indexed } from "./indexed";
import { intersection } from "./intersection";
import { invertObj } from "./invert";
import { mergeObj } from "./merge";
import { renameKeysObj } from "./rename-keys";
Expand All @@ -13,6 +13,7 @@ import { empty, first, objValues } from "./utils";
* is assumed to have plain objects as values with at least one of the
* keys present in both sides. Furthermore the objects in each set are
* assumed to have the same internal structure (i.e. sets of keys).
* Returns new set of same type as `xrel`.
*
* ```
* join(
Expand All @@ -33,23 +34,20 @@ import { empty, first, objValues } from "./utils";
* @param xrel
* @param yrel
*/
export function join<A, B>(xrel: Set<A>, yrel: Set<B>) {
export function join(xrel: Set<IObjectOf<any>>, yrel: Set<IObjectOf<any>>): Set<IObjectOf<any>> {
if (xrel.size && yrel.size) {
const ks = [...intersection(
new Set(Object.keys(first(xrel) || {})),
new Set(Object.keys(first(yrel) || {})))
];
let r, s;
const ks = commonKeysObj(first(xrel) || {}, first(yrel) || {});
let a: Set<any>, b: Set<any>;
if (xrel.size <= yrel.size) {
r = xrel;
s = yrel;
a = xrel;
b = yrel;
} else {
r = yrel;
s = xrel;
a = yrel;
b = xrel;
}
const idx = indexed(r, ks);
const res = empty(xrel, Set);
for (let x of s) {
const idx = indexed(a, ks);
const res: Set<any> = empty(xrel, Set);
for (let x of b) {
const found = idx.get(selectKeysObj(x, ks));
if (found) {
for (let f of found) {
Expand All @@ -64,8 +62,11 @@ export function join<A, B>(xrel: Set<A>, yrel: Set<B>) {

/**
* Similar to `join()`, computes the join between two sets of relations,
* using the given keys in `kmap` only. `kmap` can also be used to
* rename join keys in `yrel` where needed, e.g.
* using the given keys in `kmap` only for joining and ignoring others.
* `kmap` can also be used to translate join keys in `yrel` where
* needed. Else, if no renaming is desired, the values in `kmap` should
* be the same as their respective keys, e.g. `{id: "id"}`. Returns new
* set of same type as `xrel`.
*
* ```
* joinWith(
Expand All @@ -78,15 +79,16 @@ export function join<A, B>(xrel: Set<A>, yrel: Set<B>) {
* {type: 2, color: "blue"}]),
* {id: "type"}
* )
* // Set {
* // { type: 1, color: 'red', id: 1, name: 'foo' },
* // { type: 2, color: 'blue', id: 2, name: 'bar' } }
* ```
* If no renaming is desired, the values in `kmap` should be the same as
* their respective keys.
*
* @param xrel
* @param yrel
* @param kmap keys to compute join for
*/
export function joinWith<A, B>(xrel: Set<A>, yrel: Set<B>, kmap: IObjectOf<PropertyKey>) {
export function joinWith(xrel: Set<any>, yrel: Set<any>, kmap: IObjectOf<PropertyKey>): Set<any> {
if (xrel.size && yrel.size) {
let r: Set<any>, s: Set<any>;
let k: IObjectOf<PropertyKey>;
Expand All @@ -101,7 +103,7 @@ export function joinWith<A, B>(xrel: Set<A>, yrel: Set<B>, kmap: IObjectOf<Prope
}
const idx = indexed(r, objValues(k));
const ks = Object.keys(k);
const res = empty(xrel, Set);
const res: Set<any> = empty(xrel, Set);
for (let x of s) {
const found = idx.get(renameKeysObj(selectKeysObj(x, ks), k));
if (found) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
import { isFunction } from "@thi.ng/checks/is-function";
import { IObjectOf } from "@thi.ng/api/api";
import { isFunction } from "@thi.ng/checks/is-function";

import { copy } from "./utils";

/**
* Similar to `mapKeysObj()`, but for ES6 Maps instead of plain objects.
* Similar to `mergeApplyObj()`, but for ES6 Maps instead of plain objects.
*
* @param src
* @param xs
*/
export function mapKeysMap<K, V>(src: Map<K, V>, xs: Map<K, V | ((x: V) => V)>) {
const res: any = { ...src };
export function mergeApplyMap<K, V>(src: Map<K, V>, xs: Map<K, V | ((x: V) => V)>) {
const res: any = copy(src, Map);
for (let p of xs) {
let [k, v] = p;
if (isFunction(v)) {
v = v(res[k]);
}
isFunction(v) && (v = v(res[k]));
res.set(k, v);
}
return res;
Expand All @@ -22,8 +22,8 @@ export function mapKeysMap<K, V>(src: Map<K, V>, xs: Map<K, V | ((x: V) => V)>)
/**
* Similar to `mergeObj()`, but only supports 2 args and any function
* values in `xs` will be called with respective value in `src` to
* produce new value for that key. Returns new merged object and does
* not modify any of the inputs.
* produce a new / derived value for that key. Returns new merged object
* and does not modify any of the inputs.
*
* ```
* mapKeysObj({a: "hello", b: 23}, {a: (x) => x + " world", b: 42});
Expand All @@ -33,13 +33,11 @@ export function mapKeysMap<K, V>(src: Map<K, V>, xs: Map<K, V | ((x: V) => V)>)
* @param src
* @param xs
*/
export function mapKeysObj<V>(src: IObjectOf<V>, xs: IObjectOf<V | ((x: V) => V)>) {
export function mergeApplyObj<V>(src: IObjectOf<V>, xs: IObjectOf<V | ((x: V) => V)>) {
const res: any = { ...src };
for (let k in xs) {
let v = xs[k];
if (isFunction(v)) {
v = v(res[k]);
}
isFunction(v) && (v = v(res[k]));
res[k] = v;
}
return res;
Expand Down
10 changes: 10 additions & 0 deletions packages/associative/src/merge-deep.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { IObjectOf } from "@thi.ng/api/api";
import { isPlainObject } from "@thi.ng/checks/is-plain-object";

import { mergeObjWith } from "./merge-with";

export function mergeDeepObj(dest: IObjectOf<any>, ...xs: IObjectOf<any>[]) {
return mergeObjWith(
(a, b) => isPlainObject(a) && isPlainObject(b) ? mergeDeepObj(a, b) : b,
dest, ...xs);
}
32 changes: 32 additions & 0 deletions packages/associative/src/merge-with.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { IObjectOf } from "@thi.ng/api/api";

import { copy } from "./utils";

export function mergeMapWith<K, V>(f: (a: V, b: V) => V, dest: Map<K, V>, ...xs: Map<K, V>[]) {
const res: Map<K, V> = copy(dest, Map);
for (let x of xs) {
for (let [k, v] of x) {
if (res.has(k)) {
res.set(k, f(res.get(k), v));
} else {
res.set(k, v);
}
}
}
return res;
}

export function mergeObjWith<T>(f: (a: T, b: T) => T, dest: IObjectOf<T>, ...xs: IObjectOf<T>[]) {
const res: IObjectOf<T> = { ...dest };
for (let x of xs) {
for (let k in x) {
const v = x[k];
if (res.hasOwnProperty(k)) {
res[k] = f(dest[k], v);
} else {
res[k] = v;
}
}
}
return res;
}
32 changes: 17 additions & 15 deletions packages/associative/src/merge.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,28 @@
import { IObjectOf } from "@thi.ng/api/api";

/**
* Merges all given maps in left-to-right order into `m`.
* Returns `m`.
* Merges all given maps in left-to-right order into `dest`.
* Returns `dest`.
*
* @param m
* @param maps
* @param dest
* @param xs
*/
export function mergeMap<K, V>(m: Map<K, V>, ...maps: Map<K, V>[]) {
for (let mm of maps) {
for (let p of mm) {
m.set(p[0], p[1]);
export function mergeMap<K, V>(dest: Map<K, V>, ...xs: Map<K, V>[]) {
for (let x of xs) {
for (let pair of x) {
dest.set(pair[0], pair[1]);
}
}
return m;
return dest;
}

/**
* Merges all given objects in left-to-right order into `m`.
* Returns `m`.
* Merges all given objects in left-to-right order into `dest`.
* Returns `dest`.
*
* @param m
* @param maps
* @param dest
* @param xs
*/
export function mergeObj(m, ...maps: any[]) {
return Object.assign(m, ...maps);
export function mergeObj<T>(dest: IObjectOf<T>, ...xs: IObjectOf<T>[]): IObjectOf<T> {
return Object.assign(dest, ...xs);
}
35 changes: 26 additions & 9 deletions packages/associative/src/rename-keys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,38 @@ import { IObjectOf } from "@thi.ng/api/api";

import { empty } from "./utils";

export function renameKeysMap<T>(src: Map<any, T>, km: IObjectOf<T>): Map<any, T> {
const dest = empty(src, Map);
for (let p of src) {
const k = p[0];
const kk = km[k];
dest.set(kk !== undefined ? kk : k, p[1]);
/**
* Renames keys in `src` using mapping provided by key map `km`. Does
* support key swapping / swizzling. Does not modify original.
*
* @param src
* @param km
*/
export function renameKeysMap<K, V>(src: Map<K, V>, km: Map<K, K>) {
const dest: Map<K, V> = empty(src, Map);
for (let [k, v] of src) {
dest.set(km.has(k) ? km.get(k) : k, v);
}
return dest;
}

export function renameKeysObj(src: any, km: IObjectOf<PropertyKey>) {
/**
* Renames keys in `src` using mapping provided by key map `km`. Does
* support key swapping / swizzling. Does not modify original.
*
* ```
* // swap a & b, rename c
* renameKeysObj({a: 1, b: 2, c: 3}, {a: "b", b: "a", c: "cc"})
* // {b: 1, a: 2, cc: 3}
* ```
*
* @param src
* @param km
*/
export function renameKeysObj<T>(src: IObjectOf<T>, km: IObjectOf<PropertyKey>) {
const dest = {};
for (let k in src) {
const kk = km[k];
dest[kk != null ? kk : k] = src[k];
dest[km.hasOwnProperty(k) ? km[k] : k] = src[k];
}
return dest;
}

0 comments on commit 5991be6

Please sign in to comment.