Loop through multidimensional array with all unique IDs - javascript

I have a multidimensional array but the ID's are unique across parents and children, so I have a problem looping through using a for loop. The problem is that I cannot seem to grab the ID of the children. How do you think I should handle this?
var Options = [
{
id: 0,
children: []
},
{
id: 2,
children: []
},
{
id: 3,
children: [
{
id: 4,
children: []
},
{
id: 5,
children: []
},
{
id: 6,
children: []
}
]
},
{
id: 7,
children: [
{
id: 8,
children: []
},
{
id: 9,
children: []
}
]
}
];
I have kept the code concise for the sake of brevity. What I am trying to do is iterate through the array to compare ID's.

This does not look like a "multidimensional array", but rather like a tree. Looping one level can be done with a simple for-loop:
for (var i=0; i<Options.length; i++) // do something
To loop the tree in-order, you will need a recursive function:
function loop (children, callback) {
for (var i=0; i<children.length; i++) {
callback(children[i]);
loop(children[i].children, callback);
}
}
loop(Options, console.log);
To get all children by their id, so that you can loop through the ids (regardless of the tree structure), use a lookup table:
var nodesById = {};
loop(Options, function(node) {
nodesById[node.id] = node;
});
// access:
nodesById[4];
…and to loop them sorted by id, you now can do
Object.keys(nodesById).sort(function(a,b){return a-b;}).forEach(function(id) {
var node = nodesById[id];
// do something
});

How about recursion?
var findById = function (arr, id) {
var i, l, c;
for (i = 0, l = arr.length; i < l; i++) {
if (arr[i].id === id) {
return arr[i];
}
else {
c = findById(arr[i].children, id);
if (c !== null) {
return c;
}
}
}
return null;
}
findById(Options, 8);

Ah, use recursion :D
var Options = "defined above";//[]
var OptionArray = []; //just as an example (not sure what you want to do after looping)
(function looper(start){
for( var i = 0, len = start.length; i < len; i++ ){
var currentOption = start[i];
if( currentOption.id > 3 ){//could be more complex
OptionArray.push(currentOption);
}
if( currentOption.children.length > 0 ){
looper(currentOption.children);
}
}
})(Options);

Related

Moving an object inside an array ( Javascript)

I have an array of objects something like this
arr = [{"class":"section"},{"fieldGroup":[]},{"class":"col","name":"input"},{"class":"col","name":"dropdown"},{"class":"col","name":"date"},{"fieldGroup":[]},{"class":"col","name":"table"}]
Now I need to move the objects with "class":"col" inside its previous object which contains "fieldGroup"
So my desired output should be like this
arr = [{"class":"section"},{"fieldGroup":[{"class":"col","name":"input"},{"class":"col","name":"dropdown"},{"class":"col","name":"date"}]},{"fieldGroup":[{"class":"col","name":"table"}]}]
I have tried this piece of code
arr.forEach((item: any, index: number) => {
if (item.class === "col") {
arr[index - 1].fieldGroup.push(item);
arr.splice(index, 1);
}else {
return arr;
}
})
but getting an error saying cannot read property push of undefined
any help?
Your problem is that when you remove an item from your array using .splice() all the items in your arr shift down one index. For example, the item that used to be at index 3 will now be at index 2, the item that used to be at index 4 is will now be at index 3 etc. Your .forEach() loop doesn't take this shift into account, and so once you remove the item at index 2 with .splice() your index changes to 3 for the next iteration, but really, you need to look at index 2 again as your array items have shifted down an index. You can fix this by using a standard for loop and decrementing the index counter when you remove an item to look at th inded again:
const arr = [{"class":"section"},{"fieldGroup":[]},{"class":"col","name":"input"},{"class":"col","name":"dropdown"},{"class":"col","name":"date"},{"fieldGroup":[]},{"class":"col","name":"table"}];
for(let i = 0; i < arr.length; i++) {
const item = arr[i];
if (item.class === "col") {
arr[i - 1].fieldGroup.push(item);
arr.splice(i, 1);
i--;
}
}
console.log(arr);
try it ...
var arr = [{"class":"section","name":"input"},{"fieldGroupName":"one","fieldGroup":[]},{"class":"col","name":"input"},{"class":"col","name":"dropdown"},{"class":"section","name":"input"},{"fieldGroupName":"one","fieldGroup":[]},{"class":"col","name":"date"}]
arr.forEach((elm , index) =>{
if(elm.class == 'col'){
arr[1].fieldGroup.push(elm)
arr.splice(index, 1)
}
})
console.log(arr);
Try this:
var arr = [{
"class": "section"
},
{
"fieldGroup": []
},
{
"class": "col",
"name": "input"
},
{
"class": "dropdown"
},
{
"class": "date"
},
{
"fieldGroup": []
},
{
"class": "col",
"name": "table"
}
];
let fieldGroupPosition;
let finalArr = [];
let lastGroupPosition = null;
arr.forEach((item, index) => {
if (Array.isArray(item.fieldGroup)) {
finalArr.push({
fieldGroup: []
});
lastGroupPosition = finalArr.length - 1;
} else {
if (lastGroupPosition)
finalArr[lastGroupPosition].fieldGroup.push(item);
else
finalArr.push(item);
}
});
console.log(finalArr);
var a = [
{ class: "section" },
{ fieldGroup: [] },
{ class: "col", name: "input" },
{ class: "dropdown" },
{ class: "date" },
{ fieldGroup: [] },
{ class: "col", name: "table" },
];
var res = [];
var currFieldGroup = null;
for (var i in a) {
if ("fieldGroup" in a[i]) {
currFieldGroup = a[i];
res.push(a[i]);
} else if ("class" in a[i]) {
if (currFieldGroup) {
currFieldGroup.fieldGroup.push(a[i]);
} else {
res.push(a[i]);
}
}
}
console.log(res);

How to convert an unorganized array into an grouped array by id

I'm trying to create an array that contains objects with an id and amount, grouped by id. The ids needs to be unique. So if there is 2 objects with same id, the amount will be added.
I can do it with nested for-loops, but I find this solution inelegant and huge. Is there a more efficient or cleaner way of doing it?
var bigArray = [];
// big Array has is the source, it has all the objects
// let's give it 4 sample objects
var object1 = {
id: 1,
amount: 50
}
var object2 = {
id: 2,
amount: 50
}
var object3 = {
id: 1,
amount: 150
}
var object4 = {
id: 2,
amount:100
}
bigArray.push(object1,object2,object3,object4);
// organizedArray is the array that has unique ids with added sum. this is what I'm trying to get
var organizedArray = [];
organizedArray.push(object1);
for(var i = 1; i < bigArray.length; i++ ) {
// a boolean to keep track whether the object was added
var added = false;
for (var j = 0; j < organizedArray.length; j++){
if (organizedArray[j].id === bigArray[i].id) {
organizedArray[j].amount += bigArray[i].amount;
added = true;
}
}
if (!added){
// it has object with new id, push it to the array
organizedArray.push(bigArray[i]);
}
}
console.log(organizedArray);
You can definitly make it cleaner and shorter by using reduce, not sure about efficiency though, i would say a traditional for loop is more efficient :
var bigArray = [];
var object1 = {id: 1, amount: 50}
var object2 = {id: 2, amount: 50}
var object3 = {id: 1, amount: 150}
var object4 = {id: 2, amount: 100}
bigArray.push(object1, object2, object3, object4);
var organizedArray = bigArray.reduce((acc, curr) => {
// check if the object is in the accumulator
const ndx = acc.findIndex(e => e.id === curr.id);
if(ndx > -1) // add the amount if it exists
acc[ndx].amount += curr.amount;
else // push the object to the array if doesn't
acc.push(curr);
return acc;
}, []);
console.log(organizedArray)
Rather than an organized array, how about a single object whose keys are the ids and values are the sums.
var bigArray = [
{ id: 1, amount: 50 },
{ id: 2, amount: 50 },
{ id: 1, amount: 150 },
{ id: 2, amount: 100 }
];
let total = {}
bigArray.forEach(obj => {
total[obj.id] = (total[obj.id] || 0) + obj.amount;
});
console.log(total);
If you really need to convert this to an array of objects then you can map the keys to objects of your choosing like this:
var bigArray = [
{ id: 1, amount: 50 },
{ id: 2, amount: 50 },
{ id: 1, amount: 150 },
{ id: 2, amount: 100 }
];
let total = {}
bigArray.forEach(obj => {
total[obj.id] = (total[obj.id] || 0) + obj.amount;
});
console.log(total);
// If you need the organized array:
let organizedArray = Object.keys(total).map(key => ({ id: key, amount: total[key] }));
console.log(organizedArray);
function getUniqueSums(array) {
const uniqueElements = [];
const arrayLength = array.length;
for(let index = 0; index < arrayLength; index++) {
const element = array[index];
const id = element.id;
const uniqueElement = findElementByPropertyValue(uniqueElements, 'id', id);
if (uniqueElement !== null) {
uniqueElement.amount += element.amount;
continue;
}
uniqueElements.push(element);
}
return uniqueElements;
}
function findElementByPropertyValue(array, property, expectedValue) {
const arrayLength = array.length;
for(let index = 0; index < arrayLength; index++) {
const element = array[index];
const value = element[property];
if (value !== expectedValue) {
continue;
}
return element;
}
return null;
}
This is an untested code. You will be able to understand the logic. Logic is almost same yours. But, perhaps a more readable code.

Merge objects concatenating values, using lodash

I'm trying to manipulate this sample array of objects.
[ { name: 'John Wilson',
id: 123,
classes: ['java', 'c++']},
{ name: 'John Wilson',
id: 123,
classes: 'uml'},
{ name: 'Jane Smith',
id: 321,
classes: 'c++'} ]
What I need to do is to merge objects with the same 'id', concatenating 'classes' and keeping one 'name'.
The result should be:
[ { name: 'John Wilson',
id: 123,
classes: ['java', 'c++', 'uml']},
{ name: 'Jane Smith',
id: 321,
classes: 'c++'} ]
I tried using .merge but it doesn't concatenate the values from 'classes', it just keeps the values from the last equal object.
What is the simplest way to do that, using lodash?
The function you're looking for is _.uniqWith, with a special twist which I will explain in a minute.
_.uniqWith is a lot like _.uniq in that it generates a unique array, but it allows you to pass your own custom comparator function that will be called to determine what counts as "equality."
Sane programmers would understand that this comparator should be side-effect free. The way this code works is by breaking that rule, and using a comparison function that does extra magic behind the scenes. However, this results in very concise code that will work no matter how many of these objects are in your array, so I feel like the transgression is well-justified.
I named the comparator function compareAndMerge so as not to hide its impure nature. It will merge both classes arrays and update the relevant property on both objects, but only if their id values are identical.
function merge(people) {
return _.uniqWith(people, compareAndMerge)
}
function compareAndMerge(first, second) {
if (first.id === second.id) {
first.classes = second.classes = [].concat(first.classes, second.classes)
return true
}
return false
}
var people = [{
name: 'John Wilson',
id: 123,
classes: ['java', 'c++']
}, {
name: 'John Wilson',
id: 123,
classes: 'uml'
}, {
name: 'Jane Smith',
id: 321,
classes: 'c++'
}]
console.log(merge(people))
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.2/lodash.min.js"></script>
An aside: You were missing square brackets around your original classes lists. I made sure that the code above doesn't care whether or not the classes property holds a single string or an array of strings, though, just in case.
Using ES6 you can do so with a Map to hold the unique values, Array#reduce to populate it, and the spread operator with Map#values to convert it back to array:
const arr = [{"name":"John Wilson","id":123,"classes":["java","c++"]},{"name":"John Wilson","id":123,"classes":"uml"},{"name":"Jane Smith","id":321,"classes":"c++"}];
const result = [...arr.reduce((hash, { id, name, classes }) => {
const current = hash.get(id) || { id, name, classes: [] };
classes && (current.classes = current.classes.concat(classes));
return hash.set(id, current);
}, new Map).values()];
console.log(result);
Not sure using lodash... here's a way to do it with normal JS:
var combined = arr.reduce(function(a, item, idx) {
var found = false;
for (var i = 0; i < a.length; i++) {
if (a[i].id == item.id) {
a[i].classes = a[i].classes.concat(item.classes);
found = true;
break;
}
}
if (!found) {
a.push(item);
}
return a;
}, []);
Fiddle: https://jsfiddle.net/6zwr47mt/
use _.mergeWith to set merging customizer
_.reduce(data, function(result, item) {
item = _.mergeWith(
item,
_.find(result, {id: item.id}),
function(val, addVal) {
return _.isArray(val) ? _.concat(val, addVal) : val;
});
result = _.reject(result, {id: item.id})
return _.concat(result, item);
}, []);
The following algorithm is not the best one but at least I know what it does :-)
console.log(clean(data));
function clean (data) {
var i, x, y;
var clean = [];
var m = clean.length;
var n = data.length;
data.sort((x, y) => x.id - y.id);
for (i = 0; i < n; i++) {
y = data[i];
if (i == 0 || x.id != y.id) {
clean.push(x = clone(y)), m++;
} else {
clean[m - 1] = merge(x, y);
}
}
return clean;
}
function clone (x) {
var z = {};
z.id = x.id;
z.name = x.name;
z.classes = x.classes.slice();
return z;
}
function merge (x, y) {
var z = {};
z.id = x.id;
z.name = x.name;
z.classes = unique(
x.classes.concat(y.classes)
);
return z;
}
function unique (xs) {
var i, j, n;
n = xs.length;
for (i = 1; i < n; i++) {
j = 0; while (j < i && xs[i] !== xs[j]) j++;
if (j < i) swap(xs, i, n - 1), i--, n--;
}
return xs.slice(0, n);
}
function swap (xs, i, j) {
var x = xs[i];
xs[i] = xs[j];
xs[j] = x;
}
<script>
var data = [{
id: 123,
name: 'John Wilson',
classes: ['java', 'c++']
}, {
id: 123,
name: 'John Wilson',
classes: ['uml', 'java']
}, {
id: 321,
name: 'Jane Smith',
classes: ['c++']
}];
</script>

How do i filter array of objects nested in property of array objects?

I have model like this:
var model = [{id: 1, prices: [{count: 2}, {count: 3}]}, {id: 2, prices: [{count: 2}]}, {id: 3, prices: [{count: 3}]}];
and I need to filter this objects of array useing property count and I will need to return matched objects in three scenarios:
if the objects have two objects in array prices,
if the objects have one object in array prices matching count:2,
if the objects have one property in array prices matching count:3.
so..when i click the button without assigned value i wanna see all objects, when i click button with value = 2 i wanna see objects with count: 2 and when i click the button with value = 3 i wanna get objects with count: 3, i must do this in AngularJS –
maybe something like this?
var result = model.filter(function(m) {
// make sure the m.prices field exists and is an array
if (!m.prices || !Array.isArray(m.prices)) {
return false;
}
var numOfPrices = m.prices.length
if (numOfPrices === 2) { // return true if its length is 2
return true;
}
for (var i = 0; i < numOfPrices; i++) {
if (m.prices[i].count &&
(m.prices[i].count === 2 ||
m.prices[i].count == 3)) {
return true;
}
}
return false;
});
use lodash or underscore library.. and then your code with lodash will be like:
_.filter(model, function(i){
return _.intersection(_.map(i.prices, 'count'), [3,2]).length;
})
it returns items that on their price property have array which contains element with count = 3 or count = 2
var model = [{
id: 1,
prices: [{
count: 2
}, {
count: 3
}]
}, {
id: 2,
prices: [{
count: 2
}]
}, {
id: 3,
prices: [{
count: 3
}]
}];
var search = function(data) {
var result = {};
function arrayObjectIndexOf(myArray, searchTerm, property) {
for (var i = 0, len = myArray.length; i < len; i++) {
if (myArray[i][property] === searchTerm) return i;
}
return -1;
}
for (var index in data) {
if (data[index].hasOwnProperty("prices") && arrayObjectIndexOf(data[index].prices, 2, 'count') != -1) {
result[data[index].id] = data[index];
} else if (data[index].hasOwnProperty("prices") && arrayObjectIndexOf(data[index].prices, 3, 'count') != -1) {
result[data[index].id] = data[index];
} else if (data[index].hasOwnProperty("prices") &&
data[index].prices.length == 2) {
result[data[index].id] = data[index];
}
}
return result;
}
var output = search(model);
console.log(output);

Find item in array and toggle values

I have the following array:
tabs = [
{ id: "tabA", active: true },
{ id: "tabB", active: false },
{ id: "tabC", active: false }
];
How can I:
1. Given a tabId find if it is active?
2. Toggle the active value of all tags? So true > false and false > true.
Just iterate your array and find the object:
function findById(arr, id){
for(var i = 0;i < arr.length;i++){
if(arr[i].id == id) return arr[i] // found, return the object
}
return null; // not found
}
Then you can do:
//1
console.log(findById(tabs,'tabB').active)
//2 (individual tag)
var obj = findById(tabs,'tabB')
obj.active = !obj.active
//2 (all tags)
for(var i = 0;i < tabs.length;i++){
tabs[i].active = !tabs[i].active
}

Categories