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

fix(utils): isInRange, range 로직 개선 및 한글화 #554

Merged
merged 1 commit into from
Oct 31, 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/brown-ants-pretend.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@modern-kit/utils': patch
---

fix(utils): isInRange, range 로직 개선 - @ssi02014
46 changes: 25 additions & 21 deletions docs/docs/utils/validator/isInRange.md
Original file line number Diff line number Diff line change
@@ -1,38 +1,43 @@
# isInRange

대상 값이 주어진 범위 내에 있는지 확인합니다.
주어진 value가 `min`과 `max`로 지정된 범위 내에 있는지 여부를 판단합니다.

인자로 equalOptions 객체를 받아, 범위의 양 끝을 포함할지 여부를 설정할 수 있습니다.

equalOptions의 기본값은 min값은 true, max값은 false로 min값은 포함, max값은 포함하지 않습니다.
`inclusiveMin`/`inclusiveMax`를 통해 경계 값을 포함할지 여부를 설정할 수 있습니다. 기본적으로 최소값은 포함하며 최대값은 포함하지 않습니다.

<br />

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

## Benchmark
- `hz`: 초당 작업 수
- `mean`: 평균 응답 시간(ms)

|이름|hz|mean|성능|
|------|---|---|---|
|modern-kit/isInRange|24,435,490.55|0.0000|`fastest`|
|lodash/inRange|9,373,021.30|0.0001|`slowest`|

- **modern-kit/isInRange**
- `2.61x` faster than lodash/inRange

## Interface
```ts title="typescript"

interface IsInRangeProps {
value: number;
min: number;
max: number;
equalOptions?: {
min?: boolean;
max?: boolean;
};
inclusiveMin?: boolean;
inclusiveMax?: boolean;
}

const isInRange = ({
function isInRange({
value,
min,
max,
equalOptions = {
min: true,
max: false,
},
}: IsInRangeProps) => boolean;
inclusiveMin,
inclusiveMax,
}: IsInRangeProps): boolean;
```

## Usage
Expand All @@ -41,11 +46,10 @@ import { isInRange } from '@modern-kit/utils';

isInRange({ value: 5, min: 0, max: 10 }) // true
isInRange({ value: 0, min: 0, max: 10 }) // true
isInRange({ value: 10, min: 0, max: 10, equalOptions: { max: true } }) // true
isInRange({ value: 10, min: 0, max: 10, equalOptions: { min: false, max: true } }) // true
isInRange({ value: 10, min: 0, max: 10, equalOptions: { min: true, max: true } }) // true
isInRange({ value: 10, min: 0, max: 10, inclusiveMin: true }) // true
isInRange({ value: 10, min: 0, max: 10, inclusiveMin: false, inclusiveMax: true }) // true
isInRange({ value: 10, min: 0, max: 10, inclusiveMin: true, inclusiveMax: true }) // true

isInRange({ value: 5, max: 10, equalOptions: { max: true } }) // Error('min값은 필수입니다.')
isInRange({ value: 5, min: 10, equalOptions: { max: true } }) // Error('max값은 필수입니다.')
isInRange({ value: 5, min: 10, max: 0, equalOptions: { max: true } }) // Error('min은 max보다 작아야합니다.')
// Error
isInRange({ value: 5, min: 10, max: 0 }) // Error('min은 max보다 작아야합니다.')
```
27 changes: 21 additions & 6 deletions packages/utils/src/math/range/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,35 @@ function getRangeValue(start: number, end: number, step: number = 1) {
return result;
}

/**
* @description 시작 값(start)에서 종료 값(end)까지 주어진 간격(step)에 따라 숫자의 배열을 생성합니다.
*
* @param {number} start - 생성할 숫자 범위의 시작 값입니다.
* @param {number} end - 생성할 숫자 범위의 종료 값입니다.
* @param {number} [step] - 숫자의 증가 간격입니다.
* @returns {number[]} 시작 값에서 종료 값까지 간격에 따라 생성된 숫자의 배열을 반환합니다.
*
* @throws {Error} `step`이 0이거나 정수가 아닌 경우 오류가 발생합니다.
*
* @example
* range(5); // [0, 1, 2, 3, 4]
*
* @example
* range(1, 5); // [1, 2, 3, 4]
*
* @example
* range(1, 10, 2); // [1, 3, 5, 7, 9]
*/
export function range(end: number): number[];
export function range(start: number, end: number): number[];
export function range(start: number, end: number, step?: number): number[];
export function range(start: number, end?: number, step?: number) {
export function range(start: number, end?: number, step: number = 1) {
if (isNil(end)) {
return getRangeValue(0, start);
}

if (isNil(step)) {
step = 1;
}

if (!Number.isInteger(step) || step === 0) {
throw new Error('The step value must be a non-zero integer.');
throw new Error('step은 0이 아닌 정수여야 합니다.');
}

return getRangeValue(start, end, step);
Expand Down
34 changes: 14 additions & 20 deletions packages/utils/src/math/range/range.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,51 +2,45 @@ import { describe, it, expect } from 'vitest';
import { range } from '.';

describe('range', () => {
it('should return an array of numbers from 1 to the given number when one argument is provided', () => {
it('하나의 인자가 제공될 때 0부터 해당 숫자까지 1을 간격으로 구성된 숫자 배열을 반환해야 합니다.', () => {
const result = range(5);

expect(result).toEqual([0, 1, 2, 3, 4]);
});

it('should return an array of numbers from the first argument to the second argument when two arguments are provided', () => {
it('start, end 두 인자가 모두 제공될 때 start부터 end까지 1을 간격으로 구성된 숫자 배열을 반환해야 합니다.', () => {
const result = range(1, 5);

expect(result).toEqual([1, 2, 3, 4]);
});

it('should return an array of numbers from the first argument to the second argument when minus two arguments are provided', () => {
it('step이 주어질 때 start 값부터 end 값까지 step 값을 간격으로 구성된 숫자 배열을 반환해야 합니다.', () => {
const result = range(1, 10, 2);

expect(result).toEqual([1, 3, 5, 7, 9]);
});

it('음수로 두 개의 인자가 제공될 때 첫 번째 인자부터 두 번째 인자까지 1을 간격으로 구성된 숫자 배열을 반환해야 합니다.', () => {
const result = range(-10, -5);

expect(result).toEqual([-10, -9, -8, -7, -6]);
});

it('should return an empty array when second argument is bigger than first argument', () => {
it('end 인자가 start 인자보다 클 경우 빈 배열을 반환해야 합니다.', () => {
const result = range(10, 5, 1);

expect(result).toEqual([]);
});

it('should return an correct array when second argument is bigger than first argument with step', () => {
const result2 = range(10, 5, -1);

expect(result2).toEqual([10, 9, 8, 7, 6]);
});

it('should return an error if step is zero', () => {
it('step이 0일 경우 오류를 반환해야 합니다.', () => {
expect(() => range(1, 5, 0)).toThrowError(
'The step value must be a non-zero integer.'
'step은 0이 아닌 정수여야 합니다.'
);
});

it('should return an error if step is not integer', () => {
it('step이 정수가 아닐 경우 오류를 반환해야 합니다.', () => {
expect(() => range(1, 5, 1.1)).toThrowError(
'The step value must be a non-zero integer.'
'step은 0이 아닌 정수여야 합니다.'
);
});

it('should return an array of numbers from 1 to 10 with a step of 2', () => {
const result = range(1, 10, 2);

expect(result).toEqual([1, 3, 5, 7, 9]);
});
});
56 changes: 20 additions & 36 deletions packages/utils/src/validator/isInRange/index.ts
Original file line number Diff line number Diff line change
@@ -1,62 +1,46 @@
import { isNil } from '../../validator';

interface IsInRangeProps {
value: number;
min: number;
max: number;
equalOptions?: {
min?: boolean;
max?: boolean;
};
inclusiveMin?: boolean;
inclusiveMax?: boolean;
}

/**
* @description 값이 지정된 범위 내에 있는지 확인합니다.
* @description 주어진 value가 `min`과 `max`로 지정된 범위 내에 있는지 여부를 판단합니다.
*
* 이 함수는 주어진 값이 `min`과 `max`로 지정된 범위 내에 있는지 여부를 판단합니다.
* `min`과 `max`가 유효한 값이어야 하며, `min`이 `max`보다 클 수 없습니다.
* `equalOptions`를 통해 경계 값을 포함할지 여부를 설정할 수 있습니다.
* `inclusiveMin`/`inclusiveMax`를 통해 경계 값을 포함할지 여부를 설정할 수 있습니다. 기본적으로 최소값은 포함하며 최대값은 포함하지 않습니다.
*
* @param {{
* value: number;
* min: number;
* max: number;
* equalOptions?: {
* min?: boolean;
* max?: boolean;
* };
* }}
* - `value`: 확인할 값.
* - `min`: 값이 포함되어야 하는 최소값.
* - `max`: 값이 포함되어야 하는 최대값.
* - `equalOptions.min`: `true`일 경우 최소값을 포함합니다.
* - `equalOptions.max`: `true`일 경우 최대값을 포함합니다.
* @param {IsInRangeProps} - isInRange 함수의 옵션 객체입니다.
* @property {number} value - 확인할 값.
* @property {number} min - 값이 포함되어야 하는 최소값.
* @property {number} max - 값이 포함되어야 하는 최대값.
* @property {boolean} inclusiveMin - `true`일 경우 최소값을 포함합니다. 기본 값은 true 입니다.
* @property {boolean} inclusiveMax - `true`일 경우 최대값을 포함합니다. 기본 값은 false 입니다.
* @returns {boolean} - 값이 지정된 범위 내에 있으면 `true`, 그렇지 않으면 `false`를 반환합니다.
*
* @throws {Error} - `min` 또는 `max` 값이 유효하지 않거나, `min`이 `max`보다 큰 경우 오류가 발생합니다.
*
* @example
* isInRange({ value: 5, min: 1, max: 10 }); // true
* isInRange({ value: 10, min: 1, max: 10 }); // false
*
* @example
* isInRange({ value: 10, min: 1, max: 10, inclusiveMax: true }); // true
*/
export function isInRange({
value,
min,
max,
equalOptions = {},
inclusiveMin = true,
inclusiveMax = false,
}: IsInRangeProps): boolean {
if (isNil(min) || isNil(max)) {
throw new Error('min and max values are invalid.');
Comment on lines -48 to -49
Copy link
Contributor Author

Choose a reason for hiding this comment

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

타입스크립트로 작성되어 min, max 값을 검증하는 것은 불필요해 보여 제거합니다!

if (min >= max) {
throw new Error('최소값은 최대값보다 크거나 같은 수 없습니다.');
}

if (min > max) {
throw new Error('min value cannot be greater than the max value.');
}

const { min: minEqual = true, max: maxEqual = false } = equalOptions;

const isWithinMin = minEqual ? value >= min : value > min;
const isWithinMax = maxEqual ? value <= max : value < max;
const isInclusiveMin = inclusiveMin ? value >= min : value > min;
const isInclusiveMax = inclusiveMax ? value <= max : value < max;

return isWithinMin && isWithinMax;
return isInclusiveMin && isInclusiveMax;
}
13 changes: 13 additions & 0 deletions packages/utils/src/validator/isInRange/isInRange.bench.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { bench, describe } from 'vitest';
import { inRange as inRangeLodash } from 'lodash-es';
import { isInRange } from '.';

describe('isInRange', () => {
bench('@modern-kit/isInRange', () => {
isInRange({ value: 10, min: 0, max: 10 });
});

bench('lodash/inRange', () => {
inRangeLodash(10, 0, 10);
});
});
Loading