Created
August 19, 2017 21:36
-
-
Save goldhand/564bc0b7eb6732fd0fa23937c80062e3 to your computer and use it in GitHub Desktop.
Functional utility functions
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 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; | |
}, {}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 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