Couple of advices when working with algorithms:
- Recursion is actually quite hard for a human brain. Although I agree that some tasks are easier if you know how to apply recursion. Especially in case of nested recursion.
- Naming is really important.
So for example, for me, "keys" is quite confusing. If I revisit this function later, I would probably think at first that I should add the value to all of these keys. So I would rather rename the variable into smth like "keyPath".
Now, I'll try to explain how I would solve it. This is probably not the best solution, just something from the top of my head.
So, imagine we have a nested object:
{
key1: {
key2: {
key3: []
}
}
}
And then, let's imagine the simplest scenario, we need to insert a value all the way to key3
.
In this case, obviously keyPath=["key1", "key2", "key3"]
Now, in order to reach the last key, we can start with first key of the path, and try to go down into the object until we reach the end of keyPath
.
- take first element of keyPath.
const key = "key1";
- go deeper into our object:
const nestedObj = obj[key];
- repeat, but now use
nestedObj
instead of obj
, and "key2", and so forth
This can be a recursion, but I think it's a simple loop, isn't it?
for (const i=0; i<keyPath.length; i++) {
const key = keyPath[i];
const newObj = obj[key];
obj = newObj;
}
This code is a bit too verbose though. We can simplify it like this:
for (const key of keyPath) {
obj = obj[key];
}
And voila, we got to the final object where we can insert the element:
obj.push(value);
So the whole function would look like this:
function addNested(obj: any, keyPath: (string|number)[], value: any) {
for (const key of keyPath)
obj = obj[key];
obj.push(value);
}
This will already satisfy all of the test cases you had, but as I understood your code, there is one more possibility: last key in the keyPath doesn't exist or is a primitive value (i.e. not an object and not an array).
Well, when we loop into the object, if the key doesn't exist, our obj[key]
will return undefined
. And we can also check if it contains a primitive value or null
.
So in this case, we know that we reached the end of the object, and we simply need to assign our value.
if (typeof obj[key] === 'undefined' || obj[key] === null || obj[key] !== Object(obj[key])) {
obj[key] = value;
return;
}
typeof obj[key] === 'undefined' || obj[key] === null
can be simplified into obj[key] == null
(notice it's ==
and not ===
).
So the function becomes something like this:
function addNested(obj: any, keyPath: (string|number)[], value: any) {
for (const key of keyPath) {
if (obj[key] == null || obj[key] !== Object(obj[key])) {
obj[key] = value;
return;
}
obj = obj[key];
}
obj.push(value);
}
This is still not ideal algorithm, because it doesn't do any error checking. For example, if more than 1 key from the keyPath doesn't exist in the object, or keyPath ends with an object, it doesn't handle that.
Still, I hope that this example will help you to improve approach to working with algorithms :)