I've taken the following sample from a different question. And I am able to identify the object. But I also need to find our the position of that object. For example:
var arr = [{
Id: 1,
Categories: [{
Id: 1
},
{
Id: 2
},
]
},
{
Id: 2,
Categories: [{
Id: 100
},
{
Id: 200
},
]
}
]
If I want to find the object by the Id of the Categories, I can use the following:
var matches = [];
var needle = 100; // what to look for
arr.forEach(function(e) {
matches = matches.concat(e.Categories.filter(function(c) {
return (c.Id === needle);
}));
});
However, I also need to know the position of the object in the array. For example, if we are looking for object with Id = 100, then the above code will find the object, but how do I find that it's the second object in the main array, and the first object in the Categories array?
Thanks!
Well, if every object is unique (only in one of the categories), you can simply iterate over everything.
var arr = [{
Id: 1,
Categories: [{Id: 1},{Id: 2}]
},
{
Id: 2,
Categories: [{Id: 100},{Id: 200}]
}
];
var needle = 100;
var i = 0;
var j = 0;
arr.forEach(function(c) {
c.Categories.forEach(function(e) {
if(e.Id === needle) {
console.log("Entry is in position " + i + " of the categories and in position " + j + " in its category.");
}
j++;
});
j = 0;
i++;
});
function findInArray(needle /*object*/, haystack /*array of object*/){
let out = [];
for(let i = 0; i < haystack.lenght; i++) {
if(haystack[i].property == needle.property) {
out = {pos: i, obj: haystack[i]};
}
}
return out;
}
if you need the position and have to filter over an property of the object you can use a simple for loop. in this sample your result is an array of new object because there can be more mathches than 1 on the value of the property.
i hope it helps
Iterate over the array and set index in object where match found
var categoryGroups = [{
Id : 1,
Categories : [{
Id : 1
}, {
Id : 2
},
]
}, {
Id : 2,
Categories : [{
Id : 100
}, {
Id : 200
},
]
}
]
var filterVal = [];
var needle = 100;
for (var i = 0; i < categoryGroups.length; i++) {
var subCategory = categoryGroups[i]['Categories'];
for (var j = 0; j < subCategory.length; j++) {
if (subCategory[j]['Id'] == findId) {
filterVal.push({
catIndex : i,
subCatIndex : j,
id : needle
});
}
}
}
console.log(filterVal);
Here is solution using reduce:
var arr = [{ Id: 1, Categories: [{ Id: 1 }, { Id: 2 }, ] }, { Id: 2, Categories: [{ Id: 100 }, { Id: 200 }, ] } ]
const findPositions = (id) => arr.reduce((r,c,i) => {
let indx = c.Categories.findIndex(({Id}) => Id == id)
return indx >=0 ? {mainIndex: i, categoryIndex: indx} : r
}, {})
console.log(findPositions(100)) // {mainIndex: 1, categoryIndex: 0}
console.log(findPositions(1)) // {mainIndex: 0, categoryIndex: 0}
console.log(findPositions(200)) // {mainIndex: 1, categoryIndex: 1}
console.log(findPositions(0)) // {}
Beside the given answers with fixt depth searh, you could take an recursive approach by checking the Categories property for nested structures.
function getPath(array, target) {
var path;
array.some(({ Id, Categories = [] }) => {
var temp;
if (Id === target) {
path = [Id];
return true;
}
temp = getPath(Categories, target);
if (temp) {
path = [Id, ...temp];
return true;
}
});
return path;
}
var array = [{ Id: 1, Categories: [{ Id: 1 }, { Id: 2 },] }, { Id: 2, Categories: [{ Id: 100 }, { Id: 200 }] }];
console.log(getPath(array, 100));
.as-console-wrapper { max-height: 100% !important; top: 0; }
I'm working with a list of HTML headers (h2,h3,h4,h5,h6).
The picture describes the idea:
[
{
text: 'Some header',
rank: 2, // stays for <h2>
},
{
text: 'Some another header',
rank: 3, // stays for <h3>
},
{
text: 'A header with the same rank',
rank: 3, // stays for <h3>
},
{
text: 'One more subsection header',
rank: 4, // stays for <h4>
}
]
And I'm trying to turn it into a tree:
[
{
text: 'Some header',
children: [
{
text: 'Some another header',
},
{
text: 'A header with the same rank',
children: [
{
text: 'One more subsection header',
}
]
}
]
}
]
Here's my current code:
function list_to_tree(list) {
// We go from the end to the beggining
list = list.reverse();
let node, nextNode, roots = [], i;
for (i = 0; i < list.length; i += 1) {
node = list[i];
nextNode = list[i+1];
// If the next one's rank is greater, the current into the next as a child
if (nextNode !== undefined && node.rank > nextNode.rank) {
list[i+1].children.push(node);
} else {
// Else it's a root
roots.push(node);
}
}
return roots;
};
But it works only for the first h3, but the second h3 will go as a root. Any idea on how to achieve the goal? Thank you.
You could use the level property rank for indicating the nested position in a helper array.
Then iterate the data and build children arrays, if necessary.
function getTree(array) {
var levels = [{}];
array.forEach(function (o) {
levels.length = o.rank;
levels[o.rank - 1].children = levels[o.rank - 1].children || [];
levels[o.rank - 1].children.push(o);
levels[o.rank] = o;
});
return levels[0].children;
}
var data = [{ text: 'Main Heading', rank: 1 }, { text: 'Sub Heading', rank: 2 }, { text: 'Sub Sub Heading', rank: 3 }, { text: 'Sub Heading', rank: 2 }, { text: 'Sub Sub Heading', rank: 3 }, { text: 'Sub Sub Heading', rank: 3 }];
console.log(getTree(data));
.as-console-wrapper { max-height: 100% !important; top: 0; }
This solution will work irrespective of starting rank and also of the order in which the list is given... and it uses the exact data you provided...
var jsonHeaders =
[
{
text: 'Some header',
rank: 2, // stays for <h2>
},
{
text: 'Some another header',
rank: 3, // stays for <h3>
},
{
text: 'A header with the same rank',
rank: 3, // stays for <h3>
},
{
text: 'One more subsection header',
rank: 4, // stays for <h4>
}
];
function list_to_tree(list)
{
var jsonTree = [{}];
list = list.reverse();
for (i = 0, l = list.length; i < l; i++)
{
node = list[i];
var json = {};
json.text = node.text;
json.rank = node.rank;
if(jsonTree[0].rank == undefined)
{
jsonTree[0] = json;
}
else
if(jsonTree[0].rank == json.rank)
{
jsonTree.push(json);
}
else
if(jsonTree[0].rank < json.rank)
{
jsonTree[0] = ranker(jsonTree[0], json);
}
else
if(jsonTree[0].rank > json.rank)
{
var jsonTemp = jsonTree[0];
jsonTree[0] = json;
json = jsonTemp;
jsonTree[0] = ranker(jsonTree[0], json);
}
}
return jsonTree;
}
function ranker(jsonTree, json)
{
if(jsonTree.children == undefined)
{
jsonTree.children = [];
jsonTree.children.push(json);
}
else
if(jsonTree.children[0].rank == json.rank)
{
jsonTree.children.push(json);
}
else
if(jsonTree.children[0].rank < json.rank)
{
jsonTree.children[0] = ranker(jsonTree.children[0], json);
}
else
if(jsonTree.children[0].rank > json.rank)
{
var jsonTemp = jsonTree;
jsonTree = json;
json = jsonTemp;
jsonTree.children[0] = ranker(jsonTree.children[0], json);
}
return jsonTree;
}
var jsonTree = list_to_tree(jsonHeaders);
console.log('jsonArrayTree = ', jsonTree);
Here's the working function in case someone needs it:
function list_to_tree(list) {
list = list.reverse();
let node, currentRank = list[0].rank, i, roots = [];
for (i = 0; i < list.length; i += 1) {
node = list[i];
if (node.rank > currentRank) {
for (let n = i; n < list.length; n += 1) {
if (list[n].rank < node.rank) {
list[n].children.unshift(node);
break;
}
}
} else {
currentRank = node.rank;
roots.push(node);
}
}
return roots.reverse();
};
What would be the most efficient way (with performance in mind, assuming object may be large) to retrieve a nested object by it's key (itemID) within the following object?
Obviously, I can access the object by first referencing each of the parents (searchResults.page2.item3) but how can I retrieve item3 from searchResults using only the itemID assuming I don't know what page it's on?
var searchResults = {
page1 : {
item1 : {},
item2 : {}
},
page2 : {
item3 : {},
item4 : {}
}
}
Simply loop through your object. Here's a working solution. Hope it helps!
var searchResults = {
page1 : {
item1 : {},
item2 : {}
},
page2 : {
item3 : { id: 3},
item4 : {}
}
}
for(var i in searchResults){
var pageNumber = searchResults[i];
for(var j in pageNumber){
var deeperProperty = pageNumber[j];
if(deeperProperty.hasOwnProperty("id") && deeperProperty.id === 3){
console.log(deeperProperty);
}
}
}
Use a recursive function to just check the entire object until it finds item3
var searchResults = {
page1: {
item1: {
id: 1
},
item2: {
id: 2
}
},
page2: {
item3: {
id: 3
},
item4: {
id: 4
}
}
}
/* start the operation */
recurse_object(searchResults, 0);
function recurse_object(obj, curIdx) {
/* create array with all keys for an object */
var keys = Object.keys(obj);
/* check if we have gone through all of the object's keys */
if (curIdx < keys.length) {
/* if not, grab the key associated with curIdx */
var keyVal = keys[curIdx];
if (keyVal == 'item3') {
console.log("FOUND ITEM3 ID! " + obj.item3.id);
} else {
/* check if the current item has any sub-keys to check */
if (Object.keys(obj[keyVal]).length > 0) {
/* check the any keys that exist in a particular key */
recurse_object(obj[keyVal], 0);
}
/* now increase the index to check for the next key on object */
curIdx++;
recurse_object(obj, curIdx);
}
}
}
EDIT:
uncommented version with multiple input objects containing item3 as well as additional levels of nesting
var searchResults = {
page1: {
item1: {
id: 1
},
item2: {
id: 2
}
},
page2: {
item3: {
id: 3
},
item4: {
id: 4
}
},
page20: {
subPage1: {
item3: {
id: 6
}
},
item5: {
subItem2: {
deepItem1: {
item3: {
id: 10
}
}
}
}
}
}
recurse_object(searchResults, 0);
function recurse_object(obj, curIdx) {
var keys = Object.keys(obj);
if (curIdx < keys.length) {
var keyVal = keys[curIdx];
if (keyVal == 'item3') {
console.log("FOUND ITEM3 ID! " + obj.item3.id);
} else {
if (Object.keys(obj[keyVal]).length > 0) {
recurse_object(obj[keyVal], 0);
}
curIdx++;
recurse_object(obj, curIdx);
}
}
}
This does not seem efficient in the slightest, but it does what you are asking for as far as "can I retrieve item3 from searchResults using only the itemID assuming I don't know what page it's on?" goes.
var searchResults = {
page1: {
item1: { id: 1 },
item2: { id: 2 }
},
page2: {
item3: { id: 3 },
item4: { id: 4 }
}
}
var innerObjects = Object
.keys(searchResults)
.map(
function(key, index) {
return Object
.keys(searchResults[key])
.map(
function(innerKey, index) {
return searchResults[key][innerKey];
}
);
});
//console.log(innerObjects);
var flattenedArray = [].concat.apply([], innerObjects);
//console.log(flattenedArray);
var idYoureLookingFor = 3;
var objectYouWant = flattenedArray.filter(function(obj) { return obj.id == idYoureLookingFor; });
console.log(objectYouWant);
I have an 'item' object in JavaScript, and the item can have settings like
color, size, etc.
I need to get all possible combinations in an array.
So lets say we have an item that looks like this:
var newItem = {
name: 'new item',
Settings: [
{name: 'color', values: ['green', 'blue', 'red']},
{name: 'size', values: ['15', '18', '22']},
{name: 'gender',values: ['male', 'female']}
]
};
I need to somehow get this:
[
[{SettingName:'color',value:'green'},{SettingName:'size',value:'15'},{SettingName:'gender',value:'male'}],
[{SettingName:'color',value:'blue'},{SettingName:'size',value:'15'},{SettingName:'gender',value:'male'}],
[{SettingName:'color',value:'red'},{SettingName:'size',value:'15'},{SettingName:'gender',value:'male'}],
[{SettingName:'color',value:'green'},{SettingName:'size',value:'18'},{SettingName:'gender',value:'male'}],
[{SettingName:'color',value:'blue'},{SettingName:'size',value:'18'},{SettingName:'gender',value:'male'}],
[{SettingName:'color',value:'red'},{SettingName:'size',value:'18'},{SettingName:'gender',value:'male'}],
[{SettingName:'color',value:'green'},{SettingName:'size',value:'22'},{SettingName:'gender',value:'male'}],
[{SettingName:'color',value:'blue'},{SettingName:'size',value:'22'},{SettingName:'gender',value:'male'}],
[{SettingName:'color',value:'red'},{SettingName:'size',value:'22'},{SettingName:'gender',value:'male'}],
[{SettingName:'color',value:'green'},{SettingName:'size',value:'15'},{SettingName:'gender',value:'female'}],
[{SettingName:'color',value:'blue'},{SettingName:'size',value:'15'},{SettingName:'gender',value:'female'}],
[{SettingName:'color',value:'red'},{SettingName:'size',value:'15'},{SettingName:'gender',value:'female'}],
[{SettingName:'color',value:'green'},{SettingName:'size',value:'18'},{SettingName:'gender',value:'female'}],
[{SettingName:'color',value:'blue'},{SettingName:'size',value:'18'},{SettingName:'gender',value:'female'}],
[{SettingName:'color',value:'red'},{SettingName:'size',value:'18'},{SettingName:'gender',value:'female'}],
[{SettingName:'color',value:'green'},{SettingName:'size',value:'22'},{SettingName:'gender',value:'female'}],
[{SettingName:'color',value:'blue'},{SettingName:'size',value:'22'},{SettingName:'gender',value:'female'}],
[{SettingName:'color',value:'red'},{SettingName:'size',value:'22'},{SettingName:'gender',value:'female'}]
]
This can be a good interview question.
See JS Bin for running example.
getAllPermutations(newItem);
function getAllPermutations(item) {
var permutations = [];
getAllPermutations0(item, permutations, []);
console.log(permutations);
}
function getAllPermutations0(item, permutations, array) {
if (array && array.length === item.Settings.length) {
permutations.push(array.slice()); // The slice clone the array
return;
}
var index = array.length;
var setting = item.Settings[index];
for (var i = 0; i < setting.values.length; i++) {
if (index === 0)
array = [];
var currValue = setting.values[i];
array.push({
SettingName: setting.name,
value: currValue
});
getAllPermutations0(item, permutations, array);
array.pop(); // pop the old one first
}
}
Here is a none recursive solution. It takes an empty or existing settings "matrix" and a values array, and return a new matrix as a combination of existing matrix content cloned for each new value, appended with pairs of new value setting items.
[A] -> [1,2] gives [A][1][A][2]
[A][1][A][2] -> [X,Y] gives [A][1][X][A][2][Y][A][2][X][A][1][Y]
and so on
function processSettings(settings, name, values) {
if (settings.length == 0) {
values.forEach(function(value) {
settings.push( [{ SettingName: name, value: value }] )
})
} else {
var oldSettings = JSON.parse(JSON.stringify(settings)), settings = [], temp, i = 0
for (i; i<values.length; i++) {
temp = JSON.parse(JSON.stringify(oldSettings))
temp.forEach(function(setting) {
setting.push( { SettingName: name, value: values[i] } )
settings.push(setting)
})
}
}
return settings
}
You can now create the desired settings literal this way :
var settings = []
for (var i=0; i<newItem.Settings.length; i++) {
var item = newItem.Settings[i]
settings = processSettings(settings, item.name, item.values)
}
demo -> http://jsfiddle.net/b4ck98mf/
The above produces this :
[
[{"SettingName":"color","value":"green"},{"SettingName":"size","value":"15"},{"SettingName":"gender","value":"male"}],
[{"SettingName":"color","value":"blue"},{"SettingName":"size","value":"15"},{"SettingName":"gender","value":"male"}],
[{"SettingName":"color","value":"red"},{"SettingName":"size","value":"15"},{"SettingName":"gender","value":"male"}],
[{"SettingName":"color","value":"green"},{"SettingName":"size","value":"18"},{"SettingName":"gender","value":"male"}],
[{"SettingName":"color","value":"blue"},{"SettingName":"size","value":"18"},{"SettingName":"gender","value":"male"}],
[{"SettingName":"color","value":"red"},{"SettingName":"size","value":"18"},{"SettingName":"gender","value":"male"}],
[{"SettingName":"color","value":"green"},{"SettingName":"size","value":"22"},{"SettingName":"gender","value":"male"}],
[{"SettingName":"color","value":"blue"},{"SettingName":"size","value":"22"},{"SettingName":"gender","value":"male"}],
[{"SettingName":"color","value":"red"},{"SettingName":"size","value":"22"},{"SettingName":"gender","value":"male"}],
[{"SettingName":"color","value":"green"},{"SettingName":"size","value":"15"},{"SettingName":"gender","value":"female"}],
[{"SettingName":"color","value":"blue"},{"SettingName":"size","value":"15"},{"SettingName":"gender","value":"female"}],
[{"SettingName":"color","value":"red"},{"SettingName":"size","value":"15"},{"SettingName":"gender","value":"female"}],
[{"SettingName":"color","value":"green"},{"SettingName":"size","value":"18"},{"SettingName":"gender","value":"female"}],
[{"SettingName":"color","value":"blue"},{"SettingName":"size","value":"18"},{"SettingName":"gender","value":"female"}],
[{"SettingName":"color","value":"red"},{"SettingName":"size","value":"18"},{"SettingName":"gender","value":"female"}],
[{"SettingName":"color","value":"green"},{"SettingName":"size","value":"22"},{"SettingName":"gender","value":"female"}],
[{"SettingName":"color","value":"blue"},{"SettingName":"size","value":"22"},{"SettingName":"gender","value":"female"}],
[{"SettingName":"color","value":"red"},{"SettingName":"size","value":"22"},{"SettingName":"gender","value":"female"}]
]
You can use Array.prototype.map(), for loop, while loop, Array.prototype.concat(). Iterate gender values; select each of color, size value in succession beginning at index 0 of either; iterating the furthest adjacent array from current gender, increment the index of the closest adjacent array; merge the resulting two gender arrays to form a single array containing all combinations of gender, color, size
var colors = newItem.Settings[0].values;
var sizes = newItem.Settings[1].values;
var gen = newItem.Settings[2].values;
var i = sizes.length;
var res = [].concat.apply([], gen.map(function(value, key) {
var next = -1;
var arr = [];
for (var curr = 0; curr < i; curr++) {
while (next < i - 1) {
arr.push([{
SettingName: "gender",
value: value
}, {
SettingName: "size",
value: sizes[curr]
}, {
SettingName: "color",
value: colors[++next]
}])
}
next = -1;
}
return arr
}))
var newItem = {
"name": "new item",
"Settings": [{
"name": "color",
"values": [
"green",
"blue",
"red"
]
}, {
"name": "size",
"values": [
"15",
"18",
"22"
]
}, {
"name": "gender",
"values": [
"male",
"female"
]
}]
}
var colors = newItem.Settings[0].values;
var sizes = newItem.Settings[1].values;
var gen = newItem.Settings[2].values;
var i = sizes.length;
var res = [].concat.apply([], gen.map(function(value, key) {
var next = -1;
var arr = [];
for (var curr = 0; curr < i; curr++) {
while (next < i - 1) {
arr.push([{
SettingName: "gender",
value: value
}, {
SettingName: "size",
value: sizes[curr]
}, {
SettingName: "color",
value: colors[++next]
}])
}
next = -1;
}
return arr
}))
document.querySelector("pre").textContent = JSON.stringify(res, null, 2)
<pre></pre>
plnkr http://plnkr.co/edit/C2fOJpfwOrlBwHLQ2izh?p=preview
An approach using Array.prototype.reduce(), Array.prototype.sort(), Object.keys(), for loop, while loop
var newItem = {
name: 'new item',
Settings: [
{
name: 'color',
values: ['green', 'blue', 'red']
},
{
name: 'size',
values: ['15', '18', '22']
},
{
name: 'gender',
values: ['male', 'female']
}
]
};
var props = ["SettingName", "value"];
var settings = newItem.Settings;
function p(settings, props) {
var data = settings.reduce(function(res, setting, index) {
var name = setting.name;
var obj = {};
obj[name] = setting.values;
res.push(obj);
return res.length < index ? res : res.sort(function(a, b) {
return a[Object.keys(a)[0]].length - b[Object.keys(b)[0]].length
})
}, []);
var key = data.splice(0, 1)[0];
return [].concat.apply([], key[Object.keys(key)].map(function(value, index) {
return data.reduce(function(v, k) {
var keys = [v, k].map(function(obj) {
return Object.keys(obj)[0]
});
var i = Math.max.apply(Math, [v[keys[0]].length, k[keys[1]].length]);
var next = -1;
var arr = [];
for (var curr = 0; curr < i; curr++) {
while (next < i - 1) {
var a = {};
a[props[0]] = keys[0];
a[props[1]] = v[keys[0]][++next];
var b = {};
b[props[0]] = keys[1];
b[props[1]] = k[keys[1]][next];
var c = {};
c[props[0]] = Object.keys(key)[0];
c[props[1]] = value;
arr.push([a, b, c]);
};
next = -1;
}
return arr
});
}));
}
document.querySelector("pre").textContent = JSON.stringify(
p(settings, props), null, 2
);
<pre></pre>