Skip to content

Commit

Permalink
joining table refactorings
Browse files Browse the repository at this point in the history
  • Loading branch information
ppaska committed Feb 14, 2020
1 parent 08fa5d9 commit 21544bb
Show file tree
Hide file tree
Showing 5 changed files with 209 additions and 86 deletions.
109 changes: 99 additions & 10 deletions src/array-joins.ts
Original file line number Diff line number Diff line change
@@ -1,36 +1,79 @@

export type FieldSelectorFunction = (item: any) => string;

function fieldSelector(input: string | string[] | FieldSelectorFunction): FieldSelectorFunction {
if (typeof input === "function") {
return input;
} else if (typeof input === "string") {
return (item) => item[input];
} else if (Array.isArray(input)) {
return (item) => input.map(r => item[r]).join('|');
} else {
throw Error(`Unknown input. Can't create a fieldSelector`)
}
}

/**
* leftJoin returns all elements from the left array (leftArray), and the matched elements from the right array (rightArray).
* The result is NULL from the right side, if there is no match.
* @param leftArray : array for left side in a join
* @param rightArray : array for right side in a join
* @param leftKey : A key from left side array. What can be as a fieldName, multiple fields or key Selector
* @param rightKey : A key from right side array. what can be as a fieldName, multiple fields or key Selector
* @param resultSelector A callback function that returns result value
*/
export function leftJoin(
leftArray: any[],
rightArray: any[],
leftKeySelector: (item: any) => string,
rightKeySelector: (item: any) => string,
leftKeySelector: string | string[] | FieldSelectorFunction,
rightKeySelector: string | string[] | FieldSelectorFunction,
resultSelector: (leftItem: any, rightItem: any) => any
): any[] {
return leftOrInnerJoin(false, leftArray, rightArray, leftKeySelector, rightKeySelector, resultSelector);
}

/**
* innerJoin - Joins two arrays together by selecting elements that have matching values in both arrays.
* If there are elements in any array that do not have matches in other array, these elements will not be shown!
* @param leftArray : array for left side in a join
* @param rightArray : array for right side in a join
* @param leftKey : A key from left side array. What can be as a fieldName, multiple fields or key Selector
* @param rightKey : A key from right side array. what can be as a fieldName, multiple fields or key Selector
* @param resultSelector A callback function that returns result value
*/
export function innerJoin(
leftArray: any[],
rightArray: any[],
leftKeySelector: (item: any) => string,
rightKeySelector: (item: any) => string,
leftKey: string | string[] | FieldSelectorFunction,
rightKey: string | string[] | FieldSelectorFunction,
resultSelector: (leftItem: any, rightItem: any) => any
): any[] {
return leftOrInnerJoin(true, leftArray, rightArray, leftKeySelector, rightKeySelector, resultSelector);
return leftOrInnerJoin(true, leftArray, rightArray, leftKey, rightKey, resultSelector);
}

/**
* fullJoin returns all elements from the left array (leftArray), and all elements from the right array (rightArray).
* The result is NULL from the right/left side, if there is no match.
* @param leftArray : array for left side in a join
* @param rightArray : array for right side in a join
* @param leftKey : A key from left side array. What can be as a fieldName, multiple fields or key Selector
* @param rightKey : A key from right side array. what can be as a fieldName, multiple fields or key Selector
* @param resultSelector A callback function that returns result value
*/
export function fullJoin(
leftArray: any[],
rightArray: any[],
leftKeySelector: (item: any) => string,
rightKeySelector: (item: any) => string,
leftKey: string | string[] | FieldSelectorFunction,
rightKey: string | string[] | FieldSelectorFunction,
resultSelector: (leftItem: any, rightItem: any) => any
): any[] {
const leftKeySelector = fieldSelector(leftKey);
const rightKeySelector = fieldSelector(rightKey);

verifyJoinArgs(leftArray, rightArray, leftKeySelector, rightKeySelector, resultSelector);

// build a lookup maps for both arrays.
// so, both jeys have to be unique, otherwise it will flatter result
// so, both of them have to be unique, otherwise it will flattern result
const leftArrayMap = Object.create(null);
for (let item of rightArray) {
leftArrayMap[leftKeySelector(item)] = item;
Expand Down Expand Up @@ -67,6 +110,50 @@ export function fullJoin(
return result;
}

/**
* merges elements from two arrays. It appends source element or overrides to target array based on matching keys provided
* @param targetArray target array
* @param sourceArray source array
* @param targetKey tartget key field, arry of fields or field serlector
* @param sourceKey source key field, arry of fields or field serlector
*/
export function merge(
targetArray: any[],
sourceArray: any[],
targetKey: string | string[] | FieldSelectorFunction,
sourceKey: string | string[] | FieldSelectorFunction
): any[] {

const targetKeySelector = fieldSelector(targetKey);
const sourceKeySelector = fieldSelector(sourceKey);
verifyJoinArgs(targetArray, sourceArray, targetKeySelector, sourceKeySelector, () => { });

// build a lookup maps for both arrays.
// so, both of them have to be unique, otherwise it will flattern result
const targetArrayMap = Object.create(null);
for (let item of sourceArray) {
targetArrayMap[targetKeySelector(item)] = item;
}

const sourceArrayMap = Object.create(null);
for (let item of sourceArray) {
sourceArrayMap[sourceKeySelector(item)] = item;
}

for (let sourceItemKey of Object.keys(sourceArrayMap)) {
const sourceItem = sourceArrayMap[sourceItemKey];
if(!targetArrayMap[sourceItemKey]){
targetArray.push(sourceItem);
} else {
// merge properties in
Object.assign(targetArrayMap[sourceItemKey], sourceItem);
}
}

return targetArray;
}


function verifyJoinArgs(
leftArray: any[],
rightArray: any[],
Expand Down Expand Up @@ -98,10 +185,12 @@ function leftOrInnerJoin(
isInnerJoin: boolean,
leftArray: any[],
rightArray: any[],
leftKeySelector: (item: any) => string,
rightKeySelector: (item: any) => string,
leftKey: string | string[] | FieldSelectorFunction,
rightKey: string | string[] | FieldSelectorFunction,
resultSelector: (leftItem: any, rightItem: any) => any
): any[] {
const leftKeySelector = fieldSelector(leftKey);
const rightKeySelector = fieldSelector(rightKey);

verifyJoinArgs(leftArray, rightArray, leftKeySelector, rightKeySelector, resultSelector);

Expand Down
7 changes: 4 additions & 3 deletions src/array.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import * as pipeFuncs from './array';
import { leftJoin } from './array-joins';

export const data = [
{ name: "John", country: "US" }, { name: "Joe", country: "US" }, { name: "Bill", country: "US" }, { name: "Adam", country: "UK" },
Expand All @@ -17,7 +18,7 @@ describe('Test array methods', () => {
const testAnyPrimitiveArrayMin = 0;
const testAnyPrimitiveArrayMax = 33;

const testObjArray = testNumberArray.map(value => ({value}));
const testObjArray = testNumberArray.map(value => ({ value }));
const dates = [new Date('10/01/12'), new Date('10/01/10'), new Date('10/01/09'), new Date('10/01/11')]

it('count', () => {
Expand Down Expand Up @@ -104,8 +105,8 @@ describe('Test array methods', () => {
});

it('joinArray', () => {
const countries = [{code: 'US', capital: 'Washington'}, {code: 'UK', capital: 'London'}];
const joinedArray = pipeFuncs.joinArray(data, countries, i => i.country, i2 => i2.code);
const countries = [{ code: 'US', capital: 'Washington' }, { code: 'UK', capital: 'London' }];
const joinedArray = leftJoin(data, countries, i => i.country, i2 => i2.code, (l, r) => ({ ...r, ...l }));
expect(joinedArray.length).toBe(8);
const item = pipeFuncs.first(joinedArray, i => i.country === 'US');
expect(item.country).toBe('US');
Expand Down
74 changes: 34 additions & 40 deletions src/data-pipe.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { sum, avg, count, min, max, first, last, groupBy, flatten, countBy, joinArray } from './array';
import { sum, avg, count, min, max, first, last, groupBy, flatten, countBy } from './array';
import { Selector, Predicate } from './models';
import { fromTable, toTable } from './table';
import { ParsingOptions, fromCsv } from './dsv-parser';
import { leftJoin, innerJoin, fullJoin } from './array-joins';
import { ParsingOptions, parseCsv } from './dsv-parser';
import { leftJoin, innerJoin, fullJoin, merge, FieldSelectorFunction } from './array-joins';


export class DataPipe<T = any> {
private data: Array<T | any>;
Expand Down Expand Up @@ -125,56 +126,47 @@ export class DataPipe<T = any> {
return countBy(this.data, elementSelector);
}

innerJoin(rightArray: any[], leftKeyField: string, rightKeyField: string,
resultSelector: (leftItem: any, rightItem: any) => any
): DataPipe;

innerJoin(rightArray: any[], leftKeySelector: (item: any) => string, rightKeySelector: (item: any) => string,
resultSelector: (leftItem: any, rightItem: any) => any
): DataPipe;

innerJoin(rightArray: any[], leftKey: any, rightKey: any,
/**
* Joins two arrays together by selecting elements that have matching values in both arrays
* @param rightArray array of elements to join
* @param leftKey left Key
* @param rightKey
* @param resultSelector
*/
innerJoin(rightArray: any[],
leftKey: string | string[] | FieldSelectorFunction,
rightKey: string | string[] | FieldSelectorFunction,
resultSelector: (leftItem: any, rightItem: any) => any
): DataPipe {
const leftKeySelector: (item: any) => string = typeof leftKey === "function" ? leftKey : (item) => item[String(leftKey)];
const rightKeySelector: (item: any) => string = typeof rightKey === "function" ? rightKey : (item) => item[String(rightKey)];

this.data = innerJoin(this.data, rightArray, leftKeySelector, rightKeySelector, resultSelector);
this.data = innerJoin(this.data, rightArray, leftKey, rightKey, resultSelector);
return this;
}

leftJoin(rightArray: any[], leftKeyField: string, rightKeyField: string,
resultSelector: (leftItem: any, rightItem: any) => any
): DataPipe;

leftJoin(rightArray: any[], leftKeySelector: (item: any) => string, rightKeySelector: (item: any) => string,
resultSelector: (leftItem: any, rightItem: any) => any
): DataPipe;

leftJoin(rightArray: any[], leftKey: any, rightKey: any,
leftJoin(rightArray: any[],
leftKey: string | string[] | FieldSelectorFunction,
rightKey: string | string[] | FieldSelectorFunction,
resultSelector: (leftItem: any, rightItem: any) => any
): DataPipe {
const leftKeySelector: (item: any) => string = typeof leftKey === "function" ? leftKey : (item) => item[String(leftKey)];
const rightKeySelector: (item: any) => string = typeof rightKey === "function" ? rightKey : (item) => item[String(rightKey)];

this.data = leftJoin(this.data, rightArray, leftKeySelector, rightKeySelector, resultSelector);
this.data = leftJoin(this.data, rightArray, leftKey, rightKey, resultSelector);
return this;
}

fullJoin(rightArray: any[], leftKeyField: string, rightKeyField: string,
fullJoin(rightArray: any[],
leftKey: string | string[] | FieldSelectorFunction,
rightKey: string | string[] | FieldSelectorFunction,
resultSelector: (leftItem: any, rightItem: any) => any
): DataPipe;

fullJoin(rightArray: any[], leftKeySelector: (item: any) => string, rightKeySelector: (item: any) => string,
resultSelector: (leftItem: any, rightItem: any) => any
): DataPipe;
): DataPipe {
this.data = fullJoin(this.data, rightArray, leftKey, rightKey, resultSelector);
return this;
}

fullJoin(rightArray: any[], leftKey: any, rightKey: any,
resultSelector: (leftItem: any, rightItem: any) => any
merge(
sourceArray: any[],
targetKey: string | string[] | FieldSelectorFunction,
sourceKey: string | string[] | FieldSelectorFunction
): DataPipe {
const leftKeySelector: (item: any) => string = typeof leftKey === "function" ? leftKey : (item) => item[String(leftKey)];
const rightKeySelector: (item: any) => string = typeof rightKey === "function" ? rightKey : (item) => item[String(rightKey)];
this.data = fullJoin(this.data, rightArray, leftKeySelector, rightKeySelector, resultSelector);
this.data = merge(this.data, sourceArray, targetKey, sourceKey);
return this;
}

Expand All @@ -198,7 +190,7 @@ export class DataPipe<T = any> {
filter = this.where.bind(this);

fromCsv(content: string, options?: ParsingOptions): DataPipe {
this.data = fromCsv(content, options);
this.data = parseCsv(content, options);
return this;
}

Expand Down Expand Up @@ -232,4 +224,6 @@ export class DataPipe<T = any> {
this.data = Array.from(new Set(this.data));
return this;
}


}
Loading

0 comments on commit 21544bb

Please sign in to comment.