Skip to content

Commit

Permalink
perf: improve performance by using loops
Browse files Browse the repository at this point in the history
  • Loading branch information
RebeccaStevens committed Sep 16, 2021
1 parent 440f219 commit 38e4b7f
Show file tree
Hide file tree
Showing 2 changed files with 61 additions and 41 deletions.
89 changes: 53 additions & 36 deletions src/deepmerge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,43 +117,52 @@ function mergeUnknowns<
U extends DeepMergeMergeFunctionUtils,
MF extends DeepMergeMergeFunctionsURIs
>(values: Ts, utils: U): DeepMergeHKT<Ts, MF> {
const types = values.map(getObjectType);
const type = types[0];
const type = getObjectType(values[0]);

if (types.every((value) => value === type)) {
if (type === ObjectType.RECORD) {
// eslint-disable-next-line functional/no-loop-statement, functional/no-let -- using a loop here is more performant than mapping every value and then testing every value.
for (let i = 1; i < values.length; i++) {
// eslint-disable-next-line functional/no-conditional-statement -- waiting on https://github.com/jonaskello/eslint-plugin-functional/issues/269
if (getObjectType(values[i]) === type) {
continue;
}

return utils.mergeFunctions.mergeOthers(values, utils) as DeepMergeHKT<
Ts,
MF
>;
}

switch (type) {
case ObjectType.RECORD:
return utils.mergeFunctions.mergeRecords(
values as ReadonlyArray<Readonly<Record<RecordProperty, unknown>>>,
utils
) as DeepMergeHKT<Ts, MF>;
}

if (type === ObjectType.ARRAY) {
case ObjectType.ARRAY:
return utils.mergeFunctions.mergeArrays(
values as ReadonlyArray<ReadonlyArray<unknown>>,
utils
) as DeepMergeHKT<Ts, MF>;
}

if (type === ObjectType.SET) {
case ObjectType.SET:
return utils.mergeFunctions.mergeSets(
values as ReadonlyArray<Readonly<ReadonlySet<unknown>>>,
utils
) as DeepMergeHKT<Ts, MF>;
}

if (type === ObjectType.MAP) {
case ObjectType.MAP:
return utils.mergeFunctions.mergeMaps(
values as ReadonlyArray<Readonly<ReadonlyMap<unknown, unknown>>>,
utils
) as DeepMergeHKT<Ts, MF>;
}
}

return utils.mergeFunctions.mergeOthers(values, utils) as DeepMergeHKT<
Ts,
MF
>;
default:
return utils.mergeFunctions.mergeOthers(values, utils) as DeepMergeHKT<
Ts,
MF
>;
}
}

/**
Expand All @@ -166,26 +175,34 @@ function mergeRecords<
U extends DeepMergeMergeFunctionUtils,
MF extends DeepMergeMergeFunctionsURIs
>(values: Ts, utils: U) {
const neverValue = {};
return Object.fromEntries(
[...getKeys(values)]
.map((key) => {
const propValues = values
.map((value) =>
objectHasProperty(value, key) ? value[key] : neverValue
)
.filter((value) => value !== neverValue);

// assert(propValues.length > 0);

if (propValues.length === 1) {
return [key, propValues[0]];
}

return [key, mergeUnknowns(propValues, utils)];
})
.filter((value): value is [unknown, unknown] => value !== neverValue)
) as DeepMergeRecordsDefaultHKT<Ts, MF>;
if (values.length === 1) {
return values[0] as DeepMergeRecordsDefaultHKT<Ts, MF>;
}

const result: Record<RecordProperty, unknown> = {};

/* eslint-disable functional/no-loop-statement, functional/no-conditional-statement -- using a loop here is more performant. */

for (const key of getKeys(values)) {
const propValues = [];

for (const value of values) {
if (objectHasProperty(value, key)) {
propValues.push(value[key]);
}
}

// assert(propValues.length > 0);

result[key] =
propValues.length === 1
? propValues[0]
: mergeUnknowns(propValues, utils);
}

/* eslint-enable functional/no-loop-statement, functional/no-conditional-statement */

return result as DeepMergeRecordsDefaultHKT<Ts, MF>;
}

/**
Expand Down
13 changes: 8 additions & 5 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,19 +55,22 @@ export function getObjectType(object: unknown): ObjectType {
export function getKeys(
objects: Readonly<ReadonlyArray<object>>
): Set<RecordProperty> {
return objects.reduce<Set<RecordProperty>>((mutableCarry, object) => {
// eslint-disable-next-line functional/no-loop-statement -- using a loop here is more efficient.
const keys = new Set<RecordProperty>();

/* eslint-disable functional/no-loop-statement -- using a loop here is more efficient. */
for (const object of objects) {
for (const key of [
...Object.getOwnPropertyNames(object),
...Object.getOwnPropertySymbols(object),
].filter((property) =>
Object.prototype.propertyIsEnumerable.call(object, property)
)) {
mutableCarry.add(key);
keys.add(key);
}
}
/* eslint-enable functional/no-loop-statement */

return mutableCarry;
}, new Set<RecordProperty>());
return keys;
}

/**
Expand Down

0 comments on commit 38e4b7f

Please sign in to comment.