unEven an array, like the concatAll function - javascript

I have this 2 Arrays representing the same data:
array1 = [
{name: "max", age: 30},
{name: "paul"},
{name: "paul.Jack", age: 25},
{name: "max.David"},
{name: "max.Sylvia", age: 27},
{name: "paul.Jack.Ned"},
{name: "paul.Jack.Mike"},
{name: "max.David.Jeff"},
{name: "max.Sylvia.Anna", age: 5},
{name: "max.David.Buffy"},
{name: "max.Sylvia.Craig"},
{name: "max.Sylvia.Robin"}
];
array2 = [
{
name: "max",
age: 30,
children: [
{
name: "Sylvia",
age: 27,
children: [
{name: "Anna", age: 5},
{name: "Craig"},
{name: "Robin"}
]
},
{
name: "David",
children: [
{name: "Jeff"},
{name: "Buffy"}
]
}
]
},
{
name: "paul",
children: [
{
name: "Jack",
age: 25,
children: [
{name: "Ned"},
{name: "Mike"}
]
}
]
}
];
my objective is to have a function unEven which transform the array1 to the array2, I'm using lodash to do this here's where I'm now:
To unEven array1 to array2 the tracking property in this case 'name' contain the information about where in the new array this item should be pushed check this js bin, this work for 3 level nested array but my goal is to have it unEven arrays without limit of their nesting level, that's where I'm stuck.
The unEven function will be like the concatAll function presented in this video.
Here's the unEven function:
Array.prototype.unEven = function (trackingProperty, children, resultArray) {
var newItem, prop, index, parent, subParent, subParentIndex;
resultArray = resultArray || [];
this.forEach(function (item) {
newItem = lodash.omit(item, trackingProperty);
prop = lodash.pick(item, trackingProperty);
if (prop[trackingProperty].indexOf('.') === -1) {
resultArray.push(item);
} else {
newItem[trackingProperty] = prop[trackingProperty].split(".");
parent = {};
parent[trackingProperty] = newItem[trackingProperty][0];
index = lodash.indexOf(resultArray, lodash.find(resultArray, parent));
if (newItem[trackingProperty].length === 2) {
newItem[trackingProperty] = newItem[trackingProperty][1];
if (!Array.isArray(resultArray[index][children])) {
resultArray[index][children] = [];
}
resultArray[index][children].push(newItem);
} else if (newItem[trackingProperty].length === 3) {
subParent = {};
subParent[trackingProperty] = newItem[trackingProperty][1];
subParentIndex = lodash.indexOf(resultArray[index][children], lodash.find(resultArray[index][children], subParent));
newItem[trackingProperty] = newItem[trackingProperty][2];
if (!Array.isArray(resultArray[index][children][subParentIndex][children])) {
resultArray[index][children][subParentIndex][children] = [];
}
resultArray[index][children][subParentIndex][children].push(newItem);
}
}
});
return resultArray;
};
To give my use case for this I'm trying to make a d3js tree layout of angular ui router in my application that will be generated from the routes JSON file since I make the routes in a JSON file.

Here's a lodash-heavy solution that I think does what you want. You can skip the cloning if you don't care about mutating the original array, and skip the sorting if you always have children after their parents in the original array.
function unEven(array1) {
_(_.clone(array1, true)) //begin chaining syntax, with cloned array
.each(function(person) {
person.name = person.name.split(".") //replace name with array of names
})
.sortBy(function(person) {return person.name.length}) //sort so that we handle children after parents
.reduce(function(result, person) {
var parentArray = result;
var name = person.name.pop() //their actual name is the last one in the list
_.each(person.name, function(parentName) { //loop through parent names to find the proper array to add to
var parent = _.find(parentArray, {name: parentName});
if(!parent) throw new Error(name + " has non-existent parent "+parentName);
parentArray = parent.children = parent.children || []
})
person.name = name; //return name back to just being their name
parentArray.push(person);
return result;
}, []) //reduce ends chaining syntax; so no need for .value
}

Related

creating Object with different data

I was wondering if there's a way to create an object with changeable data.
Eg:
pearson = {
name: "",
age: 0
}
Can I do something like a loop that each time it changes these 2 variables and assign the whole object with the data into an array, but each time the object will hold the updated data?
Something like:
let pearson = { name: '', age: 20 }
const pearsonsList = [{name: 'dave', age: 20}, {name: 'jessy', age: 30}]
let arr = []
pearsonsList.forEach((e) =>{
pearson.name = e.name
pearson.age = e.age
arr.push(pearson)
})
console.log(arr[0])
/* this what arr[0] holds
{
"name": "jessy",
"age": 30
}*/
how to make arr[0] have a different value than arr[1] where the object data always get overridden ?
The main problem here is your arr is storing the reference to your pearson object, which gets modified each time you set it, even after you push them into your arr.
A solution would be to construct a new object each time before pushing it in.
const pearsonsList = [{name: 'dave', age: 20}, {name: 'jessy', age: 30}]
let arr = []
pearsonsList.forEach((e) =>{
let pearson = { name: '', age: 20 }
pearson.name = e.name
pearson.age = e.age
arr.push(pearson)
})
An optional, and cleaner solution would be to use .map instead of .forEach too
const pearsonsList = [{name: 'dave', age: 20}, {name: 'jessy', age: 30}]
let arr = pearsonsList.map((e) =>{
let pearson = { name: '', age: 20 }
pearson.name = e.name
pearson.age = e.age
return person;
})

Why Array helper Filter is returning a null value

I am new to JavaScript and while learning it trying to filter an employee based on education but my filter is returning a null value. Can anyone help me understand why is this so?
var employeeEdu = [{education: 'Masters'}];
var employees = [{id: 1, age: 35, name: 'James', dept: 'IT', education: 'Masters'},
{id: 2, age: 25, name: 'David', dept: 'Accounts', education: 'High School'},
{id: 3, age: 45,name: 'Tim', dept: 'HR', education: 'Graduate'},
{id: 4, age: 50,name: 'Vinod', dept: 'IT', education: 'PHD'}];
function chooseQualified(arrEmployee, empEducation) {
return arrEmployee.filter(function(emp) {
return emp.education === empEducation.education;
// return emp.education === 'Masters';
});
}
console.log(chooseQualified(employees, employeeEdu));
It's because employeeEdu is an array, and employeeEdu.education is undefined. What you need to do is to checkout employeeEdu[0].education:
var employeeEdu = [{education: 'Masters'}];
var employees = [{"id":1,"age":35,"name":"James","dept":"IT","education":"Masters"},{"id":2,"age":25,"name":"David","dept":"Accounts","education":"High School"},{"id":3,"age":45,"name":"Tim","dept":"HR","education":"Graduate"},{"id":4,"age":50,"name":"Vinod","dept":"IT","education":"PHD"}];
function chooseQualified(arrEmployee, empEducation) {
return arrEmployee.filter(function(emp) {
return emp.education === empEducation[0].education;
// return emp.education === 'Masters';
});
}
console.log(chooseQualified(employees, employeeEdu));
Another solution is to remove the wrapping array:
employeeEdu = {education: 'Masters'};
Like #Ori Drori says, employeeEdu is an array, so empEducation.education is undefined, but empEducation[0].education is 'Masters'. I suggest that employeeEdu be a object instead of array, like below code. var employeeEdu = {education: 'Masters'};
var employeeEdu = {education: 'Masters'};
var employees = [{id: 1, age: 35, name: 'James', dept: 'IT', education: 'Masters'},
{id: 2, age: 25, name: 'David', dept: 'Accounts', education: 'High School'},
{id: 3, age: 45,name: 'Tim', dept: 'HR', education: 'Graduate'},
{id: 4, age: 50,name: 'Vinod', dept: 'IT', education: 'PHD'}];
function chooseQualified(arrEmployee, empEducation) {
return arrEmployee.filter(function(emp) {
return emp.education === empEducation.education;
// return emp.education === 'Masters';
});
}
console.log(chooseQualified(employees, employeeEdu));
/* #Params:
* array0 [Array of Objects]: Array to search through.
* array1 [Array of Objects]: Array with the key/value to search for.
* key [String]: Key of the object in array1.
* index [Number](optional): Index of array1. default: 0.
*/
// Returns a new array of objects witch matched by key/value from the two given arrays.
function findByKV(array0, array1, key, index = 0) {
// Get the value of the key to be searched for
var value = array1[index][key];
// Filter the array to be searched obj is each Object in array1
filter()
array0.filter(function(obj) {
// Get each obj key of array0 and ...
Object.keys() .some()
return Object.keys(obj).some(function(key) {
// ...return true if at least one String value matches the value of the key in array1
.toString() .indexOf()
return obj[key].toString().indexOf(value) != -1;
var target0 = [{education: 'Masters'}];
var target1 = [{dept: 'IT',education: ''}];
var employees = [
{id: 1,age: 35,name: 'James',dept: 'IT',education: 'Masters'},
{id: 2,age: 25,name: 'David',dept: 'Accounts',education: 'High School'},
{id: 3,age: 45,name: 'Tim',dept: 'HR',education: 'Graduate'},
{id: 4,age: 50,name: 'Vinod',dept: 'IT',education: 'PHD'},
{id: 5,age: 46,name: 'Matt',dept: 'IT',education: 'Masters'}
];
function findByKV(array0, array1, key, index = 0) {
var value = array1[index][key];
var array2 = array0.filter(function(obj) {
return Object.keys(obj).some(function(key) {
return obj[key].toString().indexOf(value) != -1;
});
});
return array2;
}
var result0 = findByKV(employees, target0, 'education');
var result1 = findByKV(employees, target1, 'dept');
console.log('Found targrt0: ' + JSON.stringify(result0, null, 2));
console.log('Found target1: ' + JSON.stringify(result1, null, 2));

Find element and replace it

I want to make a function (or use library) that will search the array of objects find specific one by its property and replace it with the other object. E.g.:
var a = {name: "Jane", age: 29}
var arr = [{name: "Chris", age: 20}, {name: "Jane", age: 45}]
arr.find(person => { if (a.name === person.name) {person = a})
Is there such a function?
Edit:
It would be nice if there is no matched object in array it would push it to an array that object
I can only think of Array#map
var a = {name: "Jane", age: 29}
var arr = [{name: "Chris", age: 20}, {name: "Jane", age: 45}]
arr = arr.map(function(o){
// if names match, return new object
// otherwise, return original object
return o.name == a.name ? a : o;
});
console.log( arr );
You are probably looking for a wrapper around splice.
Your example would look like this:
arr.forEach((elem, index) => {
if (elem.name !== a.name) return
arr.splice(index, 1, a)
})
You could use the find() method, as such:
var jane = {name: "Jane", age: 29}
var persons = [
{name: "Chris", age: 20},
{name: "Jane", age: 45}
];
function findPerson(person) {
return function(element, index, array) {
if(person.name === element.name) {
array[index] = person;
return true;
}
return false;
}
}
persons.find(findPerson(jane));
This would replace the matched person if found and else returns undefined. So you could check if undefined is returned and add the person afterwards.
Since you want to modify the original array, you can use array#forEach(), just iterate through the array and replace the object which matches your a object name.
var a = {name: "Jane", age: 29}
var arr = [{name: "Chris", age: 20}, {name: "Jane", age: 45}]
arr.forEach((person,index) => {
if (a.name === person.name)
arr[index] = a;
})
console.log(arr);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Array of Objects in Javascript on the basis of an element

Lets Suppose I have 1000's of objects. Now I want to store objects with same name in an array So that I'll have multiple arrays having objects with same name. How can this be done in JavaScript?
Data can be like this
var input = [
{ name: 'ABC', age: 12 },
{ name: 'XYZ', age: 13 },
{ name: 'ABC', age: 14 },
{ name: 'XYZ', age: 15 },
];
var output = {};
input.forEach(function(obj) {
var array = output[obj.name];
if (!array) {
array = output[obj.name] = [];
}
array.push(obj)
})
for(name in output) {
console.log('There are ' + output[name].length + ' people named ' + name);
}
In javascript, objects aren't copied into arrays. They exist in memory and when added to a an array the reference to that object is what lives in an array.
In the following code, myObj === arr1[0] === arr2. Which means the following is true:
var myObj = {name:'Dave', age: 55};
var arr1 = [myObj];
var arr2 = [myObj];
arr2[0].age = 44;
console.log(myObj.age);
// prints 44
console.log(arr1[0].age);
// prints 44
So to get what you need, you just need to organize your data into arrays. In javascript, you can filter arrays:
// original data
var data = [
{name: 'ABC', age: 12},
{name: 'XYZ', age: 13},
{name: 'ABC', age: 14},
{name: 'XYZ', age: 15},
{name: 'XYZ', age: 16},
];
// this returns a function to be used in Array.filter to filter for objects with the specified name
function nameFilter(name) {
return function(datum) {
return datum.name === name;
}
}
// filter for each type
var abcPeople = data.filter(nameFilter('ABC'));
var xyzPeople = data.filter(nameFilter('XYZ'));
console.log(data.length);
//5
console.log(abcPeople.length);
//2
console.log(xyzPeople.length);
//3
If you run the above code, you would have 3 arrays and all object in abcPeople would also be in data such that any change to one would be reflected in the other. The same is true for xyzPeople. Just to be clear, filter creates a new array, so data is not modified.
UPDATE: Added example where lists are dynamically determined
// original data
var data = [
{name: 'ABC', age: 12},
{name: 'XYZ', age: 13},
{name: 'ABC', age: 14},
{name: 'XYZ', age: 15},
{name: 'XYZ', age: 16},
];
var sortedData = {};
data.forEach(function(datum){
// initializes an array for any unseen name
if(!sortedData[datum.name]) {
sortedData[datum.name] = [];
}
// add the datum to the list for its name
sortedData[datum.name].push(datum);
});
// all names
console.log(Object.keys(sortedData));
// "ABC", "XYZ"
// items named "ABC"
console.log(sortedData['ABC']);
// [{"name": "ABC","age": 12}, {"name": "ABC","age": 14}]

Merge objects on unique combinations of key-value pairs

I would like a function
combineListOnKeys(listOfObjs, listOfKeys)
that will take this:
var listOfObjs =
[
{ name: john, state: ny, age: 12}
, { name: john, state: ny, age: 22}
, { name: john, state: dc, age: 32}
, { name: john, state: dc, age: 42}
, { name: paul, state: ca, age: 52}
]
var listOfKeys = ["name", "state"]
and returns this:
combineListOnKeys(listOfObjs, listOfKeys)
[
{ "name": john, "state": ny, "age": [12, 22]}
,{ "name": john, "state": dc, "age": [32, 42]}
,{ "name": paul, "state": ca, "age": [52]}
]
I'm essentially looking to match on multiple specified keys that all these objects share, and take the remaining unspecified keys and combine them into a list, thus removing some duplicate information.
I'm using underscore.js, but I cannot find an example of this problem in the docs. Thanks in advance!
Sorry this doesn't conform with your revised requirements for a function but I got started before you revised and put in a lot of effort and I hope this is enough for you to put together your own function. Using underscore's _.reduce and _.each methods in succession (and the _.each can probably be replaced with a second _.reduce, or with _.map -- as usual there's more than one way to do it).
var arr = [
{ name: 'john', state: 'ny', age: 12}
, { name: 'john', state: 'ny', age: 22}
, { name: 'john', state: 'dc', age: 32}
, { name: 'john', state: 'dc', age: 42}
, { name: 'paul', state: 'ca', age: 52}
];
var resultsMap = _.reduce(arr, function(memo, arrEl) {
/*
* var key = JSON.stringify(_.omit(arrEl, 'age'));
*
* From original answer but naively fails to account for Javascript objects not returning in order.
* See "IIFE" below and http://stackoverflow.com/a/28989092/34806
*/
var key = (function() {
var ageOmittedObj = _.omit(arrEl, 'age');
var ageOmittedPairs = _.pairs(ageOmittedObj);
var sortedPairs = _.reduce(_.keys(ageOmittedObj).sort(), function(sortedPairs, key) {
var pair = _.find(ageOmittedPairs, function(kvPair) {return kvPair[0] == key});
sortedPairs.push(pair);
return sortedPairs;
}, []);
return JSON.stringify(sortedPairs)
}) ();
memo[key] = memo[key] || {};
memo[key].ages = memo[key].ages || [];
memo[key].ages.push(arrEl.age);
return memo;
}, {});
var resultsArr = [];
_.each(resultsMap, function(v, k) {
var resultObj = {};
var nameStatePairs = JSON.parse(k);
var nameStateObj = _.object(_.map(nameStatePairs, function(pair){return [pair[0], pair[1]]}));
// compare above to http://stackoverflow.com/a/17802471/34806
resultObj.name = nameStateObj.name;
resultObj.state = nameStateObj.state;
resultObj.age = v.ages;
resultsArr.push(resultObj);
});
console.log(JSON.stringify(resultsArr));
// [{"name":"john","state":"ny","age":[12,22]},{"name":"john","state":"dc","age":[32,42]},{"name":"paul","state":"ca","age":[52]}]
Not in underscore.js, but plain JS. Which should work fine since underscore is a library running on JavaScript.
I would output a new array using array.prototype.map combined with a for loop that test the new array for multiples. Since this is only one dimension deep, we do not need recursion.
var arr = [
{ name: "john", state: "ny", age: 12}
, { name: "john", state: "ny", age: 22}
, { name: "john", state: "dc", age: 32}
, { name: "john", state: "dc", age: 42}
, { name: "paul", state: "ca", age: 52}
]
var arr2d2 = []; //new array that is going to contain the merged values.
arr.map(function(element){
var outerElement = element;
var found = false; //set initially to false. If not found add element to the new array.
for (var i = 0; i < arr2d2.length; i++)
{
if (arr2d2[i].name == outerElement.name && arr2d2[i].state == outerElement.state)
{
found = arr2d2[i]; // save the element.
break; //stop the loop
}
};
if (found)
{
if (found.age != outerElement.age)
{
found.age.push(outerElement.age); //push the age to the new value.
}
}
else
{
outerElement.age = [outerElement.age]; //convert age to an array, like you specified.
arr2d2.push(outerElement); //not found yet. push element;
}
});
document.body.innerHTML += JSON.stringify(arr2d2); //only to display the result. Not part of the solution.

Categories