Looping multilevel Object - javascript

Assuming we have the following Object, what would be the best way to iterate it up to it's end in order to get the name property for each Object?
Please notice, that the size of the Object may vary and the browsing should be done in this order: a, b, a1, a2, b1, a21, b11, b12 ...
var obj = {
a: {
name: 'a',
a1: {
name: 'a1'
},
a2: {
name: 'a2',
a21: {
name: 'a21'
}
}
},
b: {
name: 'b'
b1: {
name: 'b1',
b11: {
name: 'b11'
},
b12: {
name: 'b12'
}
}
}
};

You could use a breadth-first search. It is an algorithm which is iterating every level of the tree first and then the next level.
This implementation works with a queue of nodes, that means, to call the function breadthFirst, the object/single node must be wrapped in an array.
function breadthFirst(queue) {
var newQueue = [];
queue.forEach(function (node) {
('name' in node) && console.log(node.name);
Object.keys(node).forEach(function (k) {
node[k] && typeof node[k] === 'object' && newQueue.push(node[k]);
});
});
newQueue.length && breadthFirst(newQueue);
}
var object = { a: { name: 'a', a1: { name: 'a1' }, a2: { name: 'a2', a21: { name: 'a21' } } }, b: { name: 'b', b1: { name: 'b1', b11: { name: 'b11' }, b12: { name: 'b12' } } } };
breadthFirst([object]); // a b a1 a2 b1 a21 b11 b12
.as-console-wrapper { max-height: 100% !important; top: 0; }

What you are looking for is a breadth-first solution which Nina has rightly mentioned. Here is my implementation of it. In this solution, you can store the result in the array and then do console.log later.
var obj = {
a: {
name: 'a',
a1: {
name: 'a1'
},
a2: {
name: 'a2',
a21: {
name: 'a21'
}
}
},
b: {
name: 'b',
b1: {
name: 'b1',
b11: {
name: 'b11'
},
b12: {
name: 'b12'
}
}
}
};
var ans = [];
var q = [];
q.push(obj);
function getAllKeys() {
if (q.length == 0) {
return;
}
var obj = q.shift();
var keys = Object.keys(obj);
ans = ans.concat(keys);
var index = ans.indexOf('name');
if (index != -1) {
ans.splice(index, 1);
}
for (var i = 0; i < keys.length; i++) {
if (typeof obj[keys[i]] == 'object') {
q.push(obj[keys[i]]);
}
}
getAllKeys();
}
getAllKeys();
console.log(ans);

You need a recursive thing here. You are free to change the console.log to push somewhere or whatever...
var obj = {
a: {
name: 'a',
a1: {
name: 'a1'
},
a2: {
name: 'a2',
a21: {
name: 'a21'
}
}
},
b: {
name: 'b',
b1: {
name: 'b1',
b11: {
name: 'b11'
},
b12: {
name: 'b12'
}
}
}
};
var looping = function(obj) {
var keys = Object.keys(obj);
for (var i = 0; i < keys.length; i++) {
if(typeof obj[keys[i]] === 'string') console.log(obj[keys[i]]);
else looping(obj[keys[i]]);
}
}
looping(obj);

Here's a simple recursive function to get all the name properties in breadth-first order. I'm using a helper, pairs, that makes it easier to process the key-value pairs provided by each object. From there, it's a simple case analysis for how the recursive function should respond:
base case – return the accumulator
key is name, append value to the accumulator
value is an object, add pairs of value to the list of pairs to process
default case - do nothing and process the next pair
This answer differs from others in that there is no side effect from running it. Instead of hard coding some behavior in the loop function itself, loop returns an array of name property values that you can then do with whatever you wish.
const pairs = o =>
Object.keys(o).map(k => ({key: k, value: o[k]}))
const loop = o => {
const aux = (acc, [x,...xs]) => {
if (x === undefined)
return acc
else if (x.key === 'name')
return aux([...acc, x.value], xs)
else if (Object(x.value) === x.value)
return aux(acc, xs.concat(pairs(x.value)))
else
return aux(acc, xs)
}
return aux([], pairs(o))
}
const obj = { a: { name: 'a', a1: { name: 'a1' }, a2: { name: 'a2', a21: { name: 'a21' } } }, b: { name: 'b', b1: { name: 'b1', b11: { name: 'b11' }, b12: { name: 'b12' } } } }
console.log(loop(obj))
// [ 'a', 'b', 'a1', 'a2', 'b1', 'a21', 'b11', 'b12' ]
Alternatively, you could implement loop using a generator such that you could act on the values while iterating. Let me know if this interests you and I'll do a write up.
Edit
Original answer processed the object in the incorrect order. The above code now answers the question properly ^_^

You are looking for breadth-first traversal:
// Breadh-first object traversal:
function traverse(...objs) {
for (let obj of objs) {
let next = Object.values(obj).filter(val => val && typeof val === 'object');
objs.push(...next);
}
return objs;
}
// Example:
let obj = {
a:{name:"a",a1:{name:"a1"},a2:{name:"a2",a21:{name:"a21"}}},
b:{name:"b",b1:{name:"b1",b11:{name:"b11"},b12:{name:"b12"}}}
};
for (let child of traverse(obj)) {
if (child.name) console.log(child.name);
}

Related

Group consecutive similar array of elements

Here is my simple array of objects
const array = [
{ name: 'a', val: '1234' },
{ name: 'b', val: '5678' },
{ name: 'c', val: '91011' },
{ name: 'c', val: '123536' },
{ name: 'e', val: '5248478' },
{ name: 'c', val: '5455' },
{ name: 'a', val: '548566' },
{ name: 'a', val: '54555' }
]
I need to group consecutive name elements and push the corresponding val. So the expected output should be
const array = [
{ name: 'a', vals: '1234' },
{ name: 'b', vals: '5678' },
{ name: 'c', vals: ['91011', '123536'] },
{ name: 'e', vals: '5248478' },
{ name: 'c', vals: '5455' },
{ name: 'a', vals: ['548566', '54555'] }
]
I tried it But could not get over it. Please help
const output = []
const result = array.reduce((a, c) => {
if (a.name === c.name) {
output.push(a);
}
}, []);
You were actually quite close:
const output = [];
array.reduce((a, c) => {
if (a.name === c.name) { // current element equals previous element, lets merge
a.values.push(c.val);
} else output.push(a = { name: c.name, values: [c.val] ); // otherwise add new entry
return a; // the current element is the next previous
} , {}); // start with an empty a, so that c always gets pushed
Note that it makes little sense to store numbers as string though.
You can reduce the array like this. Compare the current name with previous item's name. If they are not the same, add a new item to the accumulator. If they are the same, then use concat the merge val with the last item in accumulator. concat is used because vals could either be a string or an array.
const array = [
{ name: 'a', val: '1234' },
{ name: 'b', val: '5678' },
{ name: 'c', val: '91011' },
{ name: 'c', val: '123536' },
{ name: 'e', val: '5248478' },
{ name: 'c', val: '5455' },
{ name: 'a', val: '548566' },
{ name: 'a', val: '54555' }
]
const merged = array.reduce((acc, { name, val }, i, arr) => {
// check if name is same as the previous name
if (arr[i - 1] && arr[i - 1].name === name) {
const prev = acc[acc.length - 1]; // last item in the accumulator
prev.vals = [].concat(prev.vals, val)
} else
acc.push({ name, vals: val })
return acc
}, [])
console.log(merged)

Convert Array of objects to deep nested object depending on specific key

I have an array of objects.
{
c1 : ["a1", "c2"],
c2 : ["b1"],
c3: ["d1"],
b1: ["e"]
d1: ["k"]
}
I need the object to be arranged in a hierarchy. like this,
{
c1: [{a1: null}, {
c2: [{
b1: "e"
}]
}],
c3: [{ d1: "k" }]
}
Note that we can omit the array in last (deepest) key: value pair. This is what I have tried until now.
for (v in hash){
hash[v].forEach(function(ar){
if(hash[ar]){
if (new_hash[v] == undefined){
new_hash[v] = []
}
new_hash[v].push({[ar] : hash[ar]})
}
})
}
I think this problem requires dynamic programming (recursion with saving the state) in which I am not good. Please help.
You could take another hash table and store there the relation between all node and take out of this for the result only node which have no parents.
To overcom the problem of nodes without children, I added an empty array, because the original wanted structure has either null or no children at all, like this node
{ b1: "e" }
where as with a null marker it should be
{ b1: [{ e: null }] }
This solution features an empty array, which can be replaced by any other value.
{ b1: [{ e: [] }] }
var hash = { c1: ["a1", "c2"], c2: ["b1"], c3: ["d1"], b1: ["e"], d1: ["k"] },
keys = Object.keys(hash),
parents = new Set(keys),
temp = {},
tree ;
keys.forEach(k => hash[k].forEach(t => {
parents.delete(t);
temp[k] = temp[k] || [];
temp[t] = temp[t] || [];
if (!temp[k].some(o => t in o)) temp[k].push({ [t]: temp[t] });
}));
tree = Object.assign({}, ...Array.from(parents, k => ({ [k]: temp[k] })));
console.log(tree);
.as-console-wrapper { max-height: 100% !important; top: 0; }
You can walk that parent-children list without recursion to get the tree.
I left out the code to actually transform the nodes into the format you're describing because it's pretty early in the morning for me, but the transformation should be pretty straightforward.
const data = {
c1: ["a1", "c2"],
c2: ["b1"],
c3: ["d1"],
b1: ["e"],
d1: ["k"],
};
// Generate a hash of nodes, mapping them to their children and parents.
const nodes = {};
Object.entries(data).forEach(([parentId, childIds]) => {
const parent = (nodes[parentId] = nodes[parentId] || {
id: parentId,
children: [],
});
childIds.forEach(childId => {
const child = (nodes[childId] = nodes[childId] || {
id: childId,
children: [],
});
parent.children.push(child);
child.parent = parent;
});
});
// Filter in only the nodes with no parents
const rootNodes = {};
Object.values(nodes).forEach(node => {
// TODO: transform the {id, children, parent} nodes to whichever format you require
if (!node.parent) rootNodes[node.id] = node;
});
rootNodes will look like
{
c1: {
id: 'c1',
children: [
{ id: 'a1', children: [], parent: ... },
{
id: 'c2',
children: [
{
id: 'b1',
children: [ { id: 'e', children: [], parent: ... } ],
parent: ...
}
],
parent: ...
}
]
},
c3: {
id: 'c3',
children: [
{
id: 'd1',
children: [ { id: 'k', children: [], parent: ... } ],
parent: ...
}
]
}
}
You could create one function with reduce method to loop Object.keys and build new object structure and one more function to check if current key is already in object and return it.
const data = {
c1: ["a1", "c2"],
c2: ["b1"],
c3: ["d1"],
b1: ["e"],
d1: ["k"]
}
function find(obj, key) {
let result = null
for (let i in obj) {
if (obj[i] === key || i === key) {
result = obj
}
if (!result && typeof obj[i] == 'object') {
result = find(obj[i], key)
}
}
return result
}
function nest(data) {
return Object.keys(data).reduce((r, e) => {
const match = find(r, e);
if (match) {
if (!match[e]) match[e] = []
match[e].push({
[data[e]]: null
})
} else {
data[e].forEach(el => {
if (!r[e]) r[e] = [];
r[e].push({
[el]: null
})
})
}
return r;
}, {})
}
const result = nest(data);
console.log(result)

How to get index of a 2d array of 2 similar structure ones with same value

Sorry the title may not present well.
I got two 2d arrays with similar structure.
array A:
arrayA[0]['account_name'] = 'a0';
arrayA[1]['account_name'] = 'a1';
arrayA[2]['account_name'] = 'a2';
And array B:
arrayB[0]['account_name'] = 'a1';
arrayB[1]['account_name'] = 'b0';
arrayB[2]['account_name'] = 'c0';
arrayB[3]['account_name'] = 'a0';
arrayB[4]['account_name'] = 'd3';
arrayB[5]['account_name'] = 'e8';
arrayB[6]['account_name'] = 'a3';
arrayB[7]['account_name'] = 'b4';
arrayB[8]['account_name'] = 'b1';
Now I know arrayA[0]['account_name'] equals to "a0", how can I search efficiently to check if it also exists in array B / know its position in array B? And I would like to loop for all values in array A.
const a = [
{ name: 'a0' },
{ name: 'a1' },
{ name: 'b2' }
];
const b = [
{ name: 'a0' },
{ name: 'a1' },
{ name: 'a2' },
{ name: 'b0' },
{ name: 'b1' },
{ name: 'b2' }
];
a.forEach((aa, i) => {
let found;
b.forEach((bb, j) => {
if(aa.name === bb.name) {
found = {
index: j,
value: aa.name
};
return true;
}
});
console.log(found);
});

Filter array of objects whose any properties contains a value

I'm wondering what is the cleanest way, better way to filter an array of objects depending on a string keyword. The search has to be made in any properties of the object.
When I type lea I want to go trough all the objects and all their properties to return the objects that contain lea
When I type italy I want to go trough all the objects and all their properties to return the objects that contain italy.
I know there are lot of solutions but so far I just saw some for which you need to specify the property you want to match.
ES6 and lodash are welcome!
const arrayOfObject = [{
name: 'Paul',
country: 'Canada',
}, {
name: 'Lea',
country: 'Italy',
}, {
name: 'John',
country: 'Italy',
}, ];
filterByValue(arrayOfObject, 'lea') // => [{name: 'Lea',country: 'Italy'}]
filterByValue(arrayOfObject, 'ita') // => [{name: 'Lea',country: 'Italy'}, {name: 'John',country: 'Italy'}]
You could filter it and search just for one occurence of the search string.
Methods used:
Array#filter, just for filtering an array with conditions,
Object.keys for getting all property names of the object,
Array#some for iterating the keys and exit loop if found,
String#toLowerCase for getting comparable values,
String#includes for checking two string, if one contains the other.
function filterByValue(array, string) {
return array.filter(o =>
Object.keys(o).some(k => o[k].toLowerCase().includes(string.toLowerCase())));
}
const arrayOfObject = [{ name: 'Paul', country: 'Canada', }, { name: 'Lea', country: 'Italy', }, { name: 'John', country: 'Italy' }];
console.log(filterByValue(arrayOfObject, 'lea')); // [{name: 'Lea', country: 'Italy'}]
console.log(filterByValue(arrayOfObject, 'ita')); // [{name: 'Lea', country: 'Italy'}, {name: 'John', country: 'Italy'}]
.as-console-wrapper { max-height: 100% !important; top: 0; }
Well when we already know that its not going to be a search on an object with methods, we can do the following for saving bit on time complexity :
function filterByValue(array, value) {
return array.filter((data) => JSON.stringify(data).toLowerCase().indexOf(value.toLowerCase()) !== -1);
}
Use Object.keys to loop through the properties of the object. Use reduce and filter to make the code more efficient:
const results = arrayOfObject.filter((obj)=>{
return Object.keys(obj).reduce((acc, curr)=>{
return acc || obj[curr].toLowerCase().includes(term);
}, false);
});
Where term is your search term.
You can always use array.filter() and then loop through each object and if any of the values match the value you are looking for, return that object.
const arrayOfObject = [{
name: 'Paul',
country: 'Canada',
}, {
name: 'Lea',
country: 'Italy',
}, {
name: 'John',
country: 'Italy',
}, ];
let lea = arrayOfObject.filter(function(obj){
//loop through each object
for(key in obj){
//check if object value contains value you are looking for
if(obj[key].includes('Lea')){
//add this object to the filtered array
return obj;
}
}
});
console.log(lea);
This code checks all the nested values until it finds what it's looking for, then returns true to the "array.filter" for the object it was searching inside(unless it can't find anything - returns false). When true is returned, the object is added to the array that the "array.filter" method returns. When multiple keywords are entered(spaced out by a comma and a space), the search is narrowed further, making it easier for the user to search for something.
Stackblitz example
const data = [
{
a: 'aaaaaa',
b: {
c: 'c',
d: {
e: 'e',
f: [
'g',
{
i: 'iaaaaaa',
j: {},
k: [],
},
],
},
},
},
{
a: 'a',
b: {
c: 'cccccc',
d: {
e: 'e',
f: [
'g',
{
i: 'icccccc',
j: {},
k: [],
},
],
},
},
},
{
a: 'a',
b: {
c: 'c',
d: {
e: 'eeeeee',
f: [
'g',
{
i: 'ieeeeee',
j: {},
k: [],
},
],
},
},
},
];
function filterData(data, filterValues) {
return data.filter((value) => {
return filterValues.trim().split(', ').every((filterValue) => checkValue(value, filterValue));
});
}
function checkValue(value, filterValue) {
if (typeof value === 'string') {
return value.toLowerCase().includes(filterValue.toLowerCase());
} else if (typeof value === 'object' && value !== null && Object.keys(value).length > 0) {
if (Array.isArray(value)) {
return value.some((v) => checkValue(v, filterValue));
} else {
return Object.values(value).some((v) => checkValue(v, filterValue));
}
} else {
return false;
}
}
console.log(filterData(data, 'a, c'));
console.log(filterData(data, 'a, c, ic'));
One way would be to use Array#filter, String#toLowerCase and String#indexOf like below.
const arrayOfObject = [{
name: 'Paul',
country: 'Canada',
}, {
name: 'Lea',
country: 'Italy',
}, {
name: 'John',
country: 'Italy',
}];
function filterByValue(arrayOfObject, term) {
var ans = arrayOfObject.filter(function(v,i) {
if(v.name.toLowerCase().indexOf(term) >=0 || v.country.toLowerCase().indexOf(term) >=0) {
return true;
} else false;
});
console.log( ans);
}
filterByValue(arrayOfObject, 'ita');
function filterByValue(arrayOfObject,words){
let reg = new RegExp(words,'i');
return arrayOfObject.filter((item)=>{
let flag = false;
for(prop in item){
if(reg.test(prop)){
flag = true;
}
}
return flag;
});
}
Here's how I would do it using lodash:
const filterByValue = (coll, value) =>
_.filter(coll, _.flow(
_.values,
_.partialRight(_.some, _.method('match', new RegExp(value, 'i')))
));
filterByValue(arrayOfObject, 'lea');
filterByValue(arrayOfObject, 'ita');
Here is a version of the above which filters by a value which is derived from an array of object's property. The function takes in the array of objects and the specified array of object's property key.
// fake ads list with id and img properties
const ads = [{
adImg: 'https://test.com/test.png',
adId: '1'
}, {
adImg: 'https://test.com/test.png',
adId: '2'
}, {
adImg: 'https://test.com/test.png',
adId: '3'
}, {
adImg: 'https://test.com/test-2.png',
adId: '4'
}, {
adImg: 'https://test.com/test-2.png',
adId: '5'
}, {
adImg: 'https://test.com/test-3.png',
adId: '6'
}, {
adImg: 'https://test.com/test.png',
adId: '7'
}, {
adImg: 'https://test.com/test-6.png',
adId: '1'
}];
// function takes arr of objects and object property
// convert arr of objects to arr of filter prop values
const filterUniqueItemsByProp = (arrOfObjects, objPropFilter) => {
return arrOfObjects.filter((item, i, arr) => {
return arr.map(prop => prop[objPropFilter]).indexOf(item[objPropFilter]) === i;
});
};
const filteredUniqueItemsByProp = filterUniqueItemsByProp(ads, 'adImg');
console.log(filteredUniqueItemsByProp);

What is the most efficient way to determine if a collection of objects has changed?

I've previously fetched a collection from the backend. I'm polling the backend for changes and have received another collection. The dataset is reasonable sized, so we don't need any optimizations... just fetched the whole thing again.
Running both datasets through algorithm f(previousCollection, newCollection), I would like to generate results for added, removed, and modified.
What is the most efficient way to do this? Or, better put, how do you all do this in your day to day work?
Example data:
old:
{id: 1, foo: 'bar'},
{id: 2, foo: 'bar'}
new:
{id: 2, foo: 'quux'},
{id: 4, foo: 'bar'}
expected result:
{event: 'removed', id: 1},
{event: 'modified', id: 2},
{event: 'added', id: 4}
Using Array#reduce and Array#find makes this quite simple
function f(prev, curr) {
var result = prev.reduce(function(result, p) {
var c = curr.find(function(item) {
return item.id == p.id;
});
if(c) {
if(c.foo !== p.foo) {
result.push({event: 'modified', id:p.id});
}
} else {
result.push({event: 'removed', id:p.id});
}
return result;
}, []);
return curr.reduce(function(result, c) {
var p = prev.find(function(item) {
return item.id == c.id;
});
if(!p) {
result.push({event: 'added', id:c.id});
}
return result;
}, result);
}
var old = [
{id: 1, foo: 'bar'},
{id: 2, foo: 'bar'}
];
var curr = [
{id: 2, foo: 'quux'},
{id: 4, foo: 'bar'}
];
console.log(f(old, curr));
Just for laughs, this example is written in ES2015+ using Arrow functions, object shorthand and object de-structuring
var f = (previousCollection, newCollection) => newCollection.reduce((result, {id}) => {
if (!previousCollection.find(item => item.id == id)) {
result.push({event: 'added', id});
}
return result;
}, previousCollection.reduce((result, {id, foo}) => {
var {foo:newValue} = newCollection.find(item => item.id == id) || {};
if (newValue) {
if(newValue !== foo) {
result.push({event: 'modified', id});
}
} else {
result.push({event: 'removed', id});
}
return result;
}, []));
var old = [
{id: 1, foo: 'bar'},
{id: 2, foo: 'bar'}
];
var curr = [
{id: 2, foo: 'quux'},
{id: 4, foo: 'bar'}
];
console.log(f(old, curr));
This is more of a comprehensive comparison.
Possible Changes:
Value of a property updated
A new property added
A property deleted. This can overlap value changed as value is different
An object is deleted.
A new object is added.
/*
Status:
Key Added,
Key Deleted,
Object Added,
Object Deleted,
Modified,
Has Duplicate
*/
function getUpdates(old, newState) {
var result = [];
// Create new copies
old = old.slice(0);
newState = newState.slice(0);
// Deleted Objects
mismatchingObjects(old, newState, result)
old.forEach(function(o) {
var report = {};
report.id = o.id;
var match = newState.filter(function(item) {
return item.id === o.id
});
var mlen = match.length;
if (mlen) {
if(mlen === 1 && stringMatch(o, match[0])) return
if(mlen > 1) report.hasDuplicate = true;
match.forEach(function(m, index) {
if (stringMatch(o, m)) return
keyMatches(o, m, index, report)
matchValue(o, m, index, report)
})
}
if(Object.keys(report).length > 1)
result.push(report)
});
return result
}
function stringMatch(o1, o2) {
return JSON.stringify(o1) === JSON.stringify(o2);
}
function keyMatches(o1, o2, index, report) {
var k1 = Object.keys(o1);
var k2 = Object.keys(o2);
if (k1.join() !== k2.join()) {
report.keysRemoved = (report.keysRemoved || [])
var r = k1.filter(function(k) {
return k2.indexOf(k) < 0;
});
report.keysRemoved.push({
keys: r,
objectIndex: index
});
report.keysAdded = (report.keysAdded || [])
var a = k2.filter(function(k) {
return k1.indexOf(k) < 0;
});
report.keysAdded.push({
keys: a,
objectIndex: index
})
}
}
function matchValue(o1, o2, index, report) {
report.keysChanged = report.keysChanged || [];
var keys = [];
for (var k in o1) {
if (o1[k] !== o2[k] && o2[k]) {
keys.push(k);
}
}
report.keysChanged.push({
keys: keys,
objectIndex: index
})
}
function mismatchingObjects(o1, o2, result) {
var ids1 = o1.map(function(o) {
return o.id
});
var ids2 = o2.map(function(o) {
return o.id
});
ids1.forEach(function(id) {
if (ids2.indexOf(id) < 0)
result.push({
id: id,
status: "Object Deleted"
})
})
ids2.forEach(function(id) {
if (ids1.indexOf(id) < 0)
result.push({
id: id,
status: "Object Added"
})
})
}
var old = [{
id: 1,
foo: 'bar'
}, {
id: 2,
foo: 'bar'
}, {
id: 3,
foo: "test",
deletedKey: "bla bla"
}]
var newState = [{
id: 2,
foo: 'quux'
}, {
id: 3,
foo: "test",
addedKey: "bla bla"
}, {
id: 3,
foo: "test2"
}, {
id: 4,
foo: 'bar'
}];
console.log(getUpdates(old, newState))
Note: This may seems a bit of an overkill. If you feel so, please accept my apologies.

Categories

Resources