Skip to content

Commit

Permalink
feat(Array): add countBy fn
Browse files Browse the repository at this point in the history
  • Loading branch information
Thanga-Ganapathy committed Mar 18, 2024
1 parent f99a11e commit 4354b39
Show file tree
Hide file tree
Showing 14 changed files with 191 additions and 19 deletions.
5 changes: 5 additions & 0 deletions .changeset/strange-comics-fail.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@opentf/utils": minor
---

Added arrIns, arrReplace, arrRm & countBy array util fns.
39 changes: 30 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,23 +47,37 @@ bun add @opentf/utils
## Usage

```ts
import { isNum, pascalCase, sort, clone, isEql, isEqlArr, sleep } from "@opentf/utils";
import {
isNum,
pascalCase,
sort,
clone,
isEql,
isEqlArr,
sleep,
} from "@opentf/utils";

isNum(NaN); //=> false

pascalCase('pascal case'); //=> PascalCase
pascalCase("pascal case"); //=> PascalCase

sort([1, 10, 21, 2], 'desc'); //=> [ 21, 10, 2, 1 ]
sort([1, 10, 21, 2], "desc"); //=> [ 21, 10, 2, 1 ]

const obj = {
a: 1,
b: 'abc',
c: new Map([['key', 'val']])
a: 1,
b: "abc",
c: new Map([["key", "val"]]),
};
clone(obj); // It returns deeply cloned value.

const mapA = new Map([ ['a', 1], ['b', 2] ])
const mapB = new Map([ ['b', 2], ['a', 1] ])
const mapA = new Map([
["a", 1],
["b", 2],
]);
const mapB = new Map([
["b", 2],
["a", 1],
]);
isEql(mapA, mapB); //=> false

isEqlArr([1, 2, 3], [2, 3, 1]); //=> true
Expand All @@ -78,14 +92,21 @@ await sleep(1000); // It suspends the exection for 1 second.
- [arrChunk](https://js-utils.pages.dev/Array/arrChunk)
- [arrCompact](https://js-utils.pages.dev/Array/arrCompact)
- [arrDiff](https://js-utils.pages.dev/Array/arrDiff)
- [asyncFilter](https://js-utils.pages.dev/Array/asyncFilter)
- [arrIns](https://js-utils.pages.dev/Array/arrIns)
- [arrReplace](https://js-utils.pages.dev/Array/arrReplace)
- [arrRm](https://js-utils.pages.dev/Array/arrRm)
- [countBy](https://js-utils.pages.dev/Array/countBy)
- [groupBy](https://js-utils.pages.dev/Array/groupBy)
- [intersection](https://js-utils.pages.dev/Array/intersection)
- [range](https://js-utils.pages.dev/Array/range)
- [move](https://js-utils.pages.dev/Array/move)
- [sort](https://js-utils.pages.dev/Array/sort)
- [sortBy](https://js-utils.pages.dev/Array/sortBy)

## Async

- [asyncFilter](https://js-utils.pages.dev/Array/asyncFilter)

### Maths

- [percentage](https://js-utils.pages.dev/Maths/percentage)
Expand Down
2 changes: 1 addition & 1 deletion apps/docs/pages/Array/_meta.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"arrIns": "arrIns",
"arrReplace": "arrReplace",
"arrRm": "arrRm",
"asyncFilter": "asyncFilter",
"countBy": "countBy",
"groupBy": "groupBy",
"intersection": "intersection",
"move": "move",
Expand Down
60 changes: 60 additions & 0 deletions apps/docs/pages/Array/countBy.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { Callout } from "nextra/components";
import REPL from "../../components/REPL";

> Counts the items in the given array and groups them by the name provided.
## Syntax

```ts
import { countBy } from '@opentf/utils';

countBy<T>(
arr: T[] = [],
by: ((val: T) => string) | string
)
```

## Examples

```ts
countBy([]) //=> {}

countBy([1, 2, 3, 4, 5, 6, 7, 8, 9], (v) =>
v % 2 === 0 ? 'Even' : 'Odd'
)
//=> { Even: 4, Odd: 5 });

countBy(['Apple', 'Mango', 'Orange'], 'length')
//=>
// {
// '5': 2,
// '6': 1,
// }

const inventory = [
{ name: 'asparagus', type: 'vegetables', qty: 5 },
{ name: 'bananas', type: 'fruit', qty: 0 },
{ name: 'goat', type: 'meat', qty: 23 },
{ name: 'cherries', type: 'fruit', qty: 5 },
{ name: 'fish', type: 'meat', qty: 22 },
];
countBy(inventory, ({ qty }) => (qty === 0 ? 'OutOfStock' : 'InStock'))
//=> { OutOfStock: 1, InStock: 4 });

const letters = ['a', 'b', 'A', 'a', 'B', 'c'];
countBy(letters, (l) => l.toLowerCase())
//=>
// {
// a: 3,
// b: 2,
// c: 1,
// }
```

## Try

<REPL code={`const { countBy } = require('@opentf/utils');
const letters = ['a', 'b', 'A', 'a', 'B', 'c'];
countBy(letters, (l) => l.toLowerCase());
`} />
3 changes: 3 additions & 0 deletions apps/docs/pages/Async/_meta.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"asyncFilter": "asyncFilter"
}
File renamed without changes.
35 changes: 28 additions & 7 deletions packages/utils/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,15 @@ bun add @opentf/utils
## Usage

```ts
import { isNum, pascalCase, sort, clone, isEql, isEqlArr, sleep } from "@opentf/utils";
import {
isNum,
pascalCase,
sort,
clone,
isEql,
isEqlArr,
sleep,
} from '@opentf/utils';

isNum(NaN); //=> false

Expand All @@ -56,14 +64,20 @@ pascalCase('pascal case'); //=> PascalCase
sort([1, 10, 21, 2], 'desc'); //=> [ 21, 10, 2, 1 ]

const obj = {
a: 1,
b: 'abc',
c: new Map([['key', 'val']])
a: 1,
b: 'abc',
c: new Map([['key', 'val']]),
};
clone(obj); // It returns deeply cloned value.

const mapA = new Map([ ['a', 1], ['b', 2] ])
const mapB = new Map([ ['b', 2], ['a', 1] ])
const mapA = new Map([
['a', 1],
['b', 2],
]);
const mapB = new Map([
['b', 2],
['a', 1],
]);
isEql(mapA, mapB); //=> false

isEqlArr([1, 2, 3], [2, 3, 1]); //=> true
Expand All @@ -78,14 +92,21 @@ await sleep(1000); // It suspends the exection for 1 second.
- [arrChunk](https://js-utils.pages.dev/Array/arrChunk)
- [arrCompact](https://js-utils.pages.dev/Array/arrCompact)
- [arrDiff](https://js-utils.pages.dev/Array/arrDiff)
- [asyncFilter](https://js-utils.pages.dev/Array/asyncFilter)
- [arrIns](https://js-utils.pages.dev/Array/arrIns)
- [arrReplace](https://js-utils.pages.dev/Array/arrReplace)
- [arrRm](https://js-utils.pages.dev/Array/arrRm)
- [countBy](https://js-utils.pages.dev/Array/countBy)
- [groupBy](https://js-utils.pages.dev/Array/groupBy)
- [intersection](https://js-utils.pages.dev/Array/intersection)
- [range](https://js-utils.pages.dev/Array/range)
- [move](https://js-utils.pages.dev/Array/move)
- [sort](https://js-utils.pages.dev/Array/sort)
- [sortBy](https://js-utils.pages.dev/Array/sortBy)

## Async

- [asyncFilter](https://js-utils.pages.dev/Array/asyncFilter)

### Maths

- [percentage](https://js-utils.pages.dev/Maths/percentage)
Expand Down
36 changes: 36 additions & 0 deletions packages/utils/__tests__/array/countBy.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { countBy } from '../../src';

describe('Array', () => {
test('countBy', () => {
expect(countBy([])).toEqual({});
expect(
countBy([1, 2, 3, 4, 5, 6, 7, 8, 9], (v) =>
v % 2 === 0 ? 'Even' : 'Odd'
)
).toEqual({ Even: 4, Odd: 5 });

expect(countBy(['Apple', 'Mango', 'Orange'], 'length')).toEqual({
'5': 2,
'6': 1,
});

const inventory = [
{ name: 'asparagus', type: 'vegetables', qty: 5 },
{ name: 'bananas', type: 'fruit', qty: 0 },
{ name: 'goat', type: 'meat', qty: 23 },
{ name: 'cherries', type: 'fruit', qty: 5 },
{ name: 'fish', type: 'meat', qty: 22 },
];
expect(
countBy(inventory, ({ qty }) => (qty === 0 ? 'OutOfStock' : 'InStock'))
).toEqual({ OutOfStock: 1, InStock: 4 });

const letters = ['a', 'b', 'A', 'a', 'B', 'c'];

expect(countBy(letters, (l) => l.toLowerCase())).toEqual({
a: 3,
b: 2,
c: 1,
});
});
});
2 changes: 1 addition & 1 deletion packages/utils/src/array/arrDiff.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import isEql from '../common/isEql';
import isEql from '../assert/isEql';

/**
* It creates an array with the values, not included in the other array by deep comparison.
Expand Down
19 changes: 19 additions & 0 deletions packages/utils/src/array/countBy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import isFn from '../types/isFn';

/**
* Counts the items in the given array and groups them by the name provided.
*
* @example
*
* countBy([1, 2, 3, 4, 5], n => n % 2 === 0 ? 'Even' : 'Odd') //=> { Odd: 3, Even: 2 }
*/
export default function countBy<T>(
arr: T[] = [],
by: ((val: T) => string) | string
) {
return arr.reduce((acc: Record<string, number>, cur: T) => {
const k = isFn(by) ? by(cur) : (cur[by as keyof T] as string);
const count = acc[k] ? acc[k] + 1 : 1;
return { ...acc, [k]: count };
}, {});
}
4 changes: 4 additions & 0 deletions packages/utils/src/array/groupBy.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
export type GroupKey<T> = ((a: T) => string) | string;

/**
* It groups the elements of a given array according to the given key.
*
*/
export default function groupBy<T>(
arr: T[],
key: GroupKey<T>
Expand Down
File renamed without changes.
5 changes: 4 additions & 1 deletion packages/utils/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ export { default as strReplace } from './string/strReplace';
// Array
export { default as arrDiff } from './array/arrDiff';
export { default as range } from './array/range';
export { default as asyncFilter } from './array/asyncFilter';
export { default as groupBy } from './array/groupBy';
export { default as move } from './array/move';
export { default as sort } from './array/sort';
Expand All @@ -21,6 +20,10 @@ export { default as intersection } from './array/intersection';
export { default as arrIns } from './array/arrIns';
export { default as arrReplace } from './array/arrReplace';
export { default as arrRm } from './array/arrRm';
export { default as countBy } from './array/countBy';

// Async
export { default as asyncFilter } from './async/asyncFilter';

// Maths
export { default as percentage } from './maths/percentage';
Expand Down

0 comments on commit 4354b39

Please sign in to comment.