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(utils): countOccurrencesInArray 유틸 함수 추가 #159

Merged
merged 2 commits into from
May 23, 2024
Merged
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
5 changes: 5 additions & 0 deletions .changeset/blue-jeans-film.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@modern-kit/utils': minor
---

feat(utils): countOccurrencesInArray 유틸 함수 추가 - @ssi02014
34 changes: 34 additions & 0 deletions docs/docs/utils/array/countOccurrencesInArray.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# countOccurrencesInArray

입력한 배열에서 배열의 각 요소들이 등장한 횟수를 카운팅 해주는 유틸 함수입니다.

💡 단, `Object`, `Array`, `Set`, `Map`과 같은 객체 타입은 카운팅에서 제외되며, `null`, `NaN`, `undefined`는 카운팅에 포함됩니다.

<br />

## Code
[🔗 실제 구현 코드 확인](https://github.com/modern-agile-team/modern-kit/blob/main/packages/utils/src/array/countOccurrencesInArray/index.ts)

## Interface
```ts title="typescript"
const countOccurrencesInArray: <T extends readonly any[]>(
arr: T
) => Record<Exclude<T[number], object>, number>;
```

## Usage
```ts title="typescript"
import { countOccurrencesInArray } from '@modern-kit/utils';

const arr = [
'foo',
'foo',
'foo',
1,
1,
[1], // exclude
{ a: 1 }, // exclude
];

const countingObj = countOccurrencesInArray(arr); // { foo: 3, 1: 2 }
```
3 changes: 1 addition & 2 deletions docs/docs/utils/validator/isArray.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,14 @@

## Interface
```ts title="typescript"
const isArray: <T>(value: unknown) => value is T[]
const isArray: <T extends readonly any[]>(value: unknown) => value is T
```

## Usage
```ts title="typescript"
import { isArray } from '@modern-kit/utils';

isArray([]); // true

isArray(() => {}); // false
isArray('123'); // false
isArray(123); // false
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { countOccurrencesInArray } from '.';

describe('countOccurrencesInArray', () => {
it('should count occurrences of each value correctly', () => {
const testArray1 = [
'foo',
'foo',
'foo',
1,
1,
false,
false,
null,
null,
undefined,
NaN,
[1], // exclude
{ a: 1 }, // exclude
new Set(), // exclude
new Map(), // exclude
];

expect(countOccurrencesInArray(testArray1)).toEqual({
foo: 3,
1: 2,
false: 2,
null: 2,
undefined: 1,
NaN: 1,
});

const testArray2: string[] = [];

expect(countOccurrencesInArray(testArray2)).toEqual({});
});

it('should correctly type the result object based on input array types', () => {
const readonlyTestArray = [
'foo',
'foo',
'bar',
1,
2,
[1], // exclude
{ a: 1 }, // exclude
] as const;

expectTypeOf(countOccurrencesInArray(readonlyTestArray)).toEqualTypeOf<
Record<'foo' | 1 | 2 | 'bar', number>
>();

const defaultTestArray = [
'foo',
'foo',
'bar',
1,
2,
[1], // exclude
{ a: 1 }, // exclude
];

expectTypeOf(countOccurrencesInArray(defaultTestArray)).toEqualTypeOf<
Record<string | number, number>
>();
});
});
13 changes: 13 additions & 0 deletions packages/utils/src/array/countOccurrencesInArray/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
export const countOccurrencesInArray = <T extends readonly any[]>(
arr: T
): Record<Exclude<T[number], object>, number> => {
return arr.reduce((acc, cur) => {
if (typeof cur === 'object' && cur != null) {
return acc;
}

acc[cur] = (acc[cur] || 0) + 1;

return acc;
}, {});
};
1 change: 1 addition & 0 deletions packages/utils/src/array/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './countOccurrencesInArray';
1 change: 1 addition & 0 deletions packages/utils/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './array';
export * from './clipboard';
export * from './common';
export * from './device';
Expand Down
4 changes: 3 additions & 1 deletion packages/utils/src/validator/isArray/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
export const isArray = <T>(value: unknown): value is Array<T> => {
export const isArray = <T extends readonly any[]>(
value: unknown
): value is T => {
Comment on lines +1 to +3
Copy link
Contributor Author

@ssi02014 ssi02014 May 23, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

일반적인 배열 뿐만아니라 readonly 배열 까지 타입 추론을 원활하게 하기 위해 <T extends readonly any[]>로 변경하였습니다. 이를 통해 더욱 유연한 타입 지정을 할 수 있습니다! 🙏

return Array.isArray(value);
};
14 changes: 10 additions & 4 deletions packages/utils/src/validator/isArray/isArray.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,18 @@ describe('isArray', () => {
});

it('should narrow the type through if statements', () => {
const testValue = ['foo'] as string | string[];
const defaultTestArray = ['foo'] as string | string[];

if (isArray<string>(testValue)) {
expectTypeOf(testValue).toEqualTypeOf<string[]>();
if (isArray(defaultTestArray)) {
expectTypeOf(defaultTestArray).toEqualTypeOf<string[]>();
} else {
expectTypeOf(testValue).toEqualTypeOf<string>();
expectTypeOf(defaultTestArray).toEqualTypeOf<string>();
}

const readonlyTestArray = ['foo'] as const;

if (isArray(readonlyTestArray)) {
expectTypeOf(readonlyTestArray).toEqualTypeOf<readonly ['foo']>();
Comment on lines +20 to +23
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

기존대로라면 readonlyTestArray는 readonly ['foo'] & any[] 로 타입이 잡혀 타입 에러가 발생합니다.

}
});
});