From f6447d0db3f166450e5fd8f8dbd49dac2ea61975 Mon Sep 17 00:00:00 2001 From: Alberto Silva Date: Thu, 28 Dec 2023 10:01:04 -0300 Subject: [PATCH] feat: add mapDistribute function This new function makes the developer's life easier while distributing values into arrays. --- docs/04-api.md | 39 +++++++++++++++++++++++++++- readme.md | 10 ++++++++ src/currency.d.ts | 2 ++ src/currency.js | 60 ++++++++++++++++++++++++++++++++++++-------- src/currency.js.flow | 2 ++ test/test.flow.js | 4 +++ test/test.js | 24 +++++++++++++++++- test/test.ts | 4 +++ 8 files changed, 132 insertions(+), 13 deletions(-) diff --git a/docs/04-api.md b/docs/04-api.md index 85923ef3..40c0b7e4 100644 --- a/docs/04-api.md +++ b/docs/04-api.md @@ -81,6 +81,43 @@ currency(12.35).distribute(3); // => [4.12, 4.12, 4.11] currency(12.00).distribute(3); // => [4.00, 4.00, 4.00] ``` +### mapDistribute + +`currency.mapDistribute( arrayToDistribute, callbackFn )` +`currency.mapDistribute( arrayToDistribute, callbackFn, thisArg )` + +Map an array element distributing the currency value with the array element. See [distribute](#distribute) + +#### Parameters + +##### `arrayToDistribute` +The array to distribute the value to. + +##### `callbackFn` +A function to execute for each element in the array and its own distribution. Its return value is added as a single element in the new array. The function is called with the following arguments: + +> `element` +>     The current element being processed in the array. +> `distributionElement` +>     The current distribution element corresponding to the `element`. +> `index` +>     The index of the current element being processed in the array. +> `array` +>     The `arrayToDistribute` use on the whole distribution. + +##### `thisArg` *optional* +A value to use as `this` when executing `callbackFn`. + +```javascript +const paymentInstruments = ['CreditCard1', 'CreditCard2'] +const mapped = currency(2.75).mapDistribute(paymentInstruments, callbackFn) +// => the callbackFn is called +// first with CreditCard1 and 1.38 +// then CreditCard2 and 1.37 + +// and mapped would be the array of all the results of callbackFn +``` + ### format `currency.format([ function | options ])` @@ -123,4 +160,4 @@ Returns the cent value of the currency. ```js currency(123.45).cents(); // => 45 currency("0.99").cents(); // => 99 -``` \ No newline at end of file +``` diff --git a/readme.md b/readme.md index dd69ff49..d3ddab88 100644 --- a/readme.md +++ b/readme.md @@ -89,7 +89,17 @@ currency(5.00).subtract(0.50); // 4.50 currency(45.25).multiply(3); // 135.75 currency(1.12).distribute(5); // [0.23, 0.23, 0.22, 0.22, 0.22] ``` +] +There's also a utility function to map and distribute over an array +```javascript +const paymentInstruments = ['CreditCard1', 'CreditCard2'] +const mapped = currency(2.75).mapDistribute(paymentInstruments, callbackFn) +// => the callbackFn is called +// first with CreditCard1 and 1.38 +// then CreditCard2 and 1.37 +// and mapped would be the array of all the results of callbackFn +``` There's even a built in formatter that will automatically place comma delimiters in the right place. ```javascript diff --git a/src/currency.d.ts b/src/currency.d.ts index 770fb923..cd512296 100644 --- a/src/currency.d.ts +++ b/src/currency.d.ts @@ -27,6 +27,8 @@ declare module 'currency.js' { multiply(number: currency.Any): currency; divide(number: currency.Any): currency; distribute(count: number): Array; + mapDistribute(arrayToDistribute: Array, callbackFn: (element: T, distributionElement: currency, index: number, array: Array) => U, thisArg?: any): Array; + dollars(): number; cents(): number; format(opts?: currency.Options | currency.Format): string; diff --git a/src/currency.js b/src/currency.js index 1edc89fe..2dd8ffd5 100644 --- a/src/currency.js +++ b/src/currency.js @@ -104,6 +104,35 @@ function format(currency, settings) { .replace('#', dollars.replace(groups, '$1' + separator) + (cents ? decimal + cents : '')); } +/** + * Takes the currency amount and distributes the values evenly. Any extra pennies + * left over from the distribution will be stacked onto the first set of entries. + * And apply the mapFunction for each array element and it owns distributed value. + * @param {number} intValue + * @param {number} precision + * @param {object} settings + * @param {number} count + * @returns {array} + */ +function distribute(intValue, precision, settings, count) { + let distribution = [] + , split = Math[intValue >= 0 ? 'floor' : 'ceil'](intValue / count) + , pennies = Math.abs(intValue - (split * count)) + , precisionToUse = settings.fromCents ? 1 : precision; + + for (; count !== 0; count--) { + let item = currency(split / precisionToUse, settings); + + // Add any left over pennies + pennies-- > 0 && (item = item[intValue >= 0 ? 'add' : 'subtract'](1 / precision)); + + distribution.push(item); + } + + return distribution; +} + + currency.prototype = { /** @@ -153,22 +182,31 @@ currency.prototype = { * @returns {array} */ distribute(count) { - let { intValue, _precision, _settings } = this - , distribution = [] - , split = Math[intValue >= 0 ? 'floor' : 'ceil'](intValue / count) - , pennies = Math.abs(intValue - (split * count)) - , precision = _settings.fromCents ? 1 : _precision; + const { intValue, _precision, _settings } = this; + + return distribute(intValue, _precision, _settings, count) + }, - for (; count !== 0; count--) { - let item = currency(split / precision, _settings); + /** + * Takes the currency amount and distributes the values evenly. Any extra pennies + * left over from the distribution will be stacked onto the first set of entries. + * And apply the mapFunction for each array element and it owns distributed value. + * @param {array} array to distribute on + * @param {callbackFn} callback function to apply while mapping + * @param {thisArg} A value to use as this when executing callbackFn + * @returns {array} + */ + mapDistribute(arrayToDistribute, callbackFn, thisArg) { + const { intValue, _precision, _settings } = this; - // Add any left over pennies - pennies-- > 0 && (item = item[intValue >= 0 ? 'add' : 'subtract'](1 / precision)); + const distribution = distribute(intValue, _precision, _settings, arrayToDistribute.length) + const distributionResult = [] - distribution.push(item); + for (const [index, item] of distribution.entries()) { + distributionResult.push(callbackFn.call(thisArg, arrayToDistribute[index], item, index, arrayToDistribute)); } - return distribution; + return distributionResult; }, /** diff --git a/src/currency.js.flow b/src/currency.js.flow index f3e0edfc..6444dfe1 100644 --- a/src/currency.js.flow +++ b/src/currency.js.flow @@ -25,6 +25,8 @@ declare class currency { multiply(number: $currency$any): currency; divide(number: $currency$any): currency; distribute(count: number): Array; + mapDistribute(arrayToDistribute: Array, callbackFn: (element: T, distributionElement: currency, index: number, array: Array) => U, thisArg?: any): Array; + dollars(): number; cents(): number; format(options?: $currency$opts | formatFunction): string; diff --git a/test/test.flow.js b/test/test.flow.js index 340b883e..b46591b2 100644 --- a/test/test.flow.js +++ b/test/test.flow.js @@ -49,6 +49,10 @@ currencyInstance.divide(currencyInstance); // distribute let a1: Array = currencyInstance.distribute(4); +// mapDistribute +let elements: Array = ['a', 'b'] +let a2: Array<[string, currency]> = currencyInstance.mapDistribute(elements, (element, c) => [element, c]); + // dollars let d1: number = currencyInstance.dollars(); diff --git a/test/test.js b/test/test.js index ce1d38ca..3cfbd4f1 100644 --- a/test/test.js +++ b/test/test.js @@ -1,4 +1,5 @@ import test from 'ava'; +import { spy } from 'sinon'; import currency from '../dist/currency'; test('should be immutable', t => { @@ -191,6 +192,27 @@ test('should create non-equal distribution with a negative penny', t => { t.is(total, -0.01, 'sum of values matches our original amount'); }); +test('should map distributing the value by the array length', t => { + var arrayToDistribute = ['a', 'b', 'c'] + var justCopyTheArguments = (element, currencyElement, index, array) => [element, currencyElement, index, array] + var result = currency(1).mapDistribute(arrayToDistribute, justCopyTheArguments) + + t.deepEqual(result, [ + ['a', currency(0.34), 0, arrayToDistribute], + ['b', currency(0.33), 1, arrayToDistribute], + ['c', currency(0.33), 2, arrayToDistribute], + ]) +}) + +test('should call the callbackFn with thisArg', t => { + var arrayToDistribute = ['a'] + var spyToTestThisArg = spy() + var thisArg = { just: 'for Reference' } + currency(1).mapDistribute(arrayToDistribute, spyToTestThisArg, thisArg) + + t.is(spyToTestThisArg.firstCall.thisValue, thisArg) +}) + test('should get dollar value', t => { var value = currency(1.23); @@ -555,4 +577,4 @@ test('should handle fractional cents', t => { var values = currency(1234.56, { fromCents: true }); t.is(values.intValue, 1235); t.is(values.value, 12.35); -}); \ No newline at end of file +}); diff --git a/test/test.ts b/test/test.ts index 3421f5f6..ffea9a88 100644 --- a/test/test.ts +++ b/test/test.ts @@ -52,6 +52,10 @@ currencyInstance.divide(currencyInstance); // distribute let a1: Array = currencyInstance.distribute(4); +// mapDistribute +let elements: Array = ['a', 'b'] +let a2: Array<[string, currency]> = currencyInstance.mapDistribute(elements, (element, c) => [element, c]); + // dollars let d1: number = currencyInstance.dollars();