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
Related
I've an array of objects like this:
arrObj = [{
id: 1
data: {
info: {
name: 'jhon'
}
}
},{
id: 1
data: {
info: {
name: 'jane'
}
}
},{
id: 1
data: {
info: {
name: 'jhon'
}
}
}]
And I needs get a summary of occurrences for different values, like this:
{ jane: 1, jhon: 2 }
The big problem is that I need pass the nested prop dynamically:
getSummary('data.info.name',obj) //--> { jane: 1, jhon: 2 }
Any ideas?
You can use the below code, this is just hint. you need to do error handling if some input is not having correct nested keys.
let arrObj = [{
id: 1,
data: {
info: {
name: 'jhon'
}
}
},{
id: 1,
data: {
info: {
name: 'jane'
}
}
},{
id: 1,
data: {
info: {
name: 'jhon'
}
}
}]
const getSummary = (dynamicKeys,obj) => {
const list = dynamicKeys.split('.');
const op = {};
for (let i = 0; i < obj.length; i++) {
let n = 1, key = obj[i][list[0]];
while (list.length > n) {
key = key[list[n]];
n++;
}
op[key] = op[key] ? op[key] + 1 : 1;
}
return op;
}
const test = getSummary('data.info.name', arrObj);
console.log(test)
A possible solution could be as below. Here at first given prop is found out from each element of arrayObj. If the finding isn't successful, the element is skipped and move to next. When the finding is successful, append the finding value to summary if it does not exist in summary or increment the existing value. You can change the code as your requirements.
const arrObj = [{
id: 1,
data: {
info: {
name: 'jhon'
}
}
}, {
id: 1,
data: {
info: {
name: 'jane'
}
}
}, {
id: 1,
data: {
info: {
name: 'jhon'
}
}
}];
const getSummary = (prop, arr) => {
const keys = prop.split('.');
const findPropValue = (elem) =>
keys.reduce((val, key, index) => {
if (index === 0) return elem[key];
return (val && val[key]) || val
}, null);
return arr.reduce((sum, curr) => {
const key = findPropValue(curr);
if (!key) return sum;
sum[key] = (sum[key] && sum[key] + 1) || 1;
return sum;
}, {});
};
console.log(getSummary('data.info.name', arrObj));
Go over elements using forEach. For each object, access the value and build a res object with keys as value (eg jane) and object values are aggregated.
[Access the value, by split the path, access object nested using reduce)
const getSummary = (path, items) => {
const paths = path.split(".");
const res = {};
items.forEach((item) => {
const value = paths.reduce((acc, cur) => acc[cur], item);
res[value] = (res[value] ?? 0) + 1;
});
return res;
};
arrObj = [
{
id: 1,
data: {
info: {
name: "jhon",
},
},
},
{
id: 1,
data: {
info: {
name: "jane",
},
},
},
{
id: 1,
data: {
info: {
name: "jhon",
},
},
},
];
const output = getSummary("data.info.name", arrObj);
console.log(output);
I have the following array:
[{
name: 'foo',
values: '10,12'
},
{
name: 'bar',
values: 'red,blue'
}]
Using some javascript logic I would like to output the following array:
[{
option1: 10,
option2: 'red'
},
{
option1: 10,
option2: 'blue'
},
{
option1: 12,
option2: 'red'
},
{
option1: 12,
option2: 'blue'
}]
What is the best and correct way to achieve this using javascript?
Lets say your first array is named arr.
var arr = [{
name: 'foo',
values: '10,12'
},
{
name: 'bar',
values: 'red,blue'
}];
var v1 = arr[0].values.split(',');
var v2 = arr[1].values.split(',');
var res = new Array();
for(i in v1){
for(j in v2){
res.push({'option1':v1[i],'option2':v2[j]});
}
}
console.log(res);
Here's an approach that can handle an arbitrary number of objects.
function valuesCrossProduct(input) {
return input.flatMap((current, index, array) => {
let result = [];
let values = current.values.split(',');
for (let v of values) {
for (let i = 0; i < array.length; i++) {
if (i <= index) {
// Skip creating cross products with self (i.e. == index)
// and with previously visited objects (i.e. < index).
continue;
}
let iValues = array[i].values.split(',');
let currentKey = `option${index}`;
let iKey = `option${i}`;
for (let iv of iValues) {
result.push({
[currentKey]: v,
[iKey]: iv,
});
}
}
}
return result;
});
}
let twoElementArray = [{
name: 'foo',
values: '10,12'
},
{
name: 'bar',
values: 'red,blue',
}];
let threeElementArray = [{
name: 'foo',
values: '10,12'
},
{
name: 'bar',
values: 'red,blue',
},
{
name: 'baz',
values: 'wham,bam',
}];
console.log(valuesCrossProduct(twoElementArray));
console.log(valuesCrossProduct(threeElementArray));
Functional for the win.
Note: as it is, this only works for an array of two objects, with any number of values in each, where the first set of values are numbers and the second set are strings, which is what you described above.
const arr = [{
name: 'foo',
values: '10,12'
},
{
name: 'bar',
values: 'red,blue'
}];
const values = arr
.map(o => o.values.split(','))
.reduce((cur, next) => {
return cur.map(c => {
return next.map(n => {
return {
option1: parseInt(c),
option2: n
};
});
}).flat();
});
console.log(values);
If you need generic approach to get possible options from various values.
const options = data => {
let sets = [[]];
data.forEach(({ values }, i) => {
const new_set = [];
values.split(",").forEach(value => {
new_set.push(
Array.from(sets, set => [...set, [`option${i + 1}`, value]])
);
});
sets = new_set.flatMap(set => set);
});
return sets.map(set => Object.fromEntries(set));
};
const data = [
{
name: "foo",
values: "10,12"
},
{
name: "bar",
values: "red,blue,green"
},
{
name: "test",
values: "top,bottom"
}
];
console.log(options(data));
This is my array object
var item = [
{index:1, name: 'miraje'},
{index:2, name: 'alamin'},
{index:3, name: 'behestee'},
{index:4, name: 'arif'},
{index:5, name: 'riad'}
];
when i delete an object like index: 2 , and that time i want to update my index value like ..
var item = [
{ index: 1, name: 'miraje'},
{ index: 2, name: 'behestee'},
{ index: 3, name: 'arif'},
{ index: 4, name: 'riad'}
];
After you remove element you can use forEach() loop to change indexes.
var item = [
{index:1, name: 'miraje'},
{index:2, name: 'alamin'},
{index:3, name: 'behestee'},
{index:4, name: 'arif'},
{index:5, name: 'riad'}
];
item.splice(1, 1)
item.forEach((e, i) => e.index = i + 1)
console.log(item)
Remove the object and alter the index property of each object,
DEMO
i=1;
var item = [
{index:1, name: 'miraje'},
{index:2, name: 'alamin'},
{index:3, name: 'behestee'},
{index:4, name: 'arif'},
{index:5, name: 'riad'}
];
console.log(item);
delete item[ 2 ];
console.log(item);
item.forEach(function(obj) {
obj.index = i;
debugger;
i++;
});
console.log(item);
Basically, you need to find the item with index, delete it, and to update all following items.
function deleteItem(array, index) {
var i = 0, found = false;
while (i < array.length) {
if (found) {
--array[i].index;
++i;
continue;
}
if (found = array[i].index === index) {
array.splice(i, 1);
continue;
}
++i;
}
}
var items = [{ index: 1, name: 'miraje' }, { index: 2, name: 'alamin' }, { index: 3, name: 'behestee' }, { index: 4, name: 'arif' }, { index: 5, name: 'riad' }];
deleteItem(items, 2);
console.log(items);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Just for a variety, without modifying the original array an efficient O(n) approach would also be as follows. We can also provide a range to delete the elements as well...
The range is provided supplied with the array indices, not the object index properties.
var item = [
{index:1, name: 'miraje'},
{index:2, name: 'alamin'},
{index:3, name: 'behestee'},
{index:4, name: 'arif'},
{index:5, name: 'riad'}
];
function deleteObjectsFromArray(a,j,k){
return a.reduceRight((p,c,i) => i >= j && i < k ? p
: i >= k ? (c.index -= k-j, p[c.index-1] = c, p)
: (p[c.index-1] = c, p),[]);
}
console.log(deleteObjectsFromArray(item,2,4));
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);
I'm attempting to apply a filter to a JSON array using another array as my filter source. What would be the most efficient way to do that?
This is my code: my goal is to extract all elements of employees array where employee ids are the same as in employeeFilter array. Similar to SQL, where employeeId in (1,2).
var employees = [{
id: 1,
name: "john"
}, {
id: 2,
name: "paul"
}, {
id: 3,
name: "mary"
}];
var employeeFilter = [1, 2];
emps = $.grep(employees, function (value, i) {
for (j = 0; j < employeeFilter.length; j++) {
return (value.id == employeeFilter[j]); // not working
}
});
The most efficient would probably be to not use jQuery at all, but something like Array.filter
var employees = [{
id: 1,
name: "john"
}, {
id: 2,
name: "paul"
}, {
id: 3,
name: "mary"
}];
var employeeFilter = [1, 2];
var emps = employees.filter(function(employee) {
return employeeFilter.indexOf(employee.id) != -1;
});
FIDDLE
Its because you are always returning in first iteration of your employeeFilter, without ever matching to other values in the employeeFilter. A minor change, and it will work as required:
var employees = [{
id: 1,
name: "john"
}, {
id: 2,
name: "paul"
}, {
id: 3,
name: "mary"
}];
var employeeFilter = [1, 2];
emps = $.grep(employees, function (value) {
var result = false;
for (j = 0; j < employeeFilter.length; j++) {
result = (value.id == employeeFilter[j]);
if(result)
break;
}
return result;
});
demo:http://jsfiddle.net/ugze6dew/