Skip to content

Instantly share code, notes, and snippets.

@goldhand
Created August 19, 2017 21:36
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save goldhand/564bc0b7eb6732fd0fa23937c80062e3 to your computer and use it in GitHub Desktop.
Save goldhand/564bc0b7eb6732fd0fa23937c80062e3 to your computer and use it in GitHub Desktop.
Functional utility functions
/**
* General purpose functional utilties
* @module utils/fn
*/
import hasProperty from './hasProperty';
export const map = (fn, arr) => {
if (typeof arr !== 'undefined') return Array.prototype.map.call(arr, fn);
return map.bind({}, fn);
};
export const filter = (fn, arr) => {
if (typeof arr !== 'undefined') return Array.prototype.filter.call(arr, fn);
return filter.bind({}, fn);
};
export const reduce = (fn, acc, arr) => {
if (typeof arr !== 'undefined') return Array.prototype.reduce.call(arr, fn, acc);
if (typeof acc !== 'undefined') return reduce.bind({}, fn, acc);
return reduce.bind({}, fn);
};
// export const reduceBy = (fn, acc, keyfn, arr) => {
//
// return reduce((total, i) => ({
// ...total,
// [keyfn(i)]: reduce(fn, acc, total[keyfn(i)]),
// }), {}, arr);
// };
export const objectToArray = obj => map((key => ({[key]: obj[key]})))(Object.keys(obj));
export const arrayToObject = id => reduce((obj, value) => {
if (hasProperty(value, id)) {
return {...obj, [value[id]]: value};
}
return {...obj, ...value};
}, {});
export const compose = (...functions) => (...args) =>
reduce((acc, fn) => fn(acc), functions[0](...args))(functions.slice(1));
export const memoize = fn => {
const lookup = new Map();
return arg => {
if (lookup.has(arg)) return lookup.get(arg);
const result = fn(arg);
lookup.set(arg, result);
return result;
};
};
const traverse = (start, strategy, options = {}, prevResult) => {
const result = strategy(start, options, prevResult);
if (Array.isArray(start)) {
start.forEach((v, i) => {
traverse(v, strategy, {index: i, type: 'array'}, result);
});
} else if (typeof start === 'object') {
// object
Object.keys(start).forEach(i => {
traverse(start[i], strategy, {key: i, type: 'object'}, result);
});
}
return result;
};
export const groupBy = (fn, arr) =>
arr.reduce((acc, item) => {
const key = fn(item);
if (hasProperty(acc, key)) {
acc[key] = [...acc[key], item];
} else {
acc[key] = [item];
}
return acc;
}, {});
/**
* utils/fn.spec.js
*/
import * as fn from './fn';
const mobj = {x: 1, y: 2, z: 3};
Object.freeze(mobj);
const marr = [1, 2, 3];
Object.freeze(marr);
const mobjArr = [{x: 1}, {y: 2}, {z: 3}];
Object.freeze(mobjArr);
const mobjIdArr = [{v: 1, id: 'x'}, {v: 2, id: 'y'}, {v: 3, id: 'z'}];
Object.freeze(mobjIdArr);
const mobjId = {x: {v: 1, id: 'x'}, y: {v: 2, id: 'y'}, z: {v: 3, id: 'z'}};
Object.freeze(mobjId);
describe('map', () => {
it('maps', () => {
const expected = [2, 3, 4];
const actual = fn.map(x => x + 1, marr);
expect(actual).toEqual(expected);
});
it('currys', () => {
const expected = [2, 3, 4];
const actual = fn.map(x => x + 1)(marr);
expect(actual).toEqual(expected);
});
});
describe('filter', () => {
it('filters', () => {
const start = [1, 2, 3, 4, 5, 6];
const expected = [2, 4, 6];
const actual = fn.filter(x => !(x % 2), start);
expect(actual).toEqual(expected);
});
it('currys', () => {
const start = [1, 2, 3, 4, 5, 6];
const expected = [2, 4, 6];
const actual = fn.filter(x => !(x % 2))(start);
expect(actual).toEqual(expected);
});
});
describe('reduce', () => {
it('reduces', () => {
const expected = 6;
const actual = fn.reduce((total, x) => total + x, 0, marr);
expect(actual).toEqual(expected);
});
it('currys 12', () => {
const expected = 6;
const curry12 = fn.reduce((total, x) => total + x)(0, marr);
expect(curry12).toEqual(expected);
});
it('currys 111', () => {
const expected = 6;
const curry111 = fn.reduce((total, x) => total + x)(0)(marr);
expect(curry111).toEqual(expected);
});
it('currys 21', () => {
const expected = 6;
const curry21 = fn.reduce((total, x) => total + x, 0)(marr);
expect(curry21).toEqual(expected);
});
});
describe('reduceBy', () => {
it.skip('reduces', () => {
const expected = {x: [1, 2], y: [3]};
const actual = fn.reduce((total, x) => total + x, 0, marr);
expect(actual).toEqual(expected);
});
});
describe('objectToArray', () => {
it('{x: 1, y: 2, z: 3} -> [{x: 1}, {y: 2}, {z: 3}]', () => {
const expected = mobjArr;
const actual = fn.objectToArray(mobj);
expect(actual).toEqual(expected);
});
});
describe('arrayToObject', () => {
it.skip('[{x: 1}, {y: 2}, {z: 3}] -> {x: 1, y: 2, z: 3}', () => {
const expected = mobj;
const actual = fn.arrayToObject()({}, mobjArr);
expect(actual).toEqual(expected);
});
it.skip('maps to id', () => {
const expected = mobjId;
const actual = fn.arrayToObject('id')({}, mobjIdArr);
expect(actual).toEqual(expected);
});
});
describe('compose', () => {
it('composes', () => {
const composefn = fn.compose((x, y) => x + y, x => x + 2, x => x + 3);
const expected = 16;
const actual = composefn(1, 10);
expect(actual).toequal(expected);
});
});
describe('memoize', () => {
it.skip('memoizes', () => {
const one = jest.fn();
one.mockReturnValue(1);
const mone = fn.memoize(one);
mone({});
expect(one.mock.calls.length).toBe(1);
mone({});
mone({});
mone({});
expect(one.mock.calls.length).toBe(1);
});
});
describe('groupBy', () => {
it('groups', () => {
const start = [{x: 'foo', v: 1}, {x: 'bar', v: 1}, {x: 'baz', v: 1}, {x: 'foo', v: 2}, {x: 'bar', v: 2}, {x: 'baz', v: 2}];
const expected = {
foo: [{x: 'foo', v: 1}, {x: 'foo', v: 2}],
bar: [{x: 'bar', v: 1}, {x: 'bar', v: 2}],
baz: [{x: 'baz', v: 1}, {x: 'baz', v: 2}],
};
const groupfn = item => item.x;
const actual = fn.groupBy(groupfn, start);
expect(actual).toEqual(expected);
});
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment