Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add mapDistribute function #462

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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