Skip to content

Commit

Permalink
feat: add mapDistribute function
Browse files Browse the repository at this point in the history
This new function makes the developer's life easier while distributing values into arrays.
  • Loading branch information
albertossilva committed Dec 28, 2023
1 parent a6d038f commit f6447d0
Show file tree
Hide file tree
Showing 8 changed files with 132 additions and 13 deletions.
39 changes: 38 additions & 1 deletion docs/04-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 ])`
Expand Down Expand Up @@ -123,4 +160,4 @@ Returns the cent value of the currency.
```js
currency(123.45).cents(); // => 45
currency("0.99").cents(); // => 99
```
```
10 changes: 10 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions src/currency.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ declare module 'currency.js' {
multiply(number: currency.Any): currency;
divide(number: currency.Any): currency;
distribute(count: number): Array<currency>;
mapDistribute<T, U>(arrayToDistribute: Array<T>, callbackFn: (element: T, distributionElement: currency, index: number, array: Array<T>) => U, thisArg?: any): Array<U>;

dollars(): number;
cents(): number;
format(opts?: currency.Options | currency.Format): string;
Expand Down
60 changes: 49 additions & 11 deletions src/currency.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {

/**
Expand Down Expand Up @@ -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;
},

/**
Expand Down
2 changes: 2 additions & 0 deletions src/currency.js.flow
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ declare class currency {
multiply(number: $currency$any): currency;
divide(number: $currency$any): currency;
distribute(count: number): Array<currency>;
mapDistribute<T, U>(arrayToDistribute: Array<T>, callbackFn: (element: T, distributionElement: currency, index: number, array: Array<T>) => U, thisArg?: any): Array<U>;

dollars(): number;
cents(): number;
format(options?: $currency$opts | formatFunction): string;
Expand Down
4 changes: 4 additions & 0 deletions test/test.flow.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ currencyInstance.divide(currencyInstance);
// distribute
let a1: Array<currency> = currencyInstance.distribute(4);

// mapDistribute
let elements: Array<string> = ['a', 'b']
let a2: Array<[string, currency]> = currencyInstance.mapDistribute(elements, (element, c) => [element, c]);

// dollars
let d1: number = currencyInstance.dollars();

Expand Down
24 changes: 23 additions & 1 deletion test/test.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import test from 'ava';
import { spy } from 'sinon';
import currency from '../dist/currency';

test('should be immutable', t => {
Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -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);
});
});
4 changes: 4 additions & 0 deletions test/test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@ currencyInstance.divide(currencyInstance);
// distribute
let a1: Array<currency> = currencyInstance.distribute(4);

// mapDistribute
let elements: Array<string> = ['a', 'b']
let a2: Array<[string, currency]> = currencyInstance.mapDistribute(elements, (element, c) => [element, c]);

// dollars
let d1: number = currencyInstance.dollars();

Expand Down

0 comments on commit f6447d0

Please sign in to comment.