Related
I have an array with objects of unknown depth, like this
var objects = [{
id: 1,
name: 'foo'
}, {
id: 2,
name: 'bar',
childs: [{
id: 3,
name: 'baz',
childs: [{
id: 4,
name: 'foobar'
}]
}]
}];
I would like to be able to filter a specific child object by it's id.
Currently I am using this little lodash script (referred from this question) but it works only with objects not deeper than one level. So searching for id: 1 and id: 2 would work fine while searching for id: 3 or id: 4 would return undefined.
function deepFilter(obj, search) {
return _(obj)
.thru(function(coll) {
return _.union(coll, _.map(coll, 'children'));
})
.flatten()
.find(search);
}
A little JSfiddle.
You could take an iterative and recursive approach.
function find(id, array) {
var result;
array.some(o => o.id === id && (result = o) || (result = find(id, o.children || [])));
return result;
}
var objects = [{ id: 1, name: 'foo' }, { id: 2, name: 'bar', children: [{ id: 3, name: 'baz', children: [{ id: 4, name: 'foobar' }] }] }];
console.log(find(1, objects));
console.log(find(2, objects));
console.log(find(3, objects));
console.log(find(4, objects));
.as-console-wrapper { max-height: 100% !important; top: 0; }
You can do that recursively like so:
function deepFind(arr, search) {
for(var obj of arr) {
if(search(obj)) {
return obj;
}
if(obj.childs) {
var deepResult = deepFind(obj.childs, search);
if(deepResult) {
return deepResult;
}
}
}
return null;
}
Then use it like so:
var result = deepFind(objects, function(obj) {
return obj.id === myId;
});
Example:
function deepFind(arr, search) {
for(var obj of arr) {
if(search(obj)) {
return obj;
}
if(obj.childs) {
var deepResult = deepFind(obj.childs, search);
if(deepResult) {
return deepResult;
}
}
}
return null;
}
var objects = [{id: 1,name: 'foo'}, {id: 2,name: 'bar',childs: [{id: 3,name: 'baz',childs: [{id: 4,name: 'foobar'}]}]}];
console.log("ID 1:", deepFind(objects, obj => obj.id === 1));
console.log("ID 4:", deepFind(objects, obj => obj.id === 4));
console.log("ID 7:", deepFind(objects, obj => obj.id === 7));
You need to call the function recursively in order to target child object. Try following
Iterate over the array and for each object check whether the id is found. If yes, break and return the result, else continue to search in the child (if exists).
Approach 1 : Traverses the tree branch by branch
Using this approach, first the code traverses for first element till the last child, then second element this last child and so on.
var objects = [{id: 1,name: 'foo'}, {id: 2,name: 'bar',childs: [{id: 3,name: 'baz',childs: [{id: 4,name: 'foobar'}]}]}];
function findObject(arr, id) {
var result;
for (let i = 0 ; i < arr.length; i++) {
if(arr[i].id === id) {
result = arr[i];
break;
}
if(arr[i].childs) {
result = findObject(arr[i].childs, id);
if(result) break;
}
}
return result;
}
console.log(findObject(objects, 4));
Approach 2 : Traverses the tree depth by depth
Using this approach, first the code traverses for first level elements, then second level elements and so on.
var objects = [{id: 1,name: 'foo'}, {id: 2,name: 'bar',childs: [{id: 3,name: 'baz',childs: [{id: 4,name: 'foobar'}]}]}];
function findObject(arr, id) {
var result;
var children = [];
for (let i = 0 ; i < arr.length; i++) {
if(arr[i].id === id) {
result = arr[i];
break;
}
if(arr[i].childs) {
children = [...children, ...arr[i].childs];
}
}
if(!result && children.length) {
result = findObject(children, id);
}
return result;
}
console.log(findObject(objects, 4));
You can have a recursive function and check for the child
var objects = [{
id: 1,
name: 'foo'
}, {
id: 2,
name: 'bar',
childs: [{
id: 3,
name: 'baz',
childs: [{
id: 4,
name: 'foobar'
}]
}]
}];
let tempArray = [];
function doRecursiveSearch(obj, id) {
obj.forEach(function(item) {
console.log(item)
if (item.id === id) {
tempArray.push(item)
} else {
if (item.childs && Array.isArray(item.childs)) {
console.log(item)
doRecursiveSearch(item.childs, id)
}
}
})
}
doRecursiveSearch(objects, 4)
console.log(tempArray)
oldList:
var oldList = [
{id:1, time:'2018-02-06 09:00-10:00', title:'aa'},
{id:2, time:'2018-02-06 11:00-12:00', title:'bb'},
{id:3, time:'2018-02-07 10:00:02', title:'cc'},
{id:4, time:'2018-02-07 09:00-10:00', title:'dd'}
];
console.log(oldList);
Desired:
var newList = [
{
'2018-02-06' : [
{id:1, time:'2018-02-06 09:00-10:00', title:'aa'},
{id:2, time:'2018-02-06 11:00-12:00', title:'bb'},
]
},
{
'2018-02-07' : [
{id:4, time:'2018-02-07 09:00-10:00', title:'dd'},
{id:3, time:'2018-02-07 10:00:02', title:'cc'},
]
},
];
console.log(newList);
How can I get the following result from this array and object?
I haven't found a good solution at the moment。
You can use reduce for this.
var oldList = [{
id: 1,
time: '2018-02-06 09:00-10:00',
title: 'aa'
},
{
id: 2,
time: '2018-02-06 11:00-12:00',
title: 'bb'
},
{
id: 3,
time: '2018-02-07 10:00:02',
title: 'cc'
},
{
id: 4,
time: '2018-02-07 09:00-10:00',
title: 'dd'
}
];
var newList = oldList.reduce(function(c, i) {
let t = i.time.split(" ")[0];
c[t] = c[t] || [];
c[t].push(i);
return c;
}, {});
console.log( newList );
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce
you can use lodash to do this.
var newList = _.groupBy(oldList, function(o) {
return o.time.split(" ")[0];
});
var newList = {};
for (var i = 0; i < oldList.length; i++) {
var item = oldList[i];
var key = item.time; //here you can transform the key as you like (ie remove time)
if (newList[key] == null) {
newList[key] = [];
}
newList[key].push(item );
}
I created a dictionary. For each item in your old list I check if in the new list exist a key with your timestamp. If not, create a new entry with an empty array. Then, in both case, push your item into the specific array
Here's a solution without using reduce.
var oldList = [{
id: 1,
time: '2018-02-06 09:00-10:00',
title: 'aa'
}, {
id: 2,
time: '2018-02-06 11:00-12:00',
title: 'bb'
}, {
id: 3,
time: '2018-02-07 10:00:02',
title: 'cc'
}, {
id: 4,
time: '2018-02-07 09:00-10:00',
title: 'dd'
}];
var uniqueDates = []
for (i in oldList) {
if (uniqueDates.indexOf(oldList[i]['time'].split(' ')[0]) == -1) {
uniqueDates.push(oldList[i]['time'].split(' ')[0])
}
}
var newList = []
for (i in uniqueDates) {
var val = {}
val[uniqueDates[i]] = []
for (j in oldList) {
if(oldList[j]['time'].split(' ')[0] == uniqueDates[i]){
val[uniqueDates[i]].push(oldList[j])
}
}
newList.push(val)
}
console.log(newList)
But I like #Eddie's answer better
I have an array of objects:
var array = [{
id: "cards",
amount: 5
}, {
id: "shirts",
amount: 3
}, {
id: "cards",
amount: 2
}, {
id: "shirts",
amount: 3
}]
What I need to do is loop through this array and find the total of all id types.
So in this example, I would find the total amount of cards and shirts.
I'm not sure how to do this with objects. I've tried stripping the objects down with Object.values(array), but is there a way to do it with the objects?
Thanks for your help.
This should do what you want:
var array = [
{ id: "cards", amount: 5 },
{ id: "shirts", amount: 3 },
{ id: "cards", amount: 2 },
{ id: "shirts", amount: 3 }
];
var result = array.reduce(function(entities, item) {
entities[item.id] = (entities[item.id] || 0) + item.amount;
return entities;
}, {})
console.log(result);
You would loop your array, check the id property for your target object, then enumerate and outer scope variable with the value stored in the amount property.
var totalShirts = 0;
var totalCards = 0;
for(var i = 0, len = array.length; i < len; i++){
var entry = array[i];
if(entry.id === "cards"){
totalCards += entry.amount;
}
else if(entry.id === "shirts"){
totalShirts += entry.amount;
}
}
console.log("Total Cards: " + totalCards);
console.log("Total Shirts: " + totalShirts);
Here is an example that gets the total of each item
var array = [{id:"cards", amount: 5}, {id:"shirts", amount: 3}, {id:"cards", amount: 2}, {id:"shirts", amount: 3}];
var result = array.reduce(function(accumulator, current) {
if (!(current.id in accumulator)) {
accumulator[current.id] = current.amount;
} else {
accumulator[current.id] += current.amount;
}
return accumulator;
}, {});
console.log(result);
A simple forEach will do the trick:
var counts = {}
array.forEach(v => {
counts[v.id] = (counts[v.id] || 0) + v.amount
})
console.log(counts)
will print:
{
cards: 7
shirts: 6
}
Here is a O(n) time solution.
var totals = new Object();
for(var i = 0;i < array.length;i ++) {
var id = array[i].id;
var amount = array[i].amount;
if(totals[id] == undefined) {
totals[id] = amount;
} else {
totals[id] += amount;
}
}
console.log(totals);
You can use for..of loop
var array = [{
id: "cards",
amount: 5
}, {
id: "shirts",
amount: 3
}, {
id: "cards",
amount: 2
}, {
id: "shirts",
amount: 3
}]
let res = {};
for (let {id,amount} of array) {
if (!res.hasOwnProperty(id)) res[id] = 0;
res[id] += amount;
}
console.log(res);
Use a for-loop to do this:
var totalCards = 0;
var totalShirt = 0;
for (var i = 0; i < arr.length; i++) {
if (arr[i].id === "cards") {
totalCards += arr[i].amount;
} else {
totalShirt += arr[i].amount;
}
}
Do the magic in for loop. This example should be general enough:
var array = [ {id:"cards", amount: 5}, {id:"shirts", amount: 3}, {id:"cards", amount: 2}, {id:"shirts", amount: 3} ];
var output = [];
for(var i of array) {
if(!output[i.id]) {
output[i.id] = 0;
}
output[i.id] += i.amount;
}
console.log(output);
var array = [{id:"cards", amount: 5}, {id:"shirts", amount: 3}, {id:"cards", amount: 2}, {id:"shirts", amount: 3}];
var arr = [];
array.forEach(v => arr.push(v.id));
var newArr = [...new Set(arr)];
var arr2 = [];
newArr.forEach(function(v) {
var obj = {};
obj.id = v;
obj.counter = 0;
arr2.push(obj);
});
arr2.forEach(v => array.forEach(c => c.id == v.id ? v.counter += c.amount : v));
console.log(arr2);
You can use Array.forEach() to iterate over each element of the array. The total object is an associative array where the index is the id field of array element objects.
var array = [{ id: "cards", amount: 5 },
{ id: "shirts", amount: 3 },
{ id: "cards", amount: 2},
{ id: "shirts", amount: 3 }];
var total = {};
array.forEach(function (el) {
if (total[el.id]) {
total[el.id] += el.amount
} else {
total[el.id] = el.amount
}
});
console.log(JSON.stringify(total));
You could use Array#reduce and sum the amount.
var array = [{ id: "cards", amount: 5 }, { id: "shirts", amount: 3 }, { id: "cards", amount: 2 }, { id: "shirts", amount: 3 }],
result = array.reduce(function (r, a) {
r[a.id] = (r[a.id] || 0) + a.amount;
return r;
}, {});
console.log(result);
You can use this code
if (!Object.keys) {
Object.keys = function (obj) {
var keys = [],
k;
for (k in obj) {
if (Object.prototype.hasOwnProperty.call(obj, k)) {
keys.push(k);
}
}
return keys;
};
}
then you can do this in older browsers as well:
var len = Object.keys(obj).length;
Problem:
I want to get inetrsection of array of objects.
var a = [{id: 1, name: 'jake'}];
var b = [{id: 1, name: 'jake'}, {id: 4,name: 'jenny'}];
var c = [{id: 1,name: 'jake'}, {id: 4,name: 'jenny'}, {id: 9,name: 'nick'}];
intersect (a,b,c);// Find Intersection based on id key
// answer would be [{id: 1, name: 'jake'}]
I found this very help answer here
How to use underscore's "intersection" on objects?
BUT
This solution uses underscore.js while i am using jquery.
I cant seems to know what _.any is doing.
Any Help will be appreciated.
Here is complete Code
CODE: http://jsfiddle.net/luisperezphd/43vksdn6/
function intersectionObjects2(a, b, areEqualFunction) {
var results = [];
for(var i = 0; i < a.length; i++) {
var aElement = a[i];
var existsInB = _.any(b, function(bElement) { return areEqualFunction(bElement, aElement); });
if(existsInB) {
results.push(aElement);
}
}
return results;
}
function intersectionObjects() {
var results = arguments[0];
var lastArgument = arguments[arguments.length - 1];
var arrayCount = arguments.length;
var areEqualFunction = _.isEqual;
if(typeof lastArgument === "function") {
areEqualFunction = lastArgument;
arrayCount--;
}
for(var i = 1; i < arrayCount ; i++) {
var array = arguments[i];
results = intersectionObjects2(results, array, areEqualFunction);
if(results.length === 0) break;
}
return results;
}
var a = [ { id: 1, name: 'jake' }, { id: 4, name: 'jenny'} ];
var b = [ { id: 1, name: 'jake' }, { id: 9, name: 'nick'} ];
var c = [ { id: 1, name: 'jake' }, { id: 4, name: 'jenny'}, { id: 9, name: 'nick'} ];
var result = intersectionObjects(a, b, c, function(item1, item2) {
return item1.id === item2.id;
});
This solution counts the same given objects with the same property and returns them if they in both of the arrays intersection().
function intersection(a, b, key) {
function count(a) {
o[a[key]] = o[a[key]] || { value: a, count: 0 };
o[a[key]].count++;
}
var o = {}, r = [];
a.forEach(count);
b.forEach(count);
Object.keys(o).forEach(function (k) {
o[k].count === 2 && r.push(o[k].value);
});
return r;
}
function intersect(a, b, c, key) {
return intersection(intersection(a, b, key), c, key);
}
var a = [{ id: 1, name: 'jake' }],
b = [{ id: 1, name: 'jake' }, { id: 4, name: 'jenny' }],
c = [{ id: 1, name: 'jake' }, { id: 4, name: 'jenny' }, { id: 9, name: 'nick' }],
result = intersect(a, b, c, 'id');
document.write('<pre>' + JSON.stringify(result, 0, 4) + '</pre>');
This works now with a callback in this style.
function (v) {
return v.id;
}
It needs to returns a stringable value and can contain other value and combinations like this example which intersects with name and age (if existing in the data):
function (v) {
return v.name + '|' + v.age;
}
function intersection(a, b, cb) {
function count(a) {
o[cb(a)] = o[cb(a)] || { value: a, count: 0 };
o[cb(a)].count++;
}
var o = {}, r = [];
a.forEach(count);
b.forEach(count);
Object.keys(o).forEach(function (k) {
o[k].count === 2 && r.push(o[k].value);
});
return r;
}
function intersect(a, b, c, key) {
return intersection(intersection(a, b, key), c, key);
}
var a = [{ id: 1, name: 'jake' }],
b = [{ id: 1, name: 'jake' }, { id: 4, name: 'jenny' }],
c = [{ id: 1, name: 'jake' }, { id: 4, name: 'jenny' }, { id: 9, name: 'nick' }],
result = intersect(a, b, c, function (_) { return _.id; });
document.write('<pre>' + JSON.stringify(result, 0, 4) + '</pre>');
Here is my answer:
Benefits are
it gives me freedom to intersect as many objects as i want
I can use Compare function where i can use equality or any logic I want
CODE:
var a = [ { id: 1, name: 'jake' } , { id: 4, name: 'jenny'}, { id: 9, name: 'nick'} ];
var b = [ { id: 1, name: 'jake' }, { id: 9, name: 'nick'} ];
var c = [ { id: 1, name: 'jake' }, { id: 4, name: 'jenny'}, { id: 9, name: 'nick'} ];
var intersectionObjects = function() {
var results = arguments[0];
var lastArgument = arguments[arguments.length - 1];
var arrayCount = arguments.length;
var areEqualFunction;
//Internal function
var _intersection_of_2_Objects = function(array1, array2, areEqualFunction) {
var result = []
$.each(array1, function(indexArray1, valueArray1) {
$.each(array2, function(indexArray2, valueArray2) {
if (areEqualFunction(valueArray1, valueArray2)) {
result.push(valueArray2)
}
});
});
return result;
};
//
if (typeof lastArgument === "function") {
areEqualFunction = lastArgument;
arrayCount--;
}
for (var i = 1; i < arrayCount; i++) {
var array = arguments[i];
results = _intersection_of_2_Objects(results, array, areEqualFunction);
if (results.length === 0) {
break;
}
}
return results;
};
Call it like :
var _intersect = intersectionObjects(b, c, a, function(valueArray1, valueArray2) {
return (valueArray1.name == valueArray2.name);
});
console.log(_intersect);
Suppose I have the following arrays:
var first = [
{ id: 1, name: 'first' },
{ id: 2, name: 'second' },
{ id: 3, name: 'third' }
]
var second = [
{ id: 2, field: 'foo2' },
{ id: 3, field: 'foo3' },
{ id: 4, field: 'foo4' }
]
var third = [
{ id: 2, data: 'some2' },
{ id: 5, data: 'some5' },
{ id: 6, data: 'some6' }
]
I want to merge them to get the following result:
var result = [
{ id: 1, name: 'first', field: undefined, data: undefined },
{ id: 2, name: 'second', field: 'foo2', data: 'some2' },
{ id: 3, name: 'third', field: 'foo3', data: undefined },
{ id: 4, name: undefined, field: 'foo4', data: undefined },
{ id: 5, name: undefined, field: undefined, data: 'some5' },
{ id: 6, name: undefined, field: undefined, data: 'some6' }
]
How could I do it with JavaScript?
You should get all existed keys and after create new Objects with fill "empty" keys:
function mergeArrays(){
var keys = {};
//save all existed keys
for(var i=arguments.length;--i;){
for(var j=arguments[i].length;--j;){
for(var key in arguments[i][j]){
keys[key] = true;
}
}
}
var res = [];
for(var i=arguments.length;--i;){
for(var j=arguments[i].length;--j;){
//set clone of object
var clone = JSON.parse(JSON.stringify(arguments[i][j]));
for(var key in keys){
if(!(key in clone)){
clone[key] = undefined;
}
}
res.push(clone);
}
}
return res;
}
https://jsfiddle.net/x3b0tk3g/
There is no simple solution for what you want. Here is my suggestion.
var first = [
{ id: 1, name: 'first' },
{ id: 2, name: 'second' },
{ id: 3, name: 'third' }
]
var second = [
{ id: 2, filed: 'foo2' },
{ id: 3, field: 'foo3' },
{ id: 4, field: 'foo4' }
];
var third = [
{ id: 2, data: 'some2' },
{ id: 4, data: 'some4' },
{ id: 6, data: 'some6' }
];
var result = {};
first.concat(second,third).forEach(function(item){
var id = item.id;
var row = result[id];
if(!row){
result[id] = item;
return;
}
for(var column in item){
row[column] = item[column];
}
});
var finalResult = Object.keys(result).map(function(id){
return result[id];
});
console.log(finalResult);
fiddle: http://jsfiddle.net/bs20jvnj/2/
function getByProperty(arr, propName, propValue) {
for (var i = 0; i < arr.length; i++) {
if (arr[i][propName] == propValue) return arr[i];
}
}
var limit = first.length + second.length + third.length;
var res = [];
for (var i = 1; i < limit; i++) {
var x = $.extend({}, getByProperty(first, "id", i), getByProperty(second, "id", i), getByProperty(third, "id", i));
console.log(x["id"]);
if (x["id"] === undefined) x["id"] = i;
res.push(x);
}
console.log(res);
There's probably a shorter way to solve this, but this covers all the steps, including ensuring that there are default properties that are undefined if not found. It also takes any number of input arrays, and you can specify what default keys you require if they're not already covered by the keys in the existing objects, so pretty future-proof for your needs.
// merges the key/values of two objects
function merge(a, b) {
var key;
if (a && b) {
for (key in b) {
if (b.hasOwnProperty(key)) {
a[key] = b[key];
}
}
}
return a;
}
function concatenate() {
var result = [];
var args = arguments[0];
for (var i = 0, l = args.length; i < l; i++) {
result = result.concat(args[i]);
}
return result;
}
// return a default object
function getDefault() {
return {
id: undefined,
name: undefined,
data: undefined,
field: undefined
};
}
// loop over the array and check the id. Add the id as a key to
// a temporary pre-filled default object if the key
// doesn't exist, otherwise merge the existing object and the
// new object
function createMergedArray(result) {
var temp = {};
var out = [];
for (var i = 0, l = result.length; i < l; i++) {
var id = result[i].id;
if (!temp[id]) temp[id] = getDefault();
merge(temp[id], result[i]);
}
// loop over the temporary object pushing the values
// into an output array, and return the array
for (var p in temp) {
out.push(temp[p]);
}
return out;
}
function mergeAll() {
// first concatenate the objects into a single array
// and then return the results of merging that array
return createMergedArray(concatenate(arguments));
}
mergeAll(first, second, third);
DEMO