diff --git a/src/__tests__/reduceBy.js b/src/__tests__/reduceBy.js new file mode 100644 index 0000000..cdce61a --- /dev/null +++ b/src/__tests__/reduceBy.js @@ -0,0 +1,42 @@ +/* eslint-disable no-nested-ternary */ +import reduceBy from '../reduceBy'; + +describe('Core.reduceBy', () => { + test('splits the list into groups according to the grouping function', () => { + const grade = score => + score < 65 + ? 'F' + : score < 70 ? 'D' : score < 80 ? 'C' : score < 90 ? 'B' : 'A'; + + const students = [ + { name: 'Abby', score: 84 }, + { name: 'Brad', score: 73 }, + { name: 'Chris', score: 89 }, + { name: 'Dianne', score: 99 }, + { name: 'Eddy', score: 58 }, + { name: 'Fred', score: 67 }, + { name: 'Gillian', score: 91 }, + { name: 'Hannah', score: 78 }, + { name: 'Irene', score: 85 }, + { name: 'Jack', score: 69 }, + ]; + + const byGrade = student => grade(student.score || 0); + const collectNames = (acc, student) => acc.concat(student.name); + + expect(reduceBy(collectNames, [], byGrade, students)).toEqual({ + A: ['Dianne', 'Gillian'], + B: ['Abby', 'Chris', 'Irene'], + C: ['Brad', 'Hannah'], + D: ['Fred', 'Jack'], + F: ['Eddy'], + }); + }); + + test('returns an empty object if given an empty array', () => { + const byType = obj => obj.type; + const sumValues = (acc, obj) => acc + obj.val; + + expect(reduceBy(sumValues, 0, byType, [])).toEqual({}); + }); +}); diff --git a/src/countBy.js b/src/countBy.js index f770c15..51597d4 100644 --- a/src/countBy.js +++ b/src/countBy.js @@ -1,16 +1,6 @@ -import reduce from './reduce'; -import prop from './prop'; -import assoc from './assoc'; +import reduceBy from './reduceBy'; +import inc from './inc'; -const countBy = (fn, list) => - reduce( - (acc, item) => { - const key = fn(item); - const existingKey = prop(key, acc); - return assoc(key, existingKey ? existingKey + 1 : 1, acc); - }, - {}, - list, - ); +const countBy = (keyFn, list) => reduceBy(inc, 0, keyFn, list); export { countBy as default }; diff --git a/src/index.js b/src/index.js index 364fc00..065c4f2 100644 --- a/src/index.js +++ b/src/index.js @@ -45,6 +45,7 @@ import prepend from './prepend'; import product from './product'; import prop from './prop'; import reduce from './reduce'; +import reduceBy from './reduceBy'; import reduceRight from './reduceRight'; import reverse from './reverse'; import slice from './slice'; @@ -102,6 +103,7 @@ export { product, prop, reduce, + reduceBy, reduceRight, reverse, slice, diff --git a/src/reduceBy.js b/src/reduceBy.js new file mode 100644 index 0000000..d124e7d --- /dev/null +++ b/src/reduceBy.js @@ -0,0 +1,16 @@ +import reduce from './reduce'; +import has from './has'; +import assoc from './assoc'; + +const reduceBy = (valFn, valAcc, keyFn, list) => + reduce( + (acc, item) => { + const key = keyFn(item); + const val = valFn(has(key, acc) ? acc[key] : valAcc, item); + return assoc(key, val, acc); + }, + {}, + list, + ); + +export { reduceBy as default };