diff --git a/lib/util/filter-map.spec.ts b/lib/util/filter-map.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..2c198ace4c01dbb8ff04309f2307bcebe2d9135a --- /dev/null +++ b/lib/util/filter-map.spec.ts @@ -0,0 +1,17 @@ +import { filterMap } from './filter-map'; + +describe('util/filter-map', () => { + it('should return an empty array when given an empty array', () => { + const input: unknown[] = []; + const output = filterMap(input, () => 42); + expect(output).toBe(input); + expect(output).toBeEmpty(); + }); + + it('should return an array with only the mapped values that pass the filter', () => { + const input = [0, 1, 2, 3, 4]; + const output = filterMap(input, (n) => n * n); + expect(output).toBe(input); + expect(output).toEqual([1, 4, 9, 16]); + }); +}); diff --git a/lib/util/filter-map.ts b/lib/util/filter-map.ts new file mode 100644 index 0000000000000000000000000000000000000000..705d8656d0539ca9c3e9c6d875267bffe3196815 --- /dev/null +++ b/lib/util/filter-map.ts @@ -0,0 +1,26 @@ +import is from '@sindresorhus/is'; + +type Falsy = false | '' | 0 | 0n | null | undefined; + +/** + * Filter and map an array *in place* with single iteration. + */ +export function filterMap<T, U>(array: T[], fn: (item: T) => Falsy | U): U[] { + const length = array.length; + let newIdx = 0; + for (let oldIdx = 0; oldIdx < length; oldIdx += 1) { + const item = array[oldIdx]; + const res = fn(item); + if (is.truthy(res)) { + array[newIdx] = res as never; + newIdx += 1; + } + } + + const deletedCount = length - newIdx; + if (deletedCount) { + array.length = length - deletedCount; + } + + return array as never; +}