Find depth of object in iterative way - javascript

I am writing a function for calculating the depth of an object.
Here is my recursive version which seems to work as expected:
function findDepth(obj, firstCall = true) {
if (firstCall && typeof obj !== "object") {
return -1;
}
return Object.keys(obj).reduce((max, k) => {
if (typeof obj[k] === "object" && obj[k] !== null) {
const val = findDepth(obj[k], false) + 1;
if (val > max) {
max = val;
}
}
return max;
}, 1);
}
const input1 = {
a: {
b: "test",
c: {
d: {
e: {
f: [1, 2, 3],
g: {
a: null,
z: {
d: "casdsadasdsa",
q: {
z: {
i: undefined
}
}
}
}
}
},
c: {
a: "sad"
}
},
d: {
e: 5
}
},
b: {
c: {
d: "dsada"
}
}
};
const input2 = {
w: {
d: "hello",
f: {
g: "dsadas",
z: {
b: "dsajkdasjldk",
q: {
w: {
z: "dsajkdasjdla"
}
}
},
h: "dsiaodsiad"
}
},
a: "test",
b: "test2",
c: {
d: "hello",
f: {
g: "dsadas",
z: {
b: "dsajkdasjldk"
},
h: "dsiaodsiad"
}
},
e: "bye"
};
console.log(findDepth(input1));
console.log(findDepth(input2));
Now I am trying to write an iterative version, but I cannot find the best way to make the loop work.
function findDepthIterative(obj) {
if (typeof obj !== "object") {
return -1;
}
let max = 1;
let copy = Object.assign({}, obj);
let keys = Object.keys(copy);
while (keys.length) {
if (typeof copy[keys[0]] !== "object" && copy[keys[0]] !== null) {
delete copy[keys[0]];
keys = Object.keys(copy);
} else {
max++;
copy = Object.assign({}, copy[keys[0]]);
keys = Object.keys(copy);
}
}
return max;
}
const input1 = {
a: {
b: "test",
c: {
d: {
e: {
f: [1, 2, 3],
g: {
a: null,
z: {
d: "casdsadasdsa",
q: {
z: {
i: undefined
}
}
}
}
}
},
c: {
a: "sad"
}
},
d: {
e: 5
}
},
b: {
c: {
d: "dsada"
}
}
};
const input2 = {
w: {
d: "hello",
f: {
g: "dsadas",
z: {
b: "dsajkdasjldk",
q: {
w: {
z: "dsajkdasjdla"
}
}
},
h: "dsiaodsiad"
}
},
a: "test",
b: "test2",
c: {
d: "hello",
f: {
g: "dsadas",
z: {
b: "dsajkdasjldk"
},
h: "dsiaodsiad"
}
},
e: "bye"
};
console.log(findDepthIterative(input1));
console.log(findDepthIterative(input2));
As you can see from the output and the code, it just takes the first property inside the loop:
while (keys.length) {
if (typeof copy[keys[0]] !== "object" && copy[keys[0]] !== null) {
delete copy[keys[0]];
keys = Object.keys(copy);
} else {
max++;
copy = Object.assign({}, copy[keys[0]]);
keys = Object.keys(copy);
}
}
The idea was to delete the property each iteration, but I am not getting in the right way.
I tried to change it with copy[keys[keys.length - 1]] but in this way it takes only the last property instead.
Actually the issue is how to loop over all the keys, on all the depth levels, as in the recursive version.
Any suggestion about how to implement this algorithm in an iterative way?
Even any suggestion on how to improve the recursive one (or if I am missing something) is more than welcome.
p.s. NO LOADASH, UNDERSCORE, RAMDA, or whatever. Just Vanilla JS

You just need to keep a stack and push children into it while keeping track of the current depth. You can keep track of that by pushing an array of [depth, obj] into the stack and then when you pop() add one to the depth before pushing children.
const input1 = {w: {d: "hello",f: {g: "dsadas",z: {b: "dsajkdasjldk",q: {w: {z: "dsajkdasjdla"}}},h: "dsiaodsiad"}},a: "test",b: "test2",c: {d: "hello",f: {g: "dsadas",z: {b: "dsajkdasjldk"},h: "dsiaodsiad"}},e: "bye"};
function findDepthIterative(obj) {
if (typeof obj !== "object") {
return -1;
}
let max = 0;
// current depth, children
let stack = [[0, Object.values(obj)]];
while(stack.length){
let [depth, cur] = stack.pop()
if (depth > max) max = depth
if (typeof cur === "object" && cur !== null){
Object.values(cur).forEach(c => stack.push([depth+1, c]))
}
}
return max
}
console.log(findDepthIterative(input1))
// sanity check:
const depth0 = {}
const depth1 = {a:1}
const depth2 = {a:{b:2}}
console.log(findDepthIterative(depth0))
console.log(findDepthIterative(depth1))
console.log(findDepthIterative(depth2))

One way to iterate could be a depth first search using a stack.
function f(obj){
let stack = Object.keys(obj).map(k => [obj[k], 1]);
let max = 0;
while (stack.length){
let [maybeObj, d] = stack.pop();
max = Math.max(max, d);
if (typeof maybeObj == 'object' && maybeObj !== null)
Object.keys(maybeObj).map(k =>
stack.push([maybeObj[k], d + 1]));
}
return max;
}
We could also make the recursion slightly more succinct:
function f(obj){
if (typeof obj !== 'object' || obj === null)
return 0;
return Object.keys(obj).reduce((acc, k) =>
Math.max(acc, 1 + f(obj[k])), 0);
}

You should use an array instead of the object which won't work properly if there are duplicate keys. The array should contain all the objects that occur at a certain level. For each iteration, you map the array into a new one containing the previous objects' direct children:
function findDepthIterative(obj) {
if (typeof obj !== "object") {
return -1;
}
let arr = [obj]; // the array containing the objects that occur at a certain level, initially contains obj being the only object at the first level
let levels = 0; // levels counter
do { // keep doing this
levels++; // increment the levels counter
let newArr = []; // make a new array for the next level
arr.forEach(obj => { // populate it with the old level objects' children
for(let key in obj) {
if(obj[key] && typeof obj[key] === "object") {
newArr.push(obj[key]);
}
}
});
arr = newArr; // make arr the new array of object (next level)
} while (arr.length); // while there are still levels with objects in them
return levels;
}
Demo:
function findDepthIterative(obj) {
if (typeof obj !== "object") {
return -1;
}
let arr = [obj];
let levels = 0;
do {
levels++;
let newArr = [];
arr.forEach(obj => {
for(let key in obj) {
if(obj[key] && typeof obj[key] === "object") {
newArr.push(obj[key]);
}
}
});
arr = newArr;
} while (arr.length);
return levels;
}
const input1 = {
a: {
b: "test",
c: {
d: {
e: {
f: [1, 2, 3],
g: {
a: null,
z: {
d: "casdsadasdsa",
q: {
z: {
i: undefined
}
}
}
}
}
},
c: {
a: "sad"
}
},
d: {
e: 5
}
},
b: {
c: {
d: "dsada"
}
}
};
const input2 = {
w: {
d: "hello",
f: {
g: "dsadas",
z: {
b: "dsajkdasjldk",
q: {
w: {
z: "dsajkdasjdla"
}
}
},
h: "dsiaodsiad"
}
},
a: "test",
b: "test2",
c: {
d: "hello",
f: {
g: "dsadas",
z: {
b: "dsajkdasjldk"
},
h: "dsiaodsiad"
}
},
e: "bye"
};
console.log(findDepthIterative(input1));
console.log(findDepthIterative(input2));

Related

How to get keys in an object that is not in another object in JavaScript

const obj1 = {
a: 5,
e: {
c: 10,
l: {
b: 50,
},
},
};
const obj2 = {
a: 5,
e: {
c: 10,
},
};
need to get ['l', 'b'] or maybe not in the array
Here's a recursive function that deep compares the keys of both objects. This also takes into account the structure and nesting of the children.
So essentially, it goes through each nested key in obj1 and makes sure that there's an equivalent key in the same location in obj2
Your example
const obj1 = {
a: 5,
e: {
c: 10,
l: {
b: 50,
},
},
};
const obj2 = {
a: 5,
e: {
c: 10,
},
};
const missingKeys = []
function compare(obj1, obj2) {
for (let prop in obj1) {
if (obj2[prop]) {
if (typeof obj1[prop] === 'object' && typeof obj2[prop] === 'object') {
compare(obj1[prop], obj2[prop])
}
} else {
if (typeof obj1[prop] === 'object') {
compare(obj1[prop], {})
}
missingKeys.push(prop)
}
}
}
compare(obj1, obj2)
console.log(missingKeys)
Example 2:
const obj1 = {
a: 5,
e: {
c: 10,
l: {
b: 50,
d: 20,
},
},
z: 50
};
const obj2 = {
a: 5,
e: {
c: 10,
},
b: 50, // shares same key name but nested in different location
l: 50, // also shares same key but nested differently
z: 50,
};
const missingKeys = []
function compare(obj1, obj2) {
for (let prop in obj1) {
if (obj2[prop]) {
if (typeof obj1[prop] === 'object' && typeof obj2[prop] === 'object') {
compare(obj1[prop], obj2[prop])
}
} else {
if (typeof obj1[prop] === 'object') {
compare(obj1[prop], {})
}
missingKeys.push(prop)
}
}
}
compare(obj1, obj2)
console.log(missingKeys)
This would work. Without checking the levels and assuming the unique field names.
const obj1 = {
a: 5,
e: {
c: 10,
l: {
b: 50,
},
},
};
const obj2 = {
a: 5,
e: {
c: 10,
},
};
const getAllKeys = (obj) => {
let keyNames = Object.keys(obj);
Object.values(obj).forEach((value) => {
if (typeof value === "object") {
keyNames = keyNames.concat(getAllKeys(value));
}
});
return keyNames;
};
const getFilteredKeys = (keySet1, keySet2) =>
keySet1.filter((key) => !keySet2.includes(key));
const output = getFilteredKeys(getAllKeys(obj1), getAllKeys(obj2));
console.log(output);

How to sum the values in this object of objects in javascript?

const ob = {
a: 1,
b: {
c: 3,
d: 6,
e: {
f: {
g: 3,
h: {
i: 5,
j: {
k: 7
}
}
}
}
}
};
Any methods to solve this code?
I have no idea how to solve this code.
For abovementioned input I would expect a result of 1 + 3 + 6 + 3 + 5 + 7 = 25. So what I want to return from a function sumObject(ob) is: 25
You can try reduce with recursion
The condition for the sum is
If the current value is a number, sum it with result
If the current value is not a number (in your case, it's an object), call the function sumObject recursively with current result
const ob = {
a: 1,
b: {
c: 3,
d: 6,
e: {
f: {
g: 3,
h: {
i: 5,
j: {
k: 7
}
}
}
}
}
};
function sumObject(data, result = 0) {
return Object.values(data).reduce((sum, value) => typeof value === 'number' ? sum + value : sumObject(value, sum), result)
}
console.log(sumObject(ob))
If you don't understand some of the other answers, this is an easier solution to understand:
function sumObject(obj){
let result = 0;
for(let i of Object.values(obj)){ //we iterate through all values in obj
if(typeof i == "number"){ //if the current value of i is a number
result+=i; //then add that to the result
} else { //otherwise, it will be an object
result+=sumObject(i); //so we call the function on itself to iterate over this new object
}
}
return result; //finally, we return the total
}
console.log(sumObject(ob));
You can do this to extract all values into an array of numbers and then sum that array:
const getObjectValues = (obj) => (obj && typeof obj === 'object')
? Object.values(obj).map(getObjectValues).flat()
: [obj]
let nums = getObjectValues(ob)
let sum = nums.reduce((a, b) => a + b, 0)
You can recursively sum the value of each object using array#reduce and Object.values()
const ob = { a: 1, b: { c: 3, d: 6, e: { f: { g: 3, h: { i: 5, j: { k: 7 } } } } } },
getSum = o =>
Object.values(o).reduce((s, v) => {
s += typeof v === 'object' ? getSum(v): v;
return s;
}, 0);
console.log(getSum(ob));
Try recursion. This will support any amount of nested object at any level of nesting.
const sumAllNumbers = (object, total = 0) => {
const nextValues = [];
for (const value of Object.values(object)) {
if (typeof value === 'number') total += value;
if (typeof value === 'object') nextValues.push(Object.values(value));
}
if (!nextValues.length) return total;
return sumAllNumbers(nextValues.flat(), total);
};
// adds up to 25
const testCase1 = {
a: 1,
b: {
c: 3,
d: 6,
e: {
f: {
g: 3,
h: {
i: 5,
j: {
k: 7,
},
},
},
},
},
};
// adds up to 30
const testCase2 = {
a: 1,
b: 2,
c: {
a: 1,
b: 2,
c: 3,
d: {
a: 5,
b: {
c: {
d: {
e: 1,
},
},
},
},
},
d: {
g: {
f: {
c: 10,
},
},
},
e: {
f: {
a: 1,
g: {
a: 4,
},
},
},
};
// Your original test case
console.log(sumAllNumbers(testCase1));
// My much more demanding test case
console.log(sumAllNumbers(testCase2));
You could get the value of the objects and check if the value is an object, then take the result of the nested objects or call the handed over sum function for accumulator and actual value.
This approach works with a function which takes
an object
an accumulator function
a start value
This function works as well for getting all values with different accumulator function and an array as startValue.
const
reduce = (object, fn, startValue) => Object
.values(object)
.reduce(
(r, v) => v && typeof v === 'object' ? reduce(v, fn, r) : fn(r, v),
startValue
),
data = { a: 1, b: { c: 3, d: 6, e: { f: { g: 3, h: { i: 5, j: { k: 7 } } } } } },
total = reduce(data, (a, b) => a + b, 0);
console.log(total);
const ob = {
a: 1,
b: {
c: 3,
d: 6,
e: {
f: {
g: 3,
h: {
i: 5,
j: {
k: 7
}
}
}
}
}
};
const sum = data =>
Object
.keys(data)
.reduce((a,b) => a + (typeof(s = data[b]) == "number" ? s : sum(s)), 0);
console.log(sum(ob))

How to remove undefined properties from object and elements from array

I have a following object:
{
a: {
b: {
c: undefined
}
},
b: {
c: 15,
d: []
},
c: {
d: [11, undefined ,12],
e: {}
}
}
And i need to get this:
{
b: {
c: 15
},
c: {
d: [11, 12]
}
}
I found this function (source: Remove undefined properties from object )
function filter(obj) {
for (var key in obj) {
if (obj[key] === undefined) {
delete obj[key];
continue;
}
if (obj[key] && typeof obj[key] === "object") {
filter(obj[key]);
if (!Object.keys(obj[key]).length) {
delete obj[key];
}
}
}
return obj;
}
But it just delete elements of array and it turns out the following
{
b: {
c: 15
},
c: {
d: [11, empty ,12]
}
}
You need a recursive solution. Make a function that takes a value and returns something falsey if its value is falsey, or if all of its recursive elements are falsey or empty arrays or without keys:
const removeRecursive = (obj) => {
// Falsey primitive, including null:
if (!obj) return;
// Truthy primitive (or function):
if (typeof obj !== 'object') return obj;
// Array, transform all values and return the new array
// if there are any truthy transformed values:
if (Array.isArray(obj)) {
const newArr = obj.map(removeRecursive).filter(Boolean);
return newArr.length ? newArr : undefined;
}
// Otherwise, it's an object:
const newObj = Object.fromEntries(
Object.entries(obj)
.map(([key, val]) => ([key, removeRecursive(val)]))
.filter(([, val]) => val)
);
if (Object.keys(newObj).length) {
return newObj;
}
};
const obj = {
a: {
b: {
c: undefined
}
},
b: {
c: 15,
d: []
},
c: {
d: [11, undefined ,12],
e: {}
}
};
console.log(removeRecursive(obj));

How to set the value of the deepest nested properties of an object to a string of all its preceding keys?

In other words, how to turn original into expected, or how to set the values of keys which values are empty strings with a dot notation formatted representation of the key's "path" inside the object ?
original = {
A: {
B: {
C: {
D: "",
E: ""
},
F: {
G: "",
H: ""
}
}
}
}
expected = {
A: {
B: {
C: {
D: "A.B.C.D",
E: "A.B.C.E
},
F: {
G: "A.B.F.G",
H: "A.B.F.H"
}
}
}
}
Surely people must have stumbled on this kind of issue when dealing with "stringly-typed" systems ?
It can be done in that way:
const original = {
A: {
B: {
C: {
D: "",
E: ""
},
F: {
G: "",
H: ""
}
}
}
}
const addPath = (data, path = []) => {
return Object.keys(data).reduce((acc, key) => {
const currentPath = [...path, key]
return {
...acc,
[key]: typeof data[key] === 'object'
? addPath(data[key], currentPath)
: currentPath.join('.')
}
}, {})
}
console.log(addPath(original))

How to change key name with map into nested object?

Here is the object
{
a: 1,
b: {
c: {
d: 2
},
e: 3
}
}
Here is the map
{
'a': 'aaa',
'b': 'bbb',
'b.c.d': 'bcd'
}
Here is the expected result.
{
aaa: 1,
bbb: {
c: {
bcd: 2
},
e: 3
}
}
I know there's a function in lodash _.get could get the value like b.c.d.
But how can I change the key name with the map?
You can do this recursively by keeping track of the current path and building a key into the map with that:
let o = {a: 1,b: {c: {d: 2},e: 3}}
let map = {
'a': 'aaa',
'b': 'bbb',
'b.c.d': 'bcd'
}
function makeObj(obj, map, p=[]) {
let ret = {}
Object.entries(obj).forEach(([k, v]) => {
let path = p.concat(k) // add onto current path
let mapKey = map[path.join('.')] || k
ret[mapKey] = (typeof v === 'object')
? makeObj(v, map, path) // if an object recurse and pass on the current path
: v // otherwise set the value
})
return ret
}
console.log(makeObj(o, map))

Categories