2755. Deep Merge of Two Objects
Problem Description
This problem involves creating a function that can deeply merge two objects or arrays based on specific rules. A deep merge means that any nested structures within the objects or arrays will also be merged according to certain rules. Here are the rules for deep merging provided in the problem:
- If both values are objects, the merged result should include all keys from both objects. If a key is present in both, their associated values should be deep merged. If a key is only present in one object, its value should simply be included.
- If both values are arrays, the merged array should be as long as the longer of the two original arrays. Just like with objects, apply the same deep merge logic to each position in the arrays, treating the indices as if they were keys.
- In any other case (e.g., values are of different types or not objects/arrays), the result should be the second value
obj2
.
The problem statement clarifies that the inputs obj1
and obj2
are the results of JSON.parse()
, meaning they are valid JavaScript objects or arrays that have been parsed from JSON strings.
Intuition
The solution is based on recursion, a common technique for dealing with nested data structures like objects and arrays.
- We first check if the inputs are both objects or arrays since the merging logic applies only in those cases. If either
obj1
orobj2
is not an object, or they are not the same type (one is an array and the other is not), we immediately returnobj2
. - If both are objects or arrays, we iterate through the keys of
obj2
and apply thedeepMerge
function to each key. This means we attempt to mergeobj1[key]
andobj2[key]
, recursively ensuring that any nested objects or arrays are also merged correctly. - We then return
obj1
as it now contains merged values fromobj2
.
This recursive strategy neatly handles arbitrarily complex and deep object structures and directly maps onto the merging rules provided in the problem statement. The base case of the recursion is effectively whenever we reach a point where the values to be merged are not both objects or arrays.
Solution Approach
The solution uses a recursive function deepMerge
to merge two potentially complex data structures, which could be objects or arrays. Here's how this is carried out:
-
First, we define helper functions
isObj
andisArr
to check if a value is an object or array, respectively. These are used to ensure that we only apply merging logic to appropriate data types.const isObj = (obj: any) => obj && typeof obj === 'object'; const isArr = (obj: any) => Array.isArray(obj);
-
We then examine the input values
obj1
andobj2
. If either is not an object (or if one is an array and the other isn't), we returnobj2
. This serves as the base case for non-object/array values or mismatched type pairs.if (!isObj(obj1) || !isObj(obj2)) { return obj2; } if (isArr(obj1) !== isArr(obj2)) { return obj2; }
-
Next, the function iterates through the keys of
obj2
using afor...in
loop. For each key, we calldeepMerge
on the value at that key from bothobj1
andobj2
, essentially performing a merge operation on any nested structures:for (const key in obj2) { obj1[key] = deepMerge(obj1[key], obj2[key]); }
-
The operation
obj1[key] = deepMerge(obj1[key], obj2[key])
ensures that if the same key is present in bothobj1
andobj2
, their values will be merged. Otherwise, if the key exists only inobj2
, it gets added toobj1
. -
Finally,
obj1
is returned, now containing merged data from bothobj1
andobj2
.
Recursion is the key algorithmic pattern that makes the implementation succinct and able to cope with objects and arrays of any depth. The deepMerge
function merges obj2
into obj1
at every level of the data structure, complying with the rules laid out in the problem statement.
Ready to land your dream job?
Unlock your dream job with a 2-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
To illustrate the solution approach using a small example, let's consider two objects that we wish to merge using the deepMerge
function described in the solution approach:
let obj1 = { a: 1, b: { c: 3, d: 4 }, e: [5, 6], }; let obj2 = { b: { c: 8, e: 9 }, e: [7], f: 10, };
Here, we want to merge obj2
into obj1
. Here's a step-by-step walkthrough of how the deep merge would function:
-
Since both
obj1
andobj2
are objects, we proceed with merging. We do not immediately returnobj2
because the base case does not apply. -
We iterate over the keys in
obj2
. The keys areb
,e
, andf
. -
For key
b
,obj1[b]
andobj2[b]
are both objects:obj1[b]: { c: 3, d: 4 } obj2[b]: { c: 8, e: 9 }
So, we call
deepMerge
on these two objects:- Both have a key
c
, so we merge the values. The value fromobj2
(8) takes precedence. obj1[b]
has a keyd
that is not present inobj2[b]
; it remains as is.obj2[b]
has a keye
that is not present inobj1[b]
; it gets added to the result.
The merged result for key
b
is:{ c: 8, d: 4, e: 9 }
. - Both have a key
-
For key
e
,obj1[e]
is an array and so isobj2[e]
:obj1[e]: [5, 6] obj2[e]: [7]
We extend
obj1[e]
to match the length of the longer array, but sinceobj2[e]
is the shorter one, no change is made to the length. The deep merge logic doesn't introduce new items from the longer array unless there are corresponding items inobj2
. So the merged result for keye
is[7, 6]
. -
For key
f
, which is only present inobj2
, we simply add it toobj1
:obj1[f]: 10
The final merged object obj1
now looks like this:
{ a: 1, b: { c: 8, d: 4, e: 9 }, e: [7, 6], f: 10, }
This example makes it clear how each step in the solution approach actively contributes to the desired final result according to the rules of deep merging. Recursion ensures that objects and arrays nested at any level are merged correctly and the rules are followed.
Solution Implementation
1def deep_merge(first_object, second_object):
2 """
3 Function to deeply merge two objects, combining their properties and
4 sub-properties.
5
6 :param first_object: The first dictionary or object to merge.
7 :param second_object: The second dictionary or object to merge.
8 :return: A dictionary with properties from both first_object and second_object merged.
9 """
10
11 # Helper function to check if a value is a dictionary (object).
12 def is_object(item):
13 return isinstance(item, dict)
14
15 # Helper function to check if a value is a list (array).
16 def is_array(item):
17 return isinstance(item, list)
18
19 # If either argument is not a dictionary (object), return the second_object.
20 if not is_object(first_object) or not is_object(second_object):
21 return second_object
22
23 # If the types of container for both objects differ (one is a list and one is a dictionary), return second_object.
24 if is_array(first_object) != is_array(second_object):
25 return second_object
26
27 # Iterate over each property of the second_object.
28 for key in second_object:
29 # If the property key is in the dictionary's own properties (not inherited).
30 if key in second_object:
31 # Recursively merge properties from both objects.
32 first_object[key] = deep_merge(first_object.get(key), second_object[key])
33
34 # Return the merged first_object, which now contains properties from both objects.
35 return first_object
36
37# Example usage:
38# obj1 = {"a": 1, "c": 3}
39# obj2 = {"a": 2, "b": 2}
40# merged_object = deep_merge(obj1, obj2)
41# Should print: {"a": 2, "c": 3, "b": 2}
42
1import java.util.HashMap;
2import java.util.Map;
3import java.util.Set;
4
5public class DeepMergeExample {
6
7 // Method to check if an object is a Map (used to simulate an 'object' in Java).
8 private static boolean isMap(Object item) {
9 return item instanceof Map;
10 }
11
12 // Deeply merge two Maps and return the result.
13 @SuppressWarnings("unchecked")
14 public static Map<String, Object> deepMerge(Map<String, Object> firstMap, Map<String, Object> secondMap) {
15 for (String key : secondMap.keySet()) {
16 Object firstValue = firstMap.get(key);
17 Object secondValue = secondMap.get(key);
18
19 // If the current key is present in both maps and both values are also maps, then merge them recursively.
20 if (isMap(firstValue) && isMap(secondValue)) {
21 Map<String, Object> firstMapValue = (Map<String, Object>) firstValue;
22 Map<String, Object> secondMapValue = (Map<String, Object>) secondValue;
23 firstMap.put(key, deepMerge(firstMapValue, secondMapValue));
24 } else {
25 // If the second map has a key that's not in the first or the values are not maps, put it in the first map.
26 firstMap.put(key, secondValue);
27 }
28 }
29
30 // Return the merged Map object.
31 return firstMap;
32 }
33
34 // Example usage:
35 public static void main(String[] args) {
36 Map<String, Object> map1 = new HashMap<>();
37 map1.put("a", 1);
38 map1.put("c", 3);
39
40 Map<String, Object> map2 = new HashMap<>();
41 map2.put("a", 2);
42 map2.put("b", 2);
43
44 Map<String, Object> mergedMap = deepMerge(map1, map2);
45
46 // Should display: {a=2, b=2, c=3}
47 System.out.println(mergedMap);
48 }
49}
50
1#include <unordered_map>
2#include <vector>
3#include <string>
4#include <any>
5
6// Function to check if a std::any value is an object (i.e., std::unordered_map).
7bool isObject(const std::any &item) {
8 if (item.type() == typeid(std::unordered_map<std::string, std::any>)) {
9 return true;
10 }
11 return false;
12}
13
14// Function to check if a std::any value is an array (i.e., std::vector).
15bool isArray(const std::any &item) {
16 if (item.type() == typeid(std::vector<std::any>)) {
17 return true;
18 }
19 return false;
20}
21
22// Function to deeply merge two objects, combining their properties and sub-properties.
23std::any deepMerge(std::any &firstObject, std::any &secondObject) {
24 if (!isObject(firstObject) || !isObject(secondObject)) {
25 return secondObject;
26 }
27
28 if (isArray(firstObject) != isArray(secondObject)) {
29 return secondObject;
30 }
31
32 std::unordered_map<std::string, std::any>& firstMap =
33 std::any_cast<std::unordered_map<std::string, std::any>&>(firstObject);
34 std::unordered_map<std::string, std::any>& secondMap =
35 std::any_cast<std::unordered_map<std::string, std::any>&>(secondObject);
36
37 for (auto &pair : secondMap) {
38 const std::string &key = pair.first;
39 if (firstMap.find(key) == firstMap.end()) {
40 firstMap[key] = pair.second;
41 } else {
42 firstMap[key] = deepMerge(firstMap[key], pair.second);
43 }
44 }
45 return firstObject;
46}
47
48// Example usage:
49// std::unordered_map<std::string, std::any> obj1 = {{"a", 1}, {"c", 3}};
50// std::unordered_map<std::string, std::any> obj2 = {{"a", 2}, {"b", 2}};
51// auto result = deepMerge(obj1, obj2);
52// The resulting 'obj1' will be deep-merged with 'obj2'.
53
1// Function to deeply merge two objects, combining their properties and sub-properties.
2function deepMerge(firstObject: any, secondObject: any): any {
3 // Helper function to check if a value is an object.
4 const isObject = (item: any): boolean => item && typeof item === 'object' && !Array.isArray(item);
5
6 // Helper function to check if a value is an array.
7 const isArray = (item: any): boolean => Array.isArray(item);
8
9 // If either argument is not an object or array, return the secondObject.
10 if (!isObject(firstObject) || !isObject(secondObject)) {
11 return secondObject;
12 }
13
14 // If the types of container for both objects differ (one is array and one is object), return secondObject.
15 if (isArray(firstObject) !== isArray(secondObject)) {
16 return secondObject;
17 }
18
19 // Iterate over each property of the secondObject.
20 for (const key in secondObject) {
21 // If the property key is from the object's own properties (not inherited).
22 if (secondObject.hasOwnProperty(key)) {
23 // Recursively merge properties from both objects.
24 firstObject[key] = deepMerge(firstObject[key], secondObject[key]);
25 }
26 }
27
28 // Return the merged firstObject, which now contains properties from both objects.
29 return firstObject;
30}
31
32// Example usage:
33// let obj1 = {"a": 1, "c": 3}, obj2 = {"a": 2, "b": 2};
34// deepMerge(obj1, obj2); // Should log: {"a": 2, "c": 3, "b": 2}
35
Time and Space Complexity
Time Complexity
The time complexity of the deepMerge
function is a bit tricky to calculate due to its recursive nature. It depends on the structure and size of the two input objects, obj1
and obj2
. For each key in obj2
, it recursively merges the corresponding values. If n
is the total number of keys in both obj1
and obj2
at all levels, and assuming the worst case where each value is a nested object requiring a recursive merge, the time complexity would be O(n)
. It's important to note, though, that each merge itself takes time proportional to the number of keys in the object being merged, which could imply more than just a simple O(n)
complexity, but for each merge operation, we consider a constant time assuming that objects/arrays being merged at the same depth are relatively small.
Note: The recursion depth also depends on the depth of the object structure, which might impact the time complexity if we account for the cost of function calls; however, this is often disregarded in favor of analyzing the operation's complexity on the data set rather than the call stack.
Space Complexity
The space complexity is affected by two factors: the depth of the recursion (call stack) and the creation of new objects/arrays during the merge process.
-
Recursion Depth (Call Stack): If we consider
d
to be the maximum depth of recursion (which is the deepest level of nested objects/arrays), the space complexity in terms of the call stack would beO(d)
. -
Object Creation: Since the function modifies
obj1
directly without creating new objects for each merge operation, the space complexity due to object creation would beO(1)
– constant space.
Taking both points into account, the overall space complexity of deepMerge
would be O(d)
, assuming that we only consider the additional space required by the recursion stack and not the space taken by the input objects, which would remain the same throughout the execution of the function.
Is the following code DFS or BFS?
void search(Node root) { if (!root) return; visit(root); root.visited = true; for (Node node in root.adjacent) { if (!node.visited) { search(node); } } }
Recommended Readings
LeetCode Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way we
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https algomonster s3 us east 2 amazonaws com recursion jpg You first
Runtime Overview When learning about algorithms and data structures you'll frequently encounter the term time complexity This concept is fundamental in computer science and offers insights into how long an algorithm takes to complete given a certain input size What is Time Complexity Time complexity represents the amount of time
Want a Structured Path to Master System Design Too? Don’t Miss This!