Skip to content

Commit

Permalink
feat: refactor reduce into plain for loops
Browse files Browse the repository at this point in the history
Also, replace `delete` operators with "pick" where possible,
as I read `delete` can cause v8 deoptimizations
  • Loading branch information
kirillgroshkov committed Aug 5, 2024
1 parent 1736d63 commit 1837b89
Show file tree
Hide file tree
Showing 10 changed files with 291 additions and 163 deletions.
67 changes: 67 additions & 0 deletions biome.jsonc
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
{
"$schema": "https://biomejs.dev/schemas/1.8.3/schema.json",
"formatter": {
"enabled": true,
"formatWithErrors": false,
"indentStyle": "space",
"indentWidth": 2,
"lineEnding": "lf",
"lineWidth": 100,
"attributePosition": "auto"
},
"javascript": {
"formatter": {
"jsxQuoteStyle": "double",
"quoteProperties": "asNeeded",
"trailingCommas": "all",
"semicolons": "asNeeded",
"arrowParentheses": "asNeeded",
"bracketSpacing": true,
"bracketSameLine": false,
"quoteStyle": "single",
"attributePosition": "auto"
}
},
"organizeImports": { "enabled": false },
"linter": {
"enabled": true,
"rules": {
"recommended": true,
"style": {
"useShorthandFunctionType": "error",
"useShorthandAssign": "error",
"useForOf": "error",
"useConsistentArrayType": "error",
"useShorthandArrayType": "error",
"noDefaultExport": "error",
"noCommaOperator": "error",
"noArguments": "error",
"noNonNullAssertion": "off",
"useImportType": "off",
"noParameterAssign": "off",
"useTemplate": "off",
"useNumberNamespace": "off",
"noUnusedTemplateLiteral": "off"
},
"correctness": {
"noUnusedImports": "error",
"useArrayLiterals": "error"
},
"suspicious": {
"noExplicitAny": "off",
"noAssignInExpressions": "off",
"noAsyncPromiseExecutor": "off",
"noPrototypeBuiltins": "off",
"noThenProperty": "off"
},
"complexity": {
// "useSimplifiedLogicExpression": "error", // consider
"noForEach": "off",
"noUselessThisAlias": "off",
"useLiteralKeys": "off",
"noBannedTypes": "off"
}
}
},
"overrides": [{ "include": ["tsconfig.json", "tsconfig.*.json"] }]
}
5 changes: 0 additions & 5 deletions eslint.config.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,4 @@
// prettier-ignore
module.exports = [
...require('@naturalcycles/dev-lib/cfg/eslint.config'),
{
rules: {
'unicorn/no-array-reduce': 0,
},
},
]
93 changes: 48 additions & 45 deletions src/array/array.util.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { _isNotNullish } from '../is.util'
import { _assert } from '../error/assert'
import {
AbortablePredicate,
END,
Expand All @@ -22,14 +22,11 @@ import {
* Based on: https://github.com/you-dont-need/You-Dont-Need-Lodash-Underscore#_chunk
*/
export function _chunk<T>(array: readonly T[], size = 1): T[][] {
return array.reduce<T[][]>((arr, item, idx) => {
if (idx % size === 0) {
arr.push([item])
} else {
arr[arr.length - 1]!.push(item)
}
return arr
}, [])
const a: T[][] = []
for (let i = 0; i < array.length; i += size) {
a.push(array.slice(i, i + size))
}
return a
}

/**
Expand Down Expand Up @@ -93,17 +90,12 @@ export function _pushUniqBy<T>(a: T[], mapper: Mapper<T, any>, ...items: T[]): T
* Based on: https://stackoverflow.com/a/40808569/4919972
*/
export function _uniqBy<T>(arr: readonly T[], mapper: Mapper<T, any>): T[] {
return [
...arr
.reduce((map, item, index) => {
const key = item === null || item === undefined ? item : mapper(item, index)

if (!map.has(key)) map.set(key, item)

return map
}, new Map())
.values(),
]
const map = new Map<any, T>()
for (const [i, item] of arr.entries()) {
const key = item === undefined || item === null ? item : mapper(item, i)
if (!map.has(key)) map.set(key, item)
}
return [...map.values()]
}

/**
Expand Down Expand Up @@ -159,16 +151,13 @@ export function _mapBy<ITEM, KEY>(
* Returning `undefined` from the Mapper will EXCLUDE the item.
*/
export function _groupBy<T>(items: readonly T[], mapper: Mapper<T, any>): StringMap<T[]> {
return items.reduce(
(map, item, index) => {
const res = mapper(item, index)
if (res !== undefined) {
map[res] = [...(map[res] || []), item]
}
return map
},
{} as StringMap<T[]>,
)
const map: StringMap<T[]> = {}
for (const [i, item] of items.entries()) {
const key = mapper(item, i)
if (key === undefined) continue
;(map[key] ||= []).push(item)
}
return map
}

/**
Expand Down Expand Up @@ -347,7 +336,11 @@ export function _intersectsWith<T>(a1: T[], a2: T[] | Set<T>): boolean {
* // [1]
*/
export function _difference<T>(source: T[], ...diffs: T[][]): T[] {
return diffs.reduce((a, b) => a.filter(c => !b.includes(c)), source)
let a = source
for (const b of diffs) {
a = a.filter(c => !b.includes(c))
}
return a
}

/**
Expand Down Expand Up @@ -447,44 +440,54 @@ export function _first<T>(array: readonly T[]): T {
}

export function _minOrUndefined<T>(array: readonly T[]): NonNullable<T> | undefined {
const a = array.filter(_isNotNullish)
if (!a.length) return
return a.reduce((min, item) => (min <= item ? min : item))
let min: NonNullable<T> | undefined
for (const item of array) {
if (item === undefined || item === null) continue
if (min === undefined || item < min) {
min = item as NonNullable<T>
}
}
return min
}

/**
* Filters out nullish values (undefined and null).
*/
export function _min<T>(array: readonly T[]): NonNullable<T> {
const a = array.filter(_isNotNullish)
if (!a.length) throw new Error('_min called on empty array')
return a.reduce((min, item) => (min <= item ? min : item))
const min = _minOrUndefined(array)
_assert(min !== undefined, '_min called on empty array')
return min
}

export function _maxOrUndefined<T>(array: readonly T[]): NonNullable<T> | undefined {
const a = array.filter(_isNotNullish)
if (!a.length) return
return a.reduce((max, item) => (max >= item ? max : item))
let max: NonNullable<T> | undefined
for (const item of array) {
if (item === undefined || item === null) continue
if (max === undefined || item > max) {
max = item as NonNullable<T>
}
}
return max
}

/**
* Filters out nullish values (undefined and null).
*/
export function _max<T>(array: readonly T[]): NonNullable<T> {
const a = array.filter(_isNotNullish)
if (!a.length) throw new Error('_max called on empty array')
return a.reduce((max, item) => (max >= item ? max : item))
const max = _maxOrUndefined(array)
_assert(max !== undefined, '_max called on empty array')
return max
}

export function _maxBy<T>(array: readonly T[], mapper: Mapper<T, number | string | undefined>): T {
const max = _maxByOrUndefined(array, mapper)
if (max === undefined) throw new Error('_maxBy returned undefined')
_assert(max !== undefined, '_maxBy returned undefined')
return max
}

export function _minBy<T>(array: readonly T[], mapper: Mapper<T, number | string | undefined>): T {
const min = _minByOrUndefined(array, mapper)
if (min === undefined) throw new Error('_minBy returned undefined')
_assert(min !== undefined, '_minBy returned undefined')
return min
}

Expand Down
39 changes: 24 additions & 15 deletions src/datetime/localDate.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { _assert } from '../error/assert'
import { _isTruthy } from '../is.util'
import { Iterable2 } from '../iter/iterable2'
import type {
Inclusiveness,
Expand Down Expand Up @@ -686,40 +685,50 @@ class LocalDateFactory {
* Returns the earliest (min) LocalDate from the array, or undefined if the array is empty.
*/
minOrUndefined(items: LocalDateInputNullable[]): LocalDate | undefined {
return items.length ? this.min(items) : undefined
let min: LocalDate | undefined
for (const item of items) {
if (!item) continue
const ld = this.fromInput(item)
if (!min || ld.isBefore(min)) {
min = ld
}
}
return min
}

/**
* Returns the earliest LocalDate from the array.
* Throws if the array is empty.
*/
min(items: LocalDateInputNullable[]): LocalDate {
const items2 = items.filter(_isTruthy)
_assert(items2.length, 'localDate.min called on empty array')

return items2
.map(i => this.fromInput(i))
.reduce((min, item) => (min.isSameOrBefore(item) ? min : item))
const min = this.minOrUndefined(items)
_assert(min, 'localDate.min called on empty array')
return min
}

/**
* Returns the latest (max) LocalDate from the array, or undefined if the array is empty.
*/
maxOrUndefined(items: LocalDateInputNullable[]): LocalDate | undefined {
return items.length ? this.max(items) : undefined
let max: LocalDate | undefined
for (const item of items) {
if (!item) continue
const ld = this.fromInput(item)
if (!max || ld.isAfter(max)) {
max = ld
}
}
return max
}

/**
* Returns the latest LocalDate from the array.
* Throws if the array is empty.
*/
max(items: LocalDateInputNullable[]): LocalDate {
const items2 = items.filter(_isTruthy)
_assert(items2.length, 'localDate.max called on empty array')

return items2
.map(i => this.fromInput(i))
.reduce((max, item) => (max.isSameOrAfter(item) ? max : item))
const max = this.maxOrUndefined(items)
_assert(max, 'localDate.max called on empty array')
return max
}

/**
Expand Down
39 changes: 24 additions & 15 deletions src/datetime/localTime.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { _assert } from '../error/assert'
import { _isTruthy } from '../is.util'
import { _ms } from '../time/time.util'
import type {
Inclusiveness,
Expand Down Expand Up @@ -946,29 +945,39 @@ class LocalTimeFactory {
}

minOrUndefined(items: LocalTimeInputNullable[]): LocalTime | undefined {
return items.length ? this.min(items) : undefined
let min: LocalTime | undefined
for (const item of items) {
if (!item) continue
const lt = this.fromInput(item)
if (!min || lt.$date.valueOf() < min.$date.valueOf()) {
min = lt
}
}
return min
}

min(items: LocalTimeInputNullable[]): LocalTime {
const items2 = items.filter(_isTruthy)
_assert(items2.length, 'localTime.min called on empty array')

return items2
.map(i => this.fromInput(i))
.reduce((min, item) => (min.$date.valueOf() <= item.$date.valueOf() ? min : item))
const min = this.minOrUndefined(items)
_assert(min, 'localTime.min called on empty array')
return min
}

maxOrUndefined(items: LocalTimeInputNullable[]): LocalTime | undefined {
return items.length ? this.max(items) : undefined
let max: LocalTime | undefined
for (const item of items) {
if (!item) continue
const lt = this.fromInput(item)
if (!max || lt.$date.valueOf() > max.$date.valueOf()) {
max = lt
}
}
return max
}

max(items: LocalTimeInputNullable[]): LocalTime {
const items2 = items.filter(_isTruthy)
_assert(items2.length, 'localTime.max called on empty array')

return items2
.map(i => this.fromInput(i))
.reduce((max, item) => (max.$date.valueOf() >= item.$date.valueOf() ? max : item))
const max = this.maxOrUndefined(items)
_assert(max, 'localTime.max called on empty array')
return max
}
}

Expand Down
Loading

0 comments on commit 1837b89

Please sign in to comment.