diff --git a/benchmarks/performance/range.bench.ts b/benchmarks/performance/range.bench.ts index 84ba6415..7c16d03e 100644 --- a/benchmarks/performance/range.bench.ts +++ b/benchmarks/performance/range.bench.ts @@ -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); }); diff --git a/benchmarks/performance/rangeRight.bench.ts b/benchmarks/performance/rangeRight.bench.ts index 1289c208..1e392d72 100644 --- a/benchmarks/performance/rangeRight.bench.ts +++ b/benchmarks/performance/rangeRight.bench.ts @@ -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); }); diff --git a/src/compat/index.ts b/src/compat/index.ts index 3695970b..5a943aef 100644 --- a/src/compat/index.ts +++ b/src/compat/index.ts @@ -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'; diff --git a/src/compat/math/range.spec.ts b/src/compat/math/range.spec.ts new file mode 100644 index 00000000..7c1f27ac --- /dev/null +++ b/src/compat/math/range.spec.ts @@ -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); + }); + }); +}); diff --git a/src/compat/math/range.ts b/src/compat/math/range.ts new file mode 100644 index 00000000..9b64ae04 --- /dev/null +++ b/src/compat/math/range.ts @@ -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; +} diff --git a/src/compat/math/rangeRight.spec.ts b/src/compat/math/rangeRight.spec.ts new file mode 100644 index 00000000..7f1a1929 --- /dev/null +++ b/src/compat/math/rangeRight.spec.ts @@ -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); + }); + }); +}); diff --git a/src/compat/math/rangeRight.ts b/src/compat/math/rangeRight.ts new file mode 100644 index 00000000..75f00090 --- /dev/null +++ b/src/compat/math/rangeRight.ts @@ -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; +}