Skip to content

Commit

Permalink
feat: add mapNotNullable
Browse files Browse the repository at this point in the history
Add a new mapNotNullable method that maps and filters out null and undefined values.
  • Loading branch information
tomi committed Mar 13, 2022
1 parent e1d4743 commit 6686f20
Show file tree
Hide file tree
Showing 4 changed files with 93 additions and 1 deletion.
27 changes: 26 additions & 1 deletion src/Sequence.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import { sortBy } from "./transforms/sortBy";
import { take } from "./transforms/take";
import { takeWhile } from "./transforms/takeWhile";
import { without } from "./transforms/without";
import { copyIntoAnArray, iterableFromGenerator } from "./utils";
import { copyIntoAnArray, isNotNullable, iterableFromGenerator } from "./utils";

const identityPredicateFn = (x: any): boolean => x;

Expand Down Expand Up @@ -168,6 +168,17 @@ export class SequenceImpl<TItem> implements Iterable<TItem>, Sequence<TItem> {
return this._sequenceFromGenerator<TResultItem>(map, [mapFn]);
}

mapNotNullable<TResultItem>(
mapFn: MapFn<TItem, TResultItem>
): Sequence<NonNullable<TResultItem>> {
return this._composeGenerators<NonNullable<TResultItem>>(
map,
[mapFn],
filter,
[isNotNullable]
);
}

min<TItem>(): TItem | undefined {
let result: any | undefined = undefined;

Expand Down Expand Up @@ -373,6 +384,20 @@ export class SequenceImpl<TItem> implements Iterable<TItem>, Sequence<TItem> {
)
);
}

private _composeGenerators<TResult = TItem>(
first: Function,
firstArgs: any[],
second: Function,
secondArgs: any[]
) {
const iterableArg = [this._iterable, ...firstArgs];
const intemediatery = iterableFromGenerator(first, iterableArg);

return new SequenceImpl<TResult>(
iterableFromGenerator<TResult>(second, [intemediatery, ...secondArgs])
);
}
}

/**
Expand Down
15 changes: 15 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,21 @@ export interface Sequence<TItem> extends Iterable<TItem> {
* ```
*/
map<TResultItem>(mapFn: MapFn<TItem, TResultItem>): Sequence<TResultItem>;

/**
* Maps the sequence to a new sequence where each item is converted
* to a new value using the given mapper function and null and undefined
* values are removed.
*
* @example
* ```typescript
* // Returns [2, 4]
* from([-1, 1, 2]).mapNotNullable(x => x > 0 ? x * 2 : undefined);
* ```
*/
mapNotNullable<TResultItem>(
mapFn: MapFn<TItem, TResultItem>
): Sequence<NonNullable<TResultItem>>;
/**
* Returns the minimum value in the sequence. `undefined` is returned if the squence is empty.
*
Expand Down
5 changes: 5 additions & 0 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,8 @@ export const iterableFromGenerator = <TItem>(
): Iterable<TItem> => ({
[Symbol.iterator]: (): Iterator<TItem> => generatorFn.apply(undefined, args),
});

/**
* Returns true if the given value is not null or undefined
*/
export const isNotNullable = <T>(x: T) => x !== null && x !== undefined;
47 changes: 47 additions & 0 deletions test/fromfrom.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -501,6 +501,53 @@ describe("fromfrom", () => {
});
});

describe("mapNotNullable", () => {
let mapper: jest.Mock;

beforeEach(() => (mapper = jest.fn()));

it("calls map for each item", () => {
from([1, 2])
.mapNotNullable(mapper)
.toArray();

expect(mapper).toBeCalledTimes(2);
});

it("passes item as a parameter to map", () => {
from([1])
.mapNotNullable(mapper)
.toArray();

expect(mapper).lastCalledWith(1);
});

it("filters out undefined and null values", () => {
expect(
from([undefined, 1, null, 2, undefined, 3])
.mapNotNullable(x => x)
.toArray()
).toEqual([1, 2, 3]);
});

it("maps values with the given function", () => {
expect(
from([1, 2, 3])
.mapNotNullable(x => x ** 2)
.toArray()
).toEqual([1, 4, 9]);
});

it("can be iterated multiple times", () => {
const sequence = from([1, -1, 2, 3, -5]).mapNotNullable(x =>
x > 0 ? x ** 2 : null
);

expect(copyIntoAnArray(sequence)).toEqual([1, 4, 9]);
expect(copyIntoAnArray(sequence)).toEqual([1, 4, 9]);
});
});

describe("min", () => {
it("returns the minimum value for numbers", () => {
expect(from([1, 2, 3]).min()).toEqual(1);
Expand Down

0 comments on commit 6686f20

Please sign in to comment.