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(differenceBy): Update differenceBy function to handle array-like objects and filter out invalid values #790

Merged
merged 3 commits into from
Nov 9, 2024
Merged
Show file tree
Hide file tree
Changes from 2 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
39 changes: 39 additions & 0 deletions src/compat/_internal/flattenArrayLike.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { describe, expect, it } from 'vitest';
import { flattenArrayLike } from './flattenArrayLike';

describe('flattenArrayLike', () => {
it('should flatten an array of array-like objects', () => {
const input: Array<ArrayLike<string>> = [
{ length: 3, 0: 'a', 1: 'b', 2: 'c' },
{ length: 2, 0: 'd', 1: 'e' },
{ length: 0 },
];
const expectedOutput = ['a', 'b', 'c', 'd', 'e'];
expect(flattenArrayLike(input)).toEqual(expectedOutput);
});

it('should ignore non-array-like objects', () => {
const input: any[] = [{ length: 2, 0: 'x', 1: 'y' }, 3, { length: 1, 0: 'z' }];
const expectedOutput = ['x', 'y', 'z'];
expect(flattenArrayLike(input)).toEqual(expectedOutput);
});

it('should return an empty array when input is empty', () => {
const input: Array<ArrayLike<any>> = [];
const expectedOutput: any[] = [];
expect(flattenArrayLike(input)).toEqual(expectedOutput);
});

it('should handle nested array-like objects', () => {
const input: Array<ArrayLike<number[]>> = [
{ length: 2, 0: [1, 2], 1: [3, 4] },
{ length: 1, 0: [5, 6] },
];
const expectedOutput = [
[1, 2],
[3, 4],
[5, 6],
];
expect(flattenArrayLike(input)).toEqual(expectedOutput);
});
});
19 changes: 19 additions & 0 deletions src/compat/_internal/flattenArrayLike.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { isArrayLikeObject } from '../predicate/isArrayLikeObject.ts';

export function flattenArrayLike<T>(values: Array<ArrayLike<T>>): T[] {
const result: T[] = [];

for (let i = 0; i < values.length; i++) {
const arrayLike = values[i];

if (!isArrayLikeObject(arrayLike)) {
continue;
}

for (let j = 0; j < arrayLike.length; j++) {
result.push(arrayLike[j]);
}
}

return result;
}
97 changes: 97 additions & 0 deletions src/compat/array/differenceBy.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import { describe, expect, it } from 'vitest';
import { differenceBy } from './differenceBy';
import { range } from '../../math';
import { args } from '../_internal/args';
import { LARGE_ARRAY_SIZE } from '../_internal/LARGE_ARRAY_SIZE';
import { slice } from '../_internal/slice';

describe('differenceBy', () => {
Expand All @@ -25,4 +28,98 @@ describe('differenceBy', () => {
const actual = differenceBy([2, 1, 2, 3], [3, 4], [3, 2]);
expect(actual).toEqual([1]);
});

/**
* @see https://github.com/lodash/lodash/blob/6a2cc1dfcf7634fea70d1bc5bd22db453df67b42/test/difference-methods.spec.js#L1
*/

it(`should return the difference of two arrays`, () => {
const actual = differenceBy([2, 1], [2, 3]);
expect(actual).toEqual([1]);
});

it(`should return the difference of multiple arrays`, () => {
const actual = differenceBy([2, 1, 2, 3], [3, 4], [3, 2]);
expect(actual).toEqual([1]);
});

it(`should treat \`-0\` as \`0\``, () => {
const array = [-0, 0];

const actual = array.map(value => differenceBy(array, [value]));

expect(actual).toEqual([[], []]);

expect(differenceBy([-0, 1], [1])).toEqual([-0]);
});

it(`should match \`NaN\``, () => {
expect(differenceBy([1, NaN, 3], [NaN, 5, NaN])).toEqual([1, 3]);
});

it(`should work with large arrays`, () => {
const array1: unknown[] = range(LARGE_ARRAY_SIZE + 1);
const array2: unknown[] = range(LARGE_ARRAY_SIZE);

const a = {};
const b = {};
const c = {};

array1.push(a, b, c);
array2.push(b, c, a);

expect(differenceBy(array1, array2)).toEqual([LARGE_ARRAY_SIZE]);
});

it(`should work with large arrays of \`-0\` as \`0\``, () => {
const array = [-0, 0];

const actual = array.map(value => {
const largeArray = Array.from({ length: LARGE_ARRAY_SIZE }).map(() => value);

return differenceBy(array, largeArray);
});

expect(actual).toEqual([[], []]);

const largeArray = Array.from({ length: LARGE_ARRAY_SIZE }).map(() => 1);
expect(differenceBy([-0, 1], largeArray)).toEqual([-0]);
});

it(`should work with large arrays of \`NaN\``, () => {
const largeArray = Array.from({ length: LARGE_ARRAY_SIZE }).map(() => NaN);
expect(differenceBy([1, NaN, 3], largeArray)).toEqual([1, 3]);
});

it(`should work with large arrays of objects`, () => {
const object1 = {};
const object2 = {};
const largeArray = Array.from({ length: LARGE_ARRAY_SIZE }).map(() => ({}));

expect(differenceBy([object1, object2], largeArray)).toEqual([object1, object2]);
});

it(`should work with \`arguments\` objects`, () => {
const array = [0, 1, null, 3];

expect(differenceBy(array, args)).toEqual([0, null]);
expect(differenceBy(args, array)).toEqual([2]);
});

it('should work with arrayLike objects', () => {
const array = { 0: 1, 1: 2, length: 2 };

expect(differenceBy(array, [2, 3])).toEqual([1]);
expect(differenceBy([1, 2, 3], array)).toEqual([3]);
expect(differenceBy([1, 2, 3], array, value => value)).toEqual([3]);
});

it('should return an empty array when the first array is not array-like object', () => {
expect(differenceBy('23', ['2', '3'])).toEqual([]);
});

it('should filter out values that are not arrays or array-like objects', () => {
expect(differenceBy(['2', '3'], '2', ['3'])).toEqual(['2']);
expect(differenceBy(['2', '3'], '2', ['3'], value => value)).toEqual(['2']);
});
});
10 changes: 6 additions & 4 deletions src/compat/array/differenceBy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { flatten } from './flatten.ts';
import { last } from './last.ts';
import { difference as differenceToolkit } from '../../array/difference.ts';
import { differenceBy as differenceByToolkit } from '../../array/differenceBy.ts';
import { flattenArrayLike } from '../_internal/flattenArrayLike.ts';
import { isArrayLikeObject } from '../predicate/isArrayLikeObject.ts';
import { iteratee as createIteratee } from '../util/iteratee.ts';

Expand Down Expand Up @@ -121,16 +122,17 @@ export function differenceBy<T>(array: ArrayLike<T> | null | undefined, ...value
* @param {...any[]} values - Multiple arrays containing elements to be excluded from the primary array.
* @returns {T[]} A new array containing the elements that are present in the primary array but not in the values arrays.
*/
export function differenceBy<T>(arr: ArrayLike<T> | null | undefined, ...values: any[]): T[] {
export function differenceBy<T>(arr: ArrayLike<T> | null | undefined, ..._values: any[]): T[] {
if (!isArrayLikeObject(arr)) {
return [];
}

const iteratee = last(values);
const iteratee = last(_values);
const values = flattenArrayLike<T>(_values);

if (isArrayLikeObject(iteratee)) {
return differenceToolkit(Array.from(arr), flatten(values));
return differenceToolkit(Array.from(arr), values);
}

return differenceByToolkit(Array.from(arr), flatten(values.slice(0, -1)), createIteratee(iteratee));
return differenceByToolkit(Array.from(arr), values, createIteratee(iteratee));
}
Loading