feat(compat): implement range/rangeRight (#881)

* feat(compat): implement range/rangeRight

* make lint happy
This commit is contained in:
D-Sketon 2024-12-08 20:36:40 +08:00 committed by GitHub
parent 4cc2a895c1
commit 99f7611e4b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 339 additions and 0 deletions

View File

@ -1,8 +1,10 @@
import { bench, describe } from 'vitest';
import { range as rangeToolkit_ } from 'es-toolkit';
import { range as rangeCompatToolkit_ } from 'es-toolkit/compat';
import { range as rangeLodash_ } from 'lodash';
const rangeToolkit = rangeToolkit_;
const rangeCompatToolkit = rangeCompatToolkit_;
const rangeLodash = rangeLodash_;
describe('range', () => {
@ -10,6 +12,10 @@ describe('range', () => {
rangeToolkit(0, 100, 1);
});
bench('es-toolkit/compat/range', () => {
rangeCompatToolkit(0, 100, 1);
});
bench('lodash/range', () => {
rangeLodash(0, 100, 1);
});

View File

@ -1,8 +1,10 @@
import { bench, describe } from 'vitest';
import { rangeRight as rangeRightToolkit_ } from 'es-toolkit';
import { rangeRight as rangeRightCompatToolkiTt_ } from 'es-toolkit/compat';
import { rangeRight as rangeRightLodash_ } from 'lodash';
const rangeRightToolkit = rangeRightToolkit_;
const rangeRightCompatToolkit = rangeRightCompatToolkiTt_;
const rangeRightLodash = rangeRightLodash_;
describe('rangeRight', () => {
@ -10,6 +12,10 @@ describe('rangeRight', () => {
rangeRightToolkit(0, 100, 1);
});
bench('es-toolkit/compat/rangeRight', () => {
rangeRightCompatToolkit(0, 100, 1);
});
bench('lodash/rangeRight', () => {
rangeRightLodash(0, 100, 1);
});

View File

@ -104,6 +104,8 @@ export { max } from './math/max.ts';
export { min } from './math/min.ts';
export { parseInt } from './math/parseInt.ts';
export { random } from './math/random.ts';
export { range } from './math/range.ts';
export { rangeRight } from './math/rangeRight.ts';
export { round } from './math/round.ts';
export { sum } from './math/sum.ts';
export { sumBy } from './math/sumBy.ts';

View File

@ -0,0 +1,72 @@
import { describe, expect, it } from 'vitest';
import { each, map } from '..';
import { range } from './range';
import { falsey } from '../_internal/falsey';
describe('range', () => {
it(`\`_.range\` should infer the sign of \`step\` when only \`end\` is given`, () => {
expect(range(4)).toEqual([0, 1, 2, 3]);
expect(range(-4)).toEqual([0, -1, -2, -3]);
});
it(`\`_.range\` should infer the sign of \`step\` when only \`start\` and \`end\` are given`, () => {
expect(range(1, 5)).toEqual([1, 2, 3, 4]);
expect(range(5, 1)).toEqual([5, 4, 3, 2]);
});
it(`\`_.range\` should work with a \`start\`, \`end\`, and \`step\``, () => {
expect(range(0, -4, -1)).toEqual([0, -1, -2, -3]);
expect(range(5, 1, -1)).toEqual([5, 4, 3, 2]);
expect(range(0, 20, 5)).toEqual([0, 5, 10, 15]);
});
it(`\`_.range\` should support a \`step\` of \`0\``, () => {
expect(range(1, 4, 0)).toEqual([1, 1, 1]);
});
it(`\`_.range\` should work with a \`step\` larger than \`end\``, () => {
expect(range(1, 5, 20)).toEqual([1]);
});
it(`\`_.range\` should work with a negative \`step\``, () => {
expect(range(0, -4, -1)).toEqual([0, -1, -2, -3]);
expect(range(21, 10, -3)).toEqual([21, 18, 15, 12]);
});
it(`\`_.range\` should support \`start\` of \`-0\``, () => {
const actual = range(-0, 1);
expect(1 / actual[0]).toBe(-Infinity);
});
it(`\`_.range\` should treat falsey \`start\` as \`0\``, () => {
each(falsey, (value, index) => {
if (index) {
// @ts-expect-error - invalid arguments
expect(range(value)).toEqual([]);
// @ts-expect-error - invalid arguments
expect(range(value, 1)).toEqual([0]);
} else {
// @ts-expect-error - invalid arguments
expect(range()).toEqual([]);
}
});
});
it(`\`_.range\` should coerce arguments to finite numbers`, () => {
// @ts-expect-error - invalid arguments
const actual = [range('1'), range('0', 1), range(0, 1, '1'), range(NaN), range(NaN, NaN)];
expect(actual).toEqual([[0], [0], [0], [], []]);
});
it(`\`_.range\` should work as an iteratee for methods like \`_.map\``, () => {
const array = [1, 2, 3];
const object = { a: 1, b: 2, c: 3 };
const expected = [[0], [0, 1], [0, 1, 2]];
each([array, object], collection => {
const actual = map(collection, range);
expect(actual).toEqual(expected);
});
});
});

90
src/compat/math/range.ts Normal file
View File

@ -0,0 +1,90 @@
import { isIterateeCall } from '../_internal/isIterateeCall.ts';
import { toFinite } from '../util/toFinite.ts';
/**
* Returns an array of numbers from `0` (inclusive) to `end` (exclusive), incrementing by `1`.
*
* @param {number} end - The end number of the range (exclusive).
* @returns {number[]} An array of numbers from `0` (inclusive) to `end` (exclusive) with a step of `1`.
*
* @example
* // Returns [0, 1, 2, 3]
* range(4);
*/
export function range(end: number): number[];
/**
* Returns an array of numbers from `start` (inclusive) to `end` (exclusive), incrementing by `1`.
*
* @param {number} start - The starting number of the range (inclusive).
* @param {number} end - The end number of the range (exclusive).
* @returns {number[]} An array of numbers from `start` (inclusive) to `end` (exclusive) with a step of `1`.
*
* @example
* // Returns [1, 2, 3]
* range(1, 4);
*/
export function range(start: number, end: number): number[];
/**
* Returns an array of numbers from `start` (inclusive) to `end` (exclusive), incrementing by `step`.
*
* @param {number} start - The starting number of the range (inclusive).
* @param {number} end - The end number of the range (exclusive).
* @param {number} step - The step value for the range.
* @returns {number[]} An array of numbers from `start` (inclusive) to `end` (exclusive) with the specified `step`.
*
* @example
* // Returns [0, 5, 10, 15]
* range(0, 20, 5);
*/
export function range(start: number, end: number, step: number): number[];
/**
* Enables use as an iteratee for methods like `_.map`.
*
* @param {number} end - The current iteratee value.
* @param {PropertyKey} index - The iteration index.
* @param {object} guard - The iteratee object.
* @returns {number[]} An array of numbers from `start` (inclusive) to `end` (exclusive) with the specified `step`.
*/
export function range(end: number, index: PropertyKey, guard: object): number[];
/**
* Returns an array of numbers from `start` (inclusive) to `end` (exclusive), incrementing by `step`.
*
* @param {number} start - The starting number of the range (inclusive).
* @param {number} end - The end number of the range (exclusive).
* @param {number} step - The step value for the range.
* @returns {number[]} An array of numbers from `start` (inclusive) to `end` (exclusive) with the specified `step`.
*
* @example
* // Returns [0, 1, 2, 3]
* range(4);
*
* @example
* // Returns [0, -1, -2, -3]
* range(0, -4, -1);
*/
export function range(start: number, end?: PropertyKey, step?: any): number[] {
// Enables use as an iteratee for methods like `_.map`.
if (step && typeof step !== 'number' && isIterateeCall(start, end, step)) {
end = step = undefined;
}
start = toFinite(start);
if (end === undefined) {
end = start;
start = 0;
} else {
end = toFinite(end);
}
step = step === undefined ? (start < end ? 1 : -1) : toFinite(step);
const length = Math.max(Math.ceil((end - start) / (step || 1)), 0);
const result = new Array(length);
for (let index = 0; index < length; index++) {
result[index] = start;
start += step;
}
return result;
}

View File

@ -0,0 +1,72 @@
import { describe, expect, it } from 'vitest';
import { each, map } from '..';
import { rangeRight } from './rangeRight';
import { falsey } from '../_internal/falsey';
describe('rangeRight methods', () => {
it(`\`_.rangeRightRight\` should infer the sign of \`step\` when only \`end\` is given`, () => {
expect(rangeRight(4)).toEqual([0, 1, 2, 3].reverse());
expect(rangeRight(-4)).toEqual([0, -1, -2, -3].reverse());
});
it(`\`_.rangeRight\` should infer the sign of \`step\` when only \`start\` and \`end\` are given`, () => {
expect(rangeRight(1, 5)).toEqual([1, 2, 3, 4].reverse());
expect(rangeRight(5, 1)).toEqual([5, 4, 3, 2].reverse());
});
it(`\`_.rangeRight\` should work with a \`start\`, \`end\`, and \`step\``, () => {
expect(rangeRight(0, -4, -1)).toEqual([0, -1, -2, -3].reverse());
expect(rangeRight(5, 1, -1)).toEqual([5, 4, 3, 2].reverse());
expect(rangeRight(0, 20, 5)).toEqual([0, 5, 10, 15].reverse());
});
it(`\`_.rangeRight\` should support a \`step\` of \`0\``, () => {
expect(rangeRight(1, 4, 0)).toEqual([1, 1, 1].reverse());
});
it(`\`_.rangeRight\` should work with a \`step\` larger than \`end\``, () => {
expect(rangeRight(1, 5, 20)).toEqual([1]);
});
it(`\`_.rangeRight\` should work with a negative \`step\``, () => {
expect(rangeRight(0, -4, -1)).toEqual([0, -1, -2, -3].reverse());
expect(rangeRight(21, 10, -3)).toEqual([21, 18, 15, 12].reverse());
});
it(`\`_.rangeRight\` should support \`start\` of \`-0\``, () => {
const actual = rangeRight(-0, 1);
expect(1 / actual[0]).toBe(-Infinity);
});
it(`\`_.rangeRight\` should treat falsey \`start\` as \`0\``, () => {
each(falsey, (value, index) => {
if (index) {
// @ts-expect-error - invalid arguments
expect(rangeRight(value)).toEqual([]);
// @ts-expect-error - invalid arguments
expect(rangeRight(value, 1)).toEqual([0]);
} else {
// @ts-expect-error - invalid arguments
expect(rangeRight()).toEqual([]);
}
});
});
it(`\`_.rangeRight\` should coerce arguments to finite numbers`, () => {
// @ts-expect-error - invalid arguments
const actual = [rangeRight('1'), rangeRight('0', 1), rangeRight(0, 1, '1'), rangeRight(NaN), rangeRight(NaN, NaN)];
expect(actual).toEqual([[0], [0], [0], [], []]);
});
it(`\`_.rangeRight\` should work as an iteratee for methods like \`_.map\``, () => {
const array = [1, 2, 3];
const object = { a: 1, b: 2, c: 3 };
const expected = [[0], [0, 1].reverse(), [0, 1, 2].reverse()];
each([array, object], collection => {
const actual = map(collection, rangeRight);
expect(actual).toEqual(expected);
});
});
});

View File

@ -0,0 +1,91 @@
import { isIterateeCall } from '../_internal/isIterateeCall.ts';
import { toFinite } from '../util/toFinite.ts';
/**
* Returns an array of numbers from `end` (exclusive) to `0` (inclusive), decrementing by `1`.
*
* @param {number} end - The end number of the range (exclusive).
* @returns {number[]} An array of numbers from `end` (exclusive) to `0` (inclusive) with a step of `1`.
*
* @example
* // Returns [3, 2, 1, 0]
* rangeRight(4);
*/
export function rangeRight(end: number): number[];
/**
* Returns an array of numbers from `end` (exclusive) to `start` (inclusive), decrementing by `1`.
*
* @param {number} start - The starting number of the range (inclusive).
* @param {number} end - The end number of the range (exclusive).
* @returns {number[]} An array of numbers from `end` (exclusive) to `start` (inclusive) with a step of `1`.
*
* @example
* // Returns [3, 2, 1]
* rangeRight(1, 4);
*/
export function rangeRight(start: number, end: number): number[];
/**
* Returns an array of numbers from `end` (exclusive) to `start` (inclusive), decrementing by `step`.
*
* @param {number} start - The starting number of the range (inclusive).
* @param {number} end - The end number of the range (exclusive).
* @param {number} step - The step value for the range.
* @returns {number[]} An array of numbers from `end` (exclusive) to `start` (inclusive) with the specified `step`.
*
* @example
* // Returns [15, 10, 5, 0]
* rangeRight(0, 20, 5);
*/
export function rangeRight(start: number, end: number, step: number): number[];
/**
* Enables use as an iteratee for methods like `_.map`.
*
* @param {number} end - The current iteratee value.
* @param {PropertyKey} index - The iteration index.
* @param {object} guard - The iteratee object.
* @returns {number[]} An array of numbers from `start` (inclusive) to `end` (exclusive) with the specified `step`.
*/
export function rangeRight(end: number, index: PropertyKey, guard: object): number[];
/**
* Returns an array of numbers from `end` (exclusive) to `start` (inclusive), decrementing by `step`.
*
* @param {number} start - The starting number of the range (inclusive).
* @param {number} end - The end number of the range (exclusive).
* @param {number} step - The step value for the range.
* @returns {number[]} An array of numbers from `end` (exclusive) to `start` (inclusive) with the specified `step`.
* @throws {Error} Throws an error if the step value is not a non-zero integer.
*
* @example
* // Returns [3, 2, 1, 0]
* rangeRight(4);
*
* @example
* // Returns [-3, -2, -1, 0]
* rangeRight(0, -4, -1);
*/
export function rangeRight(start: number, end?: PropertyKey, step?: any): number[] {
// Enables use as an iteratee for methods like `_.map`.
if (step && typeof step !== 'number' && isIterateeCall(start, end, step)) {
end = step = undefined;
}
start = toFinite(start);
if (end === undefined) {
end = start;
start = 0;
} else {
end = toFinite(end);
}
step = step === undefined ? (start < end ? 1 : -1) : toFinite(step);
const length = Math.max(Math.ceil((end - start) / (step || 1)), 0);
const result = new Array(length);
for (let index = length - 1; index >= 0; index--) {
result[index] = start;
start += step;
}
return result;
}