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(flatten): add flatten #147

Merged
merged 8 commits into from
Jul 10, 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
27 changes: 27 additions & 0 deletions benchmarks/flatten.bench.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { bench, describe } from 'vitest';
import { flatten as flattenToolkit } from 'es-toolkit';
import { flattenDepth as flattenDepthLodash } from 'lodash';

const createNestedArray = (values: any[]) => {
if (values.length === 0) {
return [];
}
const [first, ...rest] = values;
return [first, createNestedArray(rest)];
};

describe('flatten', () => {
const arr = createNestedArray(Array.from({ length: 30 }, (_, index) => index));

bench('es-toolkit/flatten', () => {
flattenToolkit(arr, 30);
});

bench('lodash/flattenDepth', () => {
flattenDepthLodash(arr, 30);
});

bench('js built-in/flat', () => {
arr.flat(30);
});
});
1 change: 1 addition & 0 deletions docs/.vitepress/en.mts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ function sidebar(): DefaultTheme.Sidebar {
{ text: 'dropRight', link: '/reference/array/dropRight' },
{ text: 'dropRightWhile', link: '/reference/array/dropRightWhile' },
{ text: 'fill', link: '/reference/array/fill' },
{ text: 'flatten', link: '/reference/array/flatten' },
{ text: 'forEachRight', link: '/reference/array/forEachRight' },
{ text: 'groupBy', link: '/reference/array/groupBy' },
{ text: 'intersection', link: '/reference/array/intersection' },
Expand Down
1 change: 1 addition & 0 deletions docs/.vitepress/ko.mts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ function sidebar(): DefaultTheme.Sidebar {
{ text: 'dropRight', link: '/ko/reference/array/dropRight' },
{ text: 'dropRightWhile', link: '/ko/reference/array/dropRightWhile' },
{ text: 'fill', link: '/ko/reference/array/fill' },
{ text: 'flatten', link: '/ko/reference/array/flatten' },
{ text: 'forEachRight', link: '/reference/array/forEachRight' },
{ text: 'groupBy', link: '/ko/reference/array/groupBy' },
{ text: 'intersection', link: '/ko/reference/array/intersection' },
Expand Down
35 changes: 35 additions & 0 deletions docs/ko/reference/array/flatten.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# flatten

중첩된 배열을 원하는 깊이까지 풀어서 평탄화해요.

JavaScript 언어에 포함된 [Array#flat](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/flat)과 동일하게 동작하지만, 더 빨라요.

## 인터페이스

```typescript
function flatten<T, D extends number = 1>(arr: T[], depth?: D): Array<FlatArray<T[], D>>;
```

### 파라미터

- `arr` (`T[]`): 평탄화할 중첩 배열이에요.
- `depth` (`D`): 평탄화할 깊이에요. 기본값은 1이에요.

### 반환 값

(`Array<FlatArray<T[], D>>`): 원하는 깊이까지 평탄해진 새로운 배열이에요.

## 예시

```typescript
const originArr = [1, [2, 3], [4, [5, 6]]];

const array1 = flatten(originArr);
// [1, 2, 3, 4, [5, 6]]를 반환해요.

const array2 = flatten(originArr, 1);
// [1, 2, 3, 4, [5, 6]]를 반환해요.

const array3 = flatten(originArr, 2);
// [1, 2, 3, 4, 5, 6]를 반환해요.
```
35 changes: 35 additions & 0 deletions docs/reference/array/flatten.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# flatten

Flattens the nested array given as an argument to the desired depth.

It works the same as [Array.prototype.flat](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/flat) provided by default in JavaScript and returns the same type. However, its performance is superior.

## Signature

```typescript
function flatten<T, D extends number = 1>(arr: T[], depth?: D): Array<FlatArray<T[], D>>;
```

### Parameters

- `arr` (`T[]`): The array to flatten.
- `depth` (`D`): The depth to flatten, which defaults to 1.

### Returns

(`Array<FlatArray<T[], D>>`) A new array that has been flattened.

## Examples

```typescript
const originArr = [1, [2, 3], [4, [5, 6]]];

const array1 = flatten(originArr);
// Return [1, 2, 3, 4, [5, 6]]

const array2 = flatten(originArr, 1);
// Return [1, 2, 3, 4, [5, 6]]

const array3 = flatten(originArr, 2);
// Return [1, 2, 3, 4, 5, 6]
```
63 changes: 63 additions & 0 deletions src/array/flatten.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { describe, expect, it } from 'vitest';
import { flatten } from '.';

describe('flatten', () => {
const originArr = [1, [2, [3, [4]]]];

it('should flatten a array to the default depth of 1', () => {
const expectedArr = [1, 2, [3, [4]]];

expect(flatten(originArr)).toEqual(expectedArr);
expect(originArr.flat()).toEqual(expectedArr);
});

it('should flatten a deeply nested array to the specified depth', () => {
const expectedArr1 = [1, 2, [3, [4]]];
expect(flatten(originArr, 1)).toEqual(expectedArr1);
expect(originArr.flat(1)).toEqual(expectedArr1);

const expectedArr2 = [1, 2, 3, [4]];
expect(flatten(originArr, 2)).toEqual(expectedArr2);
expect(originArr.flat(2)).toEqual(expectedArr2);

const expectedArr3 = [1, 2, 3, 4];
expect(flatten(originArr, 3)).toEqual(expectedArr3);
expect(originArr.flat(3)).toEqual(expectedArr3);

expect(flatten(originArr, Infinity)).toEqual(expectedArr3);
expect(originArr.flat(Infinity)).toEqual(expectedArr3);
});

it('should return the same array if depth is 0 or NaN or negative', () => {
const expectedArr = [1, [2, [3, [4]]]];

expect(flatten(originArr, 0)).toEqual(expectedArr);
expect(originArr.flat(0)).toEqual(expectedArr);

expect(flatten(originArr, NaN)).toEqual(expectedArr);
expect(originArr.flat(NaN)).toEqual(expectedArr);

expect(flatten(originArr, -1)).toEqual(expectedArr);
expect(originArr.flat(-1)).toEqual(expectedArr);
});

it('should flatten arrays to the specified depth considering floating point values', () => {
const expectedArr1 = [1, 2, [3, [4]]];
expect(flatten(originArr, 1.3)).toEqual(expectedArr1);
expect(originArr.flat(1.3)).toEqual(expectedArr1);

const expectedArr2 = [1, 2, 3, [4]];
expect(flatten(originArr, 2.5)).toEqual(expectedArr2);
expect(originArr.flat(2.5)).toEqual(expectedArr2);

const expectedArr3 = [1, 2, 3, 4];
expect(flatten(originArr, 3.9)).toEqual(expectedArr3);
expect(originArr.flat(3.9)).toEqual(expectedArr3);
});

it('should handle empty array', () => {
const originArr: number[] = [];

expect(flatten(originArr, 2)).toEqual([]);
});
});
33 changes: 33 additions & 0 deletions src/array/flatten.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/**
* Flattens an array up to the specified depth.
*
* @template T - The type of elements within the array.
* @template D - The depth to which the array should be flattened.
* @param {T[]} arr - The array to flatten.
* @param {D} depth - The depth level specifying how deep a nested array structure should be flattened. Defaults to 1.
* @returns {Array<FlatArray<T[], D>>} A new array that has been flattened.
*
* @example
* const arr = flatten([1, [2, 3], [4, [5, 6]]], 1);
* // Returns: [1, 2, 3, 4, [5, 6]]
*
* const arr = flatten([1, [2, 3], [4, [5, 6]]], 2);
* // Returns: [1, 2, 3, 4, 5, 6]
*/
export function flatten<T, D extends number = 1>(arr: readonly T[], depth = 1 as D): Array<FlatArray<T[], D>> {
const result: Array<FlatArray<T[], D>> = [];
const flooredDepth = Math.floor(depth);

const recursive = (arr: readonly T[], currentDepth: number) => {
for (const item of arr) {
if (Array.isArray(item) && currentDepth < flooredDepth) {
recursive(item, currentDepth + 1);
} else {
result.push(item as FlatArray<T[], D>);
}
}
};

recursive(arr, 0);
return result;
}
1 change: 1 addition & 0 deletions src/array/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export { dropRight } from './dropRight.ts';
export { dropRightWhile } from './dropRightWhile.ts';
export { dropWhile } from './dropWhile.ts';
export { fill } from './fill.ts';
export { flatten } from './flatten.ts';
export { forEachRight } from './forEachRight.ts';
export { groupBy } from './groupBy.ts';
export { intersection } from './intersection.ts';
Expand Down