Skip to content

Commit

Permalink
feat(micro-dash): add partition()
Browse files Browse the repository at this point in the history
Closes #25
  • Loading branch information
ersimont committed Jul 14, 2021
1 parent 5c215da commit 14417df
Show file tree
Hide file tree
Showing 6 changed files with 332 additions and 17 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import partition from 'lodash-es/partition';

console.log(
partition([1], () => {}),
partition({ a: 1 }, () => {}),
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { partition } from '@s-libs/micro-dash';

console.log(
partition([1], () => {}),
partition({ a: 1 }, () => {}),
);
1 change: 1 addition & 0 deletions projects/micro-dash/src/lib/collection/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export { forEachRight } from './for-each-right';
export { includes } from './includes';
export { keyBy } from './key-by';
export { map } from './map';
export { partition } from './partition';
export { reduce } from './reduce';
export { reject } from './reject';
export { reduceRight } from './reduce-right';
Expand Down
265 changes: 265 additions & 0 deletions projects/micro-dash/src/lib/collection/partition.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,265 @@
import { expectCallsAndReset } from '@s-libs/ng-dev';
import { expectTypeOf } from 'expect-type';
import {
identity,
isDate,
isMap,
isString,
stubFalse,
stubTrue,
} from 'lodash-es';
import {
isA,
isDateOrString,
isMapOrString,
isNumberOrString,
isStringOr2,
} from '../../test-helpers/test-utils';
import { partition } from './partition';

describe('partition', () => {
it('can handle null/undefined collections', () => {
expect(partition(null, identity)).toEqual([[], []]);
expect(partition(undefined, identity)).toEqual([[], []]);
});

it('has fancy typing for arrays', () => {
expect().nothing();

type A = Array<string | number>;
const a = [] as A;
const aOrU = a as A | undefined;
const aOrN = a as A | null;

expectTypeOf(partition(a, () => true)).toEqualTypeOf<
[Array<string | number>, Array<string | number>]
>();
expectTypeOf(partition(aOrU, () => true)).toEqualTypeOf<
[Array<string | number>, Array<string | number>]
>();
expectTypeOf(partition(aOrN, () => true)).toEqualTypeOf<
[Array<string | number>, Array<string | number>]
>();

// narrowing

expectTypeOf(partition(a, isString)).toEqualTypeOf<[string[], number[]]>();
expectTypeOf(partition(aOrU, isString)).toEqualTypeOf<
[string[], number[]]
>();
expectTypeOf(partition(aOrN, isString)).toEqualTypeOf<
[string[], number[]]
>();

expectTypeOf(partition(a, isDateOrString)).toEqualTypeOf<
[string[], number[]]
>();
expectTypeOf(partition(aOrU, isDateOrString)).toEqualTypeOf<
[string[], number[]]
>();
expectTypeOf(partition(aOrN, isDateOrString)).toEqualTypeOf<
[string[], number[]]
>();

expectTypeOf(partition(a, isA)).toEqualTypeOf<
['a'[], Array<string | number>]
>();
expectTypeOf(partition(aOrU, isA)).toEqualTypeOf<
['a'[], Array<string | number>]
>();
expectTypeOf(partition(aOrN, isA)).toEqualTypeOf<
['a'[], Array<string | number>]
>();

type AB = Array<'a' | 'b'>;
const ab = [] as AB;
const abOrU = ab as AB | undefined;
const abOrN = ab as AB | null;

expectTypeOf(partition(ab, isA)).toEqualTypeOf<['a'[], 'b'[]]>();
expectTypeOf(partition(abOrU, isA)).toEqualTypeOf<['a'[], 'b'[]]>();
expectTypeOf(partition(abOrN, isA)).toEqualTypeOf<['a'[], 'b'[]]>();

expectTypeOf(partition(ab, isString)).toEqualTypeOf<
[Array<'a' | 'b'>, []]
>();
expectTypeOf(partition(abOrU, isString)).toEqualTypeOf<
[Array<'a' | 'b'>, []]
>();
expectTypeOf(partition(abOrN, isString)).toEqualTypeOf<
[Array<'a' | 'b'>, []]
>();

type AN = Array<'a' | number>;
const an = [] as AN;
const anOrU = an as AN | undefined;
const anOrN = an as AN | null;
expectTypeOf(partition(an, isStringOr2)).toEqualTypeOf<
[Array<'a' | 2>, number[]]
>();
expectTypeOf(partition(anOrU, isStringOr2)).toEqualTypeOf<
[Array<'a' | 2>, number[]]
>();
expectTypeOf(partition(anOrN, isStringOr2)).toEqualTypeOf<
[Array<'a' | 2>, number[]]
>();
});

it('has fancy typing for objects', () => {
expect().nothing();

interface O {
a: number;
2: string;
c: Date | Document;
}
const o = {} as O;
const oOrN = o as O | null;
const oOrU = o as O | undefined;
type AllOTypes = number | string | Date | Document;
expectTypeOf(partition(o, () => true)).toEqualTypeOf<
[AllOTypes[], AllOTypes[]]
>();
expectTypeOf(partition(oOrN, () => true)).toEqualTypeOf<
[AllOTypes[], AllOTypes[]]
>();
expectTypeOf(partition(oOrU, () => true)).toEqualTypeOf<
[AllOTypes[], AllOTypes[]]
>();

// value narrowing

expectTypeOf(partition(o, isString)).toEqualTypeOf<
[string[], Array<number | Date | Document>]
>();
expectTypeOf(partition(oOrU, isString)).toEqualTypeOf<
[string[], Array<number | Date | Document>]
>();
expectTypeOf(partition(oOrN, isString)).toEqualTypeOf<
[string[], Array<number | Date | Document>]
>();

expectTypeOf(partition(o, isDate)).toEqualTypeOf<
[Date[], Array<number | string | Document>]
>();
expectTypeOf(partition(oOrU, isDate)).toEqualTypeOf<
[Date[], Array<number | string | Document>]
>();
expectTypeOf(partition(oOrN, isDate)).toEqualTypeOf<
[Date[], Array<number | string | Document>]
>();

expectTypeOf(partition(o, isNumberOrString)).toEqualTypeOf<
[Array<string | number>, Array<Date | Document>]
>();
expectTypeOf(partition(oOrU, isNumberOrString)).toEqualTypeOf<
[Array<string | number>, Array<Date | Document>]
>();
expectTypeOf(partition(oOrN, isNumberOrString)).toEqualTypeOf<
[Array<string | number>, Array<Date | Document>]
>();

expectTypeOf(partition(o, isDateOrString)).toEqualTypeOf<
[Array<Date | string>, Array<number | Document>]
>();
expectTypeOf(partition(oOrU, isDateOrString)).toEqualTypeOf<
[Array<Date | string>, Array<number | Document>]
>();
expectTypeOf(partition(oOrN, isDateOrString)).toEqualTypeOf<
[Array<Date | string>, Array<number | Document>]
>();

expectTypeOf(partition(o, isMap)).toEqualTypeOf<[[], AllOTypes[]]>();
expectTypeOf(partition(oOrU, isMap)).toEqualTypeOf<[[], AllOTypes[]]>();
expectTypeOf(partition(oOrN, isMap)).toEqualTypeOf<[[], AllOTypes[]]>();

expectTypeOf(partition(o, isMapOrString)).toEqualTypeOf<
[string[], Array<number | Date | Document>]
>();
expectTypeOf(partition(oOrU, isMapOrString)).toEqualTypeOf<
[string[], Array<number | Date | Document>]
>();
expectTypeOf(partition(oOrN, isMapOrString)).toEqualTypeOf<
[string[], Array<number | Date | Document>]
>();

interface S2 {
a: 'a' | number;
}
const s2 = {} as S2;
const s2OrN = s2 as S2 | null;
const s2OrU = s2 as S2 | undefined;

expectTypeOf(partition(s2, isA)).toEqualTypeOf<['a'[], number[]]>();
expectTypeOf(partition(s2OrU, isA)).toEqualTypeOf<['a'[], number[]]>();
expectTypeOf(partition(s2OrN, isA)).toEqualTypeOf<['a'[], number[]]>();

expectTypeOf(partition(s2, isStringOr2)).toEqualTypeOf<
[Array<'a' | 2>, number[]]
>();
expectTypeOf(partition(s2OrU, isStringOr2)).toEqualTypeOf<
[Array<'a' | 2>, number[]]
>();
expectTypeOf(partition(s2OrN, isStringOr2)).toEqualTypeOf<
[Array<'a' | 2>, number[]]
>();
});

//
// stolen from https://github.com/lodash/lodash
//

var array = [1, 0, 1];

it('should split elements into two groups by `predicate`', () => {
expect(partition([], identity)).toEqual([[], []]);
expect(partition(array, stubTrue)).toEqual([array, []]);
expect(partition(array, stubFalse)).toEqual([[], array]);
});

it('should work with an object for `collection`', () => {
expect(partition({ a: 1.1, b: 0.2, c: 1.3 }, Math.floor)).toEqual([
[1.1, 1.3],
[0.2],
]);
});

it('should provide correct iteratee arguments', () => {
const spy = jasmine.createSpy();
partition([1, 2, 3], spy);
expect(spy.calls.first().args).toEqual([1]);
});

it('iterates over own string keyed properties of objects', () => {
const object = { a: 1 };
const spy = jasmine.createSpy();

partition(object, spy);

expectCallsAndReset(spy, [1]);
});

it('should ignore changes to `length`', () => {
const array = [1];
const spy = jasmine.createSpy().and.callFake(() => {
array.push(2);
return true;
});

partition(array, spy);

expect(spy).toHaveBeenCalledTimes(1);
});

it('should ignore added `object` properties', () => {
const object: any = { a: 1 };
const spy = jasmine.createSpy().and.callFake(() => {
object.b = 2;
return true;
});

partition(object, spy);

expect(spy).toHaveBeenCalledTimes(1);
});
});
37 changes: 37 additions & 0 deletions projects/micro-dash/src/lib/collection/partition.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { ValuesType } from 'utility-types';
import {
IfCouldBe,
Narrow,
Nil,
ValueIteratee,
ValueNarrowingIteratee,
} from '../interfaces';
import { forEach } from './for-each';

/**
* Creates an array of elements split into two groups, the first of which contains elements `predicate` returns truthy for, the second of which contains elements `predicate` returns falsey for.
*
* Contribution to minified bundle size, when it is the only function imported:
* - Lodash: 14,205 bytes
* - Micro-dash: 265 bytes
*/

export function partition<T, O>(
array: T | Nil,
predicate: ValueNarrowingIteratee<ValuesType<T>, O>,
): [
IfCouldBe<ValuesType<T>, O, Array<Narrow<ValuesType<T>, O>>, []>,
Exclude<ValuesType<T>, O> extends never
? []
: Array<Exclude<ValuesType<T>, O>>,
];
export function partition<T>(
array: T | Nil,
predicate: ValueIteratee<ValuesType<T>, any>,
): [Array<ValuesType<T>>, Array<ValuesType<T>>];

export function partition(collection: any, predicate: any): any {
const result: [any[], any] = [[], []];
forEach(collection, (value) => result[predicate(value) ? 0 : 1].push(value));
return result;
}
Loading

0 comments on commit 14417df

Please sign in to comment.