Get the level of a hierarchy - javascript

I have an array of objects, Where each object has an id and a ParentId property (so they can be arranged in trees). They are in no particular order.
Please note that the id's and parentId's will not be integers, they will be strings (just wanted to have the sample code cleaner..)
There is only one root: lets say its id:1
The data looks like so:
data = [
{
id:"id-2",
parentId:"id-3"
},
{
id:"id-4",
parentId:"2"
},
{
id:"id-3",
parentId:"id-4"
},
{
id:"id-5",
parentId:"id-4"
},
{
id:"id-6",
parentId:"id-1"
},
{
id:"id-7",
parentId:"id-1"
}
// and so on...
]
I am looking for a efficient way to give each object a level property which should specify the nested level it is...
They should then look like this:
data = [
{
id:"id-2",
parentId:"id-1",
level:2
},
{
id:"id-3",
parentId:"id-4",
level:5
},
{
id:"id-4",
parentId:"id-2",
level:3
},
{
id:"id-5",
parentId:"id-4",
level:5
},
{
id:"id-6",
parentId:"id-1",
level:2
},
{
id:"id-7",
parentId:"id-3",
level:4
}
// and so on...
]
In short:
I want that level to be added dynamically via looping thru the array and figuring out the hierarchy..
Additionally, (if posible) they should then be sorted according to there order, like for instance all objects level:3's from the same parent should be next to each other, not that there should be siblings of the same parent next to each other rather then two cousins of level 3 next to each other.

A working example of the below code is on jsFiddle.
Index the tree by id and traverse it upwards, from each node, and count until you hit the root. By indexing first, we approach O(n) time complexity (depending on tree density). ****Updated to satisfy the sorting requirement, and allow exclusion of root node***:
function levelAndSort(data, startingLevel) {
// indexes
var indexed = {}; // the original values
var nodeIndex = {}; // tree nodes
var i;
for (i = 0; i < data.length; i++) {
var id = data[i].id;
var node = {
id: id,
level: startingLevel,
children: [],
sorted: false
};
indexed[id] = data[i];
nodeIndex[id] = node;
}
// populate tree
for (i = 0; i < data.length; i++) {
var node = nodeIndex[data[i].id];
var pNode = node;
var j;
var nextId = indexed[pNode.id].parentId;
for (j = 0; nextId in nodeIndex; j++) {
pNode = nodeIndex[nextId];
if (j == 0) {
pNode.children.push(node.id);
}
node.level++;
nextId = indexed[pNode.id].parentId;
}
}
// extract nodes and sort-by-level
var nodes = [];
for (var key in nodeIndex) {
nodes.push(nodeIndex[key]);
}
nodes.sort(function(a, b) {
return a.level - b.level;
});
// refine the sort: group-by-siblings
var retval = [];
for (i = 0; i < nodes.length; i++) {
var node = nodes[i];
var parentId = indexed[node.id].parentId;
if (parentId in indexed) {
var pNode = nodeIndex[parentId];
var j;
for (j = 0; j < pNode.children.length; j++) {
var child = nodeIndex[pNode.children[j]];
if (!child.sorted) {
indexed[child.id].level = child.level;
retval.push(indexed[child.id]);
child.sorted = true;
}
}
}
else if (!node.sorted) {
indexed[node.id].level = node.level;
retval.push(indexed[node.id]);
node.sorted = true;
}
}
return retval;
}
Example:
// level 0 (root) excluded
var startingLevel = 1;
var someData = [
{id : "id-1", parentId : "id-0"},
{id : "id-2", parentId : "id-0"},
{id : "id-3", parentId : "id-2"},
{id : "id-4", parentId : "id-3"},
{id : "id-5", parentId : "id-4"},
{id : "id-6", parentId : "id-4"},
{id : "id-7", parentId : "id-0"},
{id : "id-8", parentId : "id-1"},
{id : "id-9", parentId : "id-7"},
{id : "id-10", parentId : "id-1"},
{id : "id-11", parentId : "id-1"},
{id : "id-12", parentId : "id-1"}
];
var outputArray = levelAndSort(someData, startingLevel);
Output:
Note
If you change the input order, the sort comes out a little different, but it's still correct (i.e., in level-order, grouped by sibling).

I'm not sure where you get the value for level so I'll assume that its just an integer. BTW, you can add the level property to each of your array's item by looping through it.
for (var i = 0, l = data.length; i < l; i++) {
data[i].level = i
}
which will give
data = [{id:"1", parentId:"3", level:0 }, {id:"2", parentId:"1", level:1 } ...]

Here is your working code. Level starts at 2.
ALERT: If a level cannot be calculated, the application may go into an infinite loop. So, make sure the parentId is valid for all objects and at least one of them have parentId="id-1".
<script type="text/javascript">
data = [
{
id:"id-2",
parentId:"id-3"
},
{
id:"id-4",
parentId:"id-2"
},
{
id:"id-3",
parentId:"id-5"
},
{
id:"id-5",
parentId:"id-1"
}
];
function processData() {
flag = true;
while(flag) {
flag = false;
for(var i = 0; i < data.length; i++) {
if(data[i].parentId == "id-1") {
data[i].level = 2;
} else {
l = getLevel(data[i].parentId);
if(l > 0) {
data[i].level = l + 1;
} else {
flag = true;
}
}
}
}
}
function getLevel(id) {
for(var i = 0; i < data.length; i++) {
if(data[i].id == id) {
if(data[i].level) {
return data[i].level;
} else {
return 0;
}
}
}
return 0;
}
processData();
console.log(data);
</script>

One way to address this without the need for recursion is to create a DOM hierarchy. For each item in the array, create a div and attach it to its parent. then walk the DOM and assign the levels (top is level 1, then add 1 for each child).
I have set up a rough demo here:
http://jsfiddle.net/4AqgM/
An excerpt from the code:
top.dataset.level="1";
var topChildren=top.getElementsByTagName("div");
for (var i=0;i<topChildren.length;i++) {
topChildren[i].dataset.level=parseInt(topChildren[i].parentNode.dataset.level)+1;
}

Related

Check against two arrays using for loop and if statement

I'm trying to run a check against two arrays (one has 4 objects, one just have a few strings) with for loops and if statement for a problem set.
The idea is to use for loop to iterate over every element in the object array and the string array, then use if statement to figure out matches and shove the matching string into a new array. Once all the elements are iterated, it returns the string if there is a matching one.
The problem is the function calls it a day once a single match in the object array is found and returns that only that instead of iterating over the rest of the elements in the object array.
var passengers = [
{ name: ["Michael Jackson"], paid: true },
{ name: ["Osama"], paid: false },
{ name: ["Harambe"], paid: true },
{ name: ["Pepe"], paid: true },
];
var noFlyList = ["Jimmy", "John", "Pepe", "Osama"];
function checkNoFly(passengers, noFlyList) {
for (var i = 0; i < passengers.length; i++) {
for (var j = 0; j < noFlyList.length; j++) {
if (passengers[i].name[0] == noFlyList[j]) {
var passengerList = [];
passengerList.push(passengers[i].name[0]);
return passengerList;
}
}
}
return true;
}
function checkNotPaid(passengers) {
return (!passengers.paid);
}
function processPassenger(passengers, testFunction) {
for (var i = 0; i < passengers.length; i++) {
if (testFunction(passengers[i])) {
return false;
}
}
return true;
}
var allCanFly = processPassenger(passengers, checkNoFly);
if (!allCanFly) {
console.log("We cannot fly because " + checkNoFly(passengers, noFlyList) + " is on the no-fly list");
}
var allPaid = processPassenger(passengers, checkNotPaid);
if (!allPaid) {
console.log("we cannot fly because not all passengers have paid");
}
use this: having passengerList in the loop make its reinitialized to empty array on each loop, and returning passengerList in the loop makes the loop break once it finishes the first loop
function checkNoFly(passengers, noFlyList) {
var passengerList = [];
for (var i = 0; i < passengers.length; i++) {
for (var j = 0; j < noFlyList.length; j++) {
if (passengers[i].name[0] == noFlyList[j]) {
passengerList.push(passengers[i].name[0]);
}
}
}
return passengerList;
}
EDIT:
change your original processPassenger function to the one below, originally you have only 1 argument passed to your checkNoFly function, where you'd defined it to take 2 arguments, so the false is returned for wrong number of argument, which stop you from getting it the way you want.
function processPassenger(passengers, testFunction) {
if (testFunction(passengers, noFlyList).length !=0) {
return false;
}
return true;
}
EDIT 2: for your updated question, since for the first check we are returning an array for the single function processPassenger() for validation, we can take similar approach for the checkNotPaid function to return an array for those who have not paid.
function checkNotPaid(passengers) {
var passengerNotPaid = [];
for (var i = 0; i < passengers.length; i++) {
if (!passengers[i].paid) {
passengerNotPaid.push(passengers[i].name[0]);
}
}
return passengerNotPaid;
}
unless you'd want to refactor everything, i think this would be ok.
You're telling it to do so: return passengerList; in your inner loop. Also, you keep re-declaring the variable var passengerList = []; inside your inner for-loop, emptying it every time.
var passengers = [
{ name: ["Michael Jackson"], paid: true },
{ name: ["Osama"], paid: false },
{ name: ["Harambe"], paid: true },
{ name: ["Pepe"], paid: true },
];
var noFlyList = ["Jimmy", "John", "Pepe", "Osama"];
function checkNoFly(passengers, noFlyList) {
var passengerList = [];
for (var i = 0; i < passengers.length; i++) {
for (var j = 0; j < noFlyList.length; j++) {
if (passengers[i].name[0] == noFlyList[j]) {
passengerList.push(passengers[i].name[0]);
}
}
}
return passengerList;
}
function checkNoPay(passengers) {
var nonPayers = [];
for (var i = 0; i < passengers.length; i++) {
if (!passengers[i].paid) { nonPayers.push(passengers[i].name); }
}
return nonPayers;
}
var banList = checkNoFly(passengers, noFlyList);
if (banList.length) {
console.log("We cannot fly because " + banList + " is/are on the no-fly list");
}
var unpaidList = checkNoPay(passengers);
if (unpaidList.length) {
console.log("We cannot fly because " + unpaidList + " has/have not payed the flight");
}
var canWeFly = !(banList.length || unpaidList.length);
console.log(canWeFly ? "We can fly" : "We cannot fly");

JavaScript Json row undefined for TreeView

I have a question. When querying my Json object, although i have my Field as same name and all Json rows available give me an error that is undefined.
//e.g: row.DepParentId
Bellow is my code. Am I missing some tag?
function convert(rows) {
debugger;
function exists(rows, parent) {
for (var i = 0; i < rows.length; i++) {
if (rows[i].DepId === parent) return true;
}
return false;
}
var nodes = [];
// get the top level nodes
for (var i = 0; i < rows.length; i++) {
var row = rows[i];
if (!exists(rows, row.DepParentId)) {
nodes.push({
id: row.DepId,
name: row.Title
});
}
}
var toDo = [];
for (var i = 0; i < nodes.length; i++) {
toDo.push(nodes[i]);
}
while (toDo.length) {
var node = toDo.shift();
// the parent node
// get the children nodes
for (var i = 0; i < rows.length; i++) {
var row = rows[i];
if (row.DepParentId == node.Id) {
var child = {
Id: row.DepId,
Name: row.Title
};
if (node.options) {
node.options.push(child);
} else {
node.options = [child];
}
toDo.push(child);
}
}
}
return nodes;
}
My Json example for one row picked from Firefox
"{"DepId":7,"DepParentId":3,"Title":"OTT"}"
Thanks for helping
Joao
thanks all for helping, it's working now, the problem was the JSON.stringify() was returning only an [Object] so when you use JSON.parse() generates a sintax error.
function onLoaded() {
var itemsCount = items.get_count();
for (var i = 0; i < itemsCount; i++) {
var item = items.itemAt(i);
var dep = JSON.stringify(item.get_fieldValues());
deps.push(dep);
}
So, i had to change the format to be valid to parse like this
var dt = "[" + deps + "]";
var ret = JSON.parse(dt);
Thanks
Joao

trying to create an array of arrays for return JSON

Im currently working on this script that is written in javascript that returns data from the netsuite ERP platform.
Right now we have the code returning in an array, whilst this is good, it is a result of a dataset of product information.
The script is querying for 3 products, and as a result it returns an array of 21 keys.
this should be returning 3 arrays of arrays so we can handle the content easily externally to Netsuite.
I for the life of me cant figure out which loop I am required to create a new array to manage the content.
function loadRecord(request, response)
{
var recType = request.getParameter('recType');
var savedSearchId = request.getParameter('savedSearchId');
var internalid = request.getParameter('internalid');
//perform the required search.
var filter = [];
if(recType == 'customer' || recType == 'contact' )
{
filter[0] = new nlobjSearchFilter('internalid', null, 'is', internalid); // just get the 1 item by the internal id of the record
}
if( recType == 'item')
{
var internal_ids = new Array();
internal_ids[0] = 25880;
internal_ids[1] = 25980;
internal_ids[2] = 333 ;
filter[0] = new nlobjSearchFilter('internalid', null, 'anyOf', internal_ids); // just get the 1 item by the internal id of the record
}
if(recType == 'transaction')
{
filter[0] = new nlobjSearchFilter('type',null,'anyOf','SalesOrd');
filter[1] = new nlobjSearchFilter('internalid','customer','is', internalid );
}
var rsResults = nlapiSearchRecord(recType, savedSearchId, filter);
var rsObj = [];
// not sure how to make each row a new array of arrays so it is structured more elegantly...
for (x = 0; x < rsResults.length; x++)
{
var flds = rsResults[x].getAllColumns();
for (i = 0; i < flds.length; i++)
{
var rowObj = {};
rowObj.name = flds[i].getName();
rowObj.label = flds[i].getLabel();
rowObj.val = rsResults[x].getValue(flds[i].getName(), flds[i].getJoin(), flds[i].getSummary());
rowObj.txtval = rsResults[x].getText(flds[i].getName(), flds[i].getJoin(), flds[i].getSummary())
rsObj.push(rowObj);
}
}
response.write(JSON.stringify(rsObj));
}
Any help greatly appreciated
Is this what you're looking for?
var rsObj = [];
var rowArr, fields, x, i;
for (x = 0; x < rsResults.length; x++)
{
flds = rsResults[x].getAllColumns();
for (i = 0; i < flds.length; i++)
{
rowArr = rsObj[x] = [];
rowArr.push(flds[i].getName());
rowArr.push(flds[i].getLabel());
rowArr.push(rsResults[x].getValue(flds[i].getName(), flds[i].getJoin(), flds[i].getSummary()));
rowArr.push(rsResults[x].getText(flds[i].getName(), flds[i].getJoin(), flds[i].getSummary()));
}
}
console.log(rsObj[0][0]); // row0.name
console.log(rsObj[2][1]); // row2.label
Maybe something like this:
for (var x = 0; x < rsResults.length; x++)
{
var flds = rsResults[x].getAllColumns();
for (var i = 0; i < flds.length; i++)
{
rsObj.push({
name: flds[i].getName(),
label: flds[i].getLabel(),
val: rsResults[x].getValue(flds[i].getName(), flds[i].getJoin(), flds[i].getSummary()),
txtval: rsResults[x].getText(flds[i].getName(), flds[i].getJoin(), flds[i].getSummary())
});
}
}
If you are using ECMAScript5, you cold simplify the loop with forEach, like this:
rsResults.forEach(function(result) {
result.getAllColumns().forEach(function(fields) {
rsObj.push({
name: fields.getName(),
label: fields.getLabel(),
val: result.getValue(fields.getName(), fields.getJoin(), fields.getSummary()),
txtval: result.getText(fields.getName(), fields.getJoin(), fields.getSummary())
});
});
});
This must solve you problem. Some declaration problem might be there.
var rsObj = [];
for (int x = 0; x < rsResults.length; x++)
{
var flds = rsResults[x].getAllColumns();
for (int i = 0; i < flds.length; i++)
{
var rowObj = [];
rowObj.push(flds[i].getName());
rowObj.push(flds[i].getLabel());
rowObj.push(rsResults[x].getValue(flds[i].getName(), flds[i].getJoin(), flds[i].getSummary()));
rowObj.push(rsResults[x].getText(flds[i].getName(), flds[i].getJoin(), flds[i].getSummary()));
rsObj.push(rowObj);
}
}

Get name of key in key/value pair in JSON using jQuery?

Say I have this JSON:
[
{
"ID": "1",
"title": "Title 1",
},
{
"ID": "2",
"title": "Title 2",
}
]
How would I return the set of key names that recur for each record? In this case, ID, title.
I tried:
$.getJSON('testing.json', function(data) {
var items = [];
$.each(data, function(key, val) {
items.push(key +', ');
});
$('<p/>', {
html: items.join('')
}).appendTo('#content');
});
without success.
This is a JSON "database", and every "record" has the same keys. I just want a script that will tell me what the keys are, not test whether or not they occur in every entry.
This will give you an array of all the string properties that match across an array of objects. Is that what you are looking for?
$.getJSON('testing.json', function(data) {
var propertiesThatExistInAll = getPropertiesThatExistInAll(data);
});
var getPropertiesThatExistInAll = function(arr) {
var properties = $.map(data[0], function (prop, value) {
return prop;
});
var propertiesThatExistInAll = [];
$.each(properties, function (index, property) {
var keyExistsInAll = true;
// skip the first one since we know it has all the properties
for (var i = 1, len = data.length; i < len; i++) {
if (!data[i].hasOwnProperty(property)) {
keyExistsInAll = false;
break;
}
}
if (keyExistsInAll) {
propertiesThatExistInAll.push(property);
}
});
return propertiesThatExistInAll;
};
Something like this, perhaps?
items = [];
for (key in jsonobj) {
if (!itemExists(items, key)) {
items[items.length] = key
}
}
function itemExists(items, value) {
for (i = 0; i < items.length; i++) {
if (items[i] == value) {
return true
}
}
return false;
}
Of course, that will return items that exist in any one of the objects, not that exist in all. It's not entirely clear from your question if this is the solution you want.
This can probably be made more efficient/concise, but the function below will do it.
var testJson = [ {'oi' : 1, 'arf': 2, 'foo' : 0}, {'oi': 5, 'arf': 7}];
function commonKeys(j)
{
var fillUp = [];
for(var i in j[0])
fillUp.push(i);
for(var i = 1; i < j.length; i++)
{
var cur = j[i]; var curArr = [];
for (var i in cur) {curArr.push(i)};
fillUp = fillUp.filter(function(x) {return (curArr.indexOf(x) != -1);});
}
return fillUp;
}
alert(commonKeys(testJson)); //oi,arf (not foo)

Javascript building tree hierarchy

var array = [{"grandpa","father"}, {"father"}, {"grandpa","father","me"}];
Given the above array, I want to product a java-script object(JSON) like below, that has the parent-child like structure.
{"id":"grandpa",
"children":[
{"id":"father",
"children":[
{"id":"me",
"children":[]
}]
}]
}
If you're asking how you would take a list of hierarchy paths and create a tree structure, here's how you could do it in JavaScript:
function convertToHierarchy(arry /* array of array of strings */)
{
var item, path;
// Discard duplicates and set up parent/child relationships
var children = {};
var hasParent = {};
for (var i = 0; i < arry.length; i++)
{
var path = arry[i];
var parent = null;
for (var j = 0; j < path.length; j++)
{
var item = path[j];
if (!children[item]) {
children[item] = {};
}
if (parent) {
children[parent][item] = true; /* dummy value */
hasParent[item] = true;
}
parent = item;
}
}
// Now build the hierarchy
var result = [];
for (item in children) {
if (!hasParent[item]) {
result.push(buildNodeRecursive(item, children));
}
}
return result;
}
function buildNodeRecursive(item, children)
{
var node = {id:item, children:[]};
for (var child in children[item]) {
node.children.push(buildNodeRecursive(child, children));
}
return node;
}
convertToHierarchy([["1","2"], ["1"], ["1","2","3"]]);
Edit:
Your question is still ambiguous. My previous version assumed these two things:
Each node ID uniquely identifies a node
A specified hierarchy path can start at other than the root node
In this sample, I'll assume the following:
Node IDs are not unique, but they are unique within the children of a particular node
All hierarchy paths start at the root node of the tree
Here's the code:
function convertToHierarchy(arry /* array of array of strings */)
{
// Build the node structure
var rootNode = {id:"root", children:{}}
for (var i = 0; i < arry.length; i++)
{
var path = arry[i];
buildNodeRecursive(rootNode, path, 0);
}
return rootNode;
}
function buildNodeRecursive(node, path, idx)
{
if (idx < path.length)
{
item = path[idx];
if (!node.children[item])
{
node.children[item] = {id:item, children:{}};
}
buildNodeRecursive(node.children[item], path, idx + 1);
}
}
The hierarchy structure is returned, but the format's a bit different. However, you should get the picture.
I think this should work. I'm using firebug to track the structure of the output.
var el = {"name": "Level 1", "paths" : ["fruits"]};
var el2 = {"name": "Level 3", "paths" : ["fruits", "apples", "fuji"]};
var el3 = {"name": "Level 4", "paths" : ["fruits", "apples", "fuji", "red"]};
var el4 = {"name": "Level 2", "paths" : ["fruits", "apples"]};
var allEl = [el, el2, el3, el4];
/* Define recursive function for setting the child */
function setChild(parent, pos, arr, name)
{
if(pos < arr.length)
{
if(pos == arr.length-1) //last element of the paths
parent.name = name;
if(!parent.children){
parent.children = [];
parent.children[0] = new Object();
}
setChild(parent.children[0], pos + 1, arr, name);
}
}
/* Test starts here */
var root = new Object();
for(var i=0; i<allEl.length; i++)
{
var el = allEl[i];
setChild(root, 0, el.paths, el.name);
}
//Firefox debugging ...getfirebug.com
console.debug(root);
If you want to encode JSON, just use a JSON library.
Don't try and roll your own.

Categories

Resources