Lodash invertBy Grouping Logic for Duplicate Values
This article examines the structural grouping logic used by the
Lodash library's _.invertBy method to handle duplicate
values. It details how the function traverses source object properties,
dynamically generates inverted target keys, and constructs array buckets
to accumulate identical values without overwriting previous data.
The Core Inversion Mechanism
In standard dictionary inversion, keys and values swap positions
directly. When multiple keys share the identical string value, a naive
inversion leads to property overwriting, retaining only the last
processed key. Lodash resolves this through
_.invertBy(object, [iteratee=_.identity]), which groups
conflicting keys into an array rather than storing a single scalar
key.
Internal Grouping Logic
The dynamic mapping of duplicate values operates through an accumulator-based aggregation pattern:
- Object Traversal: Lodash iterates over the object's own enumerable string keyed properties using its internal object iteration methods.
- Key Derivation via Iteratee: For each property, the
current value is passed to the iteratee function (defaulting to
_.identity). The returned value is coerced to a string to serve as the new key on the output object. - Collision Detection via
hasOwnProperty: Lodash checks whether the derived key already exists on the accumulator object usingObject.prototype.hasOwnProperty. - Dynamic Bucket Initialization and Appending:
- If the key does not exist, the accumulator
initializes a new array containing the current source key:
result[groupKey] = [sourceKey]. - If the key already exists, Lodash pushes the
current source key into the pre-existing array:
result[groupKey].push(sourceKey).
- If the key does not exist, the accumulator
initializes a new array containing the current source key:
Code Representation
The underlying algorithm follows this structure:
const source = {
a: 'alpha',
b: 'beta',
c: 'alpha'
};
// Logical equivalent of _.invertBy
const result = {};
for (const key of Object.keys(source)) {
const value = source[key];
const groupKey = String(value); // transformed by iteratee if provided
if (Object.prototype.hasOwnProperty.call(result, groupKey)) {
result[groupKey].push(key);
} else {
result[groupKey] = [key];
}
}
// Result: { alpha: ['a', 'c'], beta: ['b'] }Array Growth and Dynamic Mutation
By using native array references within the accumulator object,
_.invertBy dynamically scales storage per unique value.
When subsequent duplicate string values appear during traversal, the
reference to the existing bucket is retrieved in \(O(1)\) average time, and the new key is
appended via an array push operation. This produces a partitioned map of
arrays representing an inverted index of the source collection.