按n个键或嵌套键高效分组对象数组的最优方法
Great question! Let's walk through how to tackle grouping object arrays into the structure you need—whether you're grouping by the full object (like your example), a set of specific keys, or even nested keys. We'll focus on efficient, reusable implementations that work for most real-world scenarios.
1. Grouping by Entire Objects (Your Example Scenario)
First, let's solve your exact use case: grouping duplicate objects into sub-arrays. The core challenge here is that JavaScript objects are reference types, so you can't use them directly as keys in a map or object. Instead, we need to create a unique, consistent identifier for each object's content.
Implementation
We’ll use Array.reduce() with a Map (more efficient than plain objects for grouping, especially with large datasets) to track groups, then convert the Map’s values to your desired array structure:
const data = [ {a: 1, b: 2, c:3}, {d: 4, e: 5, f: 6}, {a: 1, b: 2, c:3}, {g: 7, h: 8, i: 9}, {d: 4, e: 5, f: 6}, {g: 7, h: 8, i: 9}, ]; // Group by entire object content const groupedByObject = Array.from( data.reduce((map, obj) => { // Create a consistent key by sorting object entries before stringifying const sortedEntries = Object.entries(obj).sort(); const key = JSON.stringify(Object.fromEntries(sortedEntries)); // Add the object to its group (or create a new group if it doesn't exist) map.set(key, [...(map.get(key) || []), obj]); return map; }, new Map()) ); console.log(groupedByObject); // Output matches your desired grouped structure!
Key Notes
- Sorting object entries before stringifying ensures that objects with the same key-value pairs in different orders (e.g.,
{a:1, b:2}vs{b:2, a:1}) generate the same group key. Mapis preferred over plain objects because it handles non-string keys better and has consistent O(1) time complexity forget/setoperations.
2. Grouping by a Set of Specific Keys (n Keys)
If you don’t want to group by the entire object, but instead by a subset of keys (e.g., group by a and b), we can build a reusable function that accepts your target keys as a parameter.
Implementation
/** * Groups an array of objects by a set of specified keys * @param {Array} arr - The array to group * @param {Array} keys - The keys to use for grouping * @returns {Array} Array of grouped sub-arrays */ function groupByKeys(arr, keys) { return Array.from( arr.reduce((map, obj) => { // Create a key from the specified keys' values (sorted for consistency) const sortedKeys = keys.sort(); const keyValues = sortedKeys.map(key => obj[key]); const groupKey = JSON.stringify(keyValues); map.set(groupKey, [...(map.get(groupKey) || []), obj]); return map; }, new Map()) ); } // Example usage: group by keys 'a' and 'b' const sampleData = [ {a:1, b:2, c:3}, {a:1, b:2, d:4}, {a:2, b:3, e:5}, {a:1, b:2, f:6}, ]; const groupedByAB = groupByKeys(sampleData, ['a', 'b']); console.log(groupedByAB); // Output: [[{a:1,b:2,c:3}, {a:1,b:2,d:4}, {a:1,b:2,f:6}], [{a:2,b:3,e:5}]]
Key Notes
- Sorting the input keys ensures that passing the same set of keys in any order (e.g.,
['a','b']vs['b','a']) generates the same group key. - This function works for any number of keys—1, 2, or n—making it flexible for different use cases.
3. Grouping by Nested Keys
For nested keys (e.g., user.address.city), we first need a helper function to safely extract nested values from objects, then use that value as our group key.
Implementation
First, the helper function to get nested values:
/** * Safely gets a nested value from an object * @param {Object} obj - The target object * @param {String} path - Dot-separated path to the nested value (e.g., 'user.address.city') * @returns {*} The nested value, or undefined if the path doesn't exist */ function getNestedValue(obj, path) { return path.split('.').reduce((current, key) => { return current && current[key] !== undefined ? current[key] : undefined; }, obj); }
Then, the grouping function for nested keys:
/** * Groups an array of objects by a nested key path * @param {Array} arr - The array to group * @param {String} nestedPath - Dot-separated path to the nested key * @returns {Array} Array of grouped sub-arrays */ function groupByNestedKey(arr, nestedPath) { return Array.from( arr.reduce((map, obj) => { const key = getNestedValue(obj, nestedPath); // Handle cases where the nested path doesn't exist (group under a fallback key) const groupKey = key !== undefined ? JSON.stringify(key) : 'undefined-group'; map.set(groupKey, [...(map.get(groupKey) || []), obj]); return map; }, new Map()) ); } // Example usage: group by 'user.address.city' const nestedData = [ {user: {name: 'Alice', address: {city: 'New York'}}}, {user: {name: 'Bob', address: {city: 'London'}}}, {user: {name: 'Charlie', address: {city: 'New York'}}}, {user: {name: 'Dave'}}, // No address key ]; const groupedByCity = groupByNestedKey(nestedData, 'user.address.city'); console.log(groupedByCity); // Output: [[{user: {...city: 'New York'}}, {user: {...city: 'New York'}}], [{user: {...city: 'London'}}], [{user: {name: 'Dave'}}]]
Efficiency Considerations
- Time Complexity: All implementations run in O(n * k) time, where
nis the number of objects in the array, andkis the time to generate the group key (stringifying keys/objects or traversing nested paths). This is optimal for most real-world scenarios. - Map vs. Plain Objects:
Mapis more reliable for grouping because it doesn’t coerce keys to strings (though we use stringified keys here for consistency) and avoids conflicts with built-in object properties (liketoString). - Stringify Alternatives: If you need even better performance, you can create custom hash strings instead of
JSON.stringify(e.g., concatenating key-value pairs with a unique separator). However,JSON.stringifyis the most straightforward and consistent method for most cases.
内容的提问来源于stack exchange,提问作者Pommesloch

