create children properties recursively in Javascript - javascript

I am trying to create a depth of children based on a passed parameter n.
Basically if n is 4, the resulting object should be
parent.children.children.children.children.
I've come up with this so far:
parent = {}
function makechildren( current, depth ){
current['children']={}
while (depth>0){ {
str = JSON.stringify(current)
return makechildren(current, depth-1)
}
}
}
makechildren(parent, 4)

I tested this code and it works
parent={};
var obj;
function makechildren( current, depth){
if(depth>0)
{
current = JSON.parse(JSON.stringify(current).replace('{}','{"children":{}}'))
makechildren(current, depth-1);
}else{
obj = current;
return ;
}
}
console.log(obj)

Related

adding two objects without overriding keys within the objects Javascript

i have two objects, a master and a temp
the master looks like
{
"gnome":{
"child":{
name:"child",
race:"gnome"
},
"youngling":{
name:"youngling",
race:"gnome"
}
},
"human":{...},
...
}
and the temp looks like
{
"gnome":{
"man":{
name:"man",
race:"gnome"
}
}
what i am trying to do is have the temp be added to the master like
{
"gnome":{
"child":{...},
"youngling":{...},
"man":{...}
},
"human":{...},
...
}
what i currently have
let obj = {}
function generateJson() {
let race = getinput("race").value
let name = getinput("name").value
let temp = {}
temp[`${race}`] = {}
temp[`${race}`][`${name}`] = {
name: name,
race: race
}
obj = Object.assign(obj, temp)
}
all it does is empties and override the first duplicate key with the temp value
a.e. {gnome:{man:{..}}}
earlier this question was closed because it should have been awnsered with How can I merge properties of two JavaScript objects dynamically?
which sadly it didn't, all of the solutions override objects within objects, i want to add to similar keys
Based on your example, this should work
<script>
function merge_without_override(master, temp) {
for(var key in temp) {
if( !temp.hasOwnProperty(key) )
continue;
if(master[key] !== undefined) // key already exists in master.
continue;
master[key] = Object.assign(temp[key]); // key doesnt exist, assign it.
}
}
var master = {
"gnome":{
"child":{
name:"child",
race:"gnome"
},
"youngling":{
name:"youngling",
race:"gnome"
}
},
"human":{}
};
var temp = {
"gnome":{
"man":{
name:"man",
race:"gnome"
}
}
};
console.log("master before merge");
console.log(master);
merge_without_override(master["gnome"], temp["gnome"]);
console.log("master after merge");
console.log(master);
</script>
Output (in jsfiddle):
{
gnome: {
child: { ... },
man: { ... },
youngling: { ... }
},
human: { ... }
}
JsFiddle: https://jsfiddle.net/h10fpcx2/
Chris Ferdinandi wrote a helper for this here;
I've added an updated version below, but wanted to add a fix for my biggest frustration with Object.assign.
It mutates the source material. To fix this, you can add an empty object as the first argument of the deepAssign function, or use function copyDeepAssign below.
// mutates the source material
function deepAssign(...args) {
// Make sure there are objects to merge
const len = args.length;
if (len < 1) return;
const main = args[0];
if (len < 2) return main
// Merge all objects into first
let i = 0,
curr;
while (i < len) {
curr = args[i];
for (var key in curr) {
// If it's an object, recursively merge
// Otherwise, push to key
if (Object.prototype.toString.call(curr[key]) === '[object Object]') {
main[key] = deepAssign(main[key] || {}, curr[key]);
} else {
main[key] = curr[key];
}
}
i++;
}
return main;
}
// Doesn't mutate the source material
function copyDeepAssign(...args) {
const base = {};
return deepAssign(base, ...args);
}

Browse associative array as circular list

I would like to browse an associative array like a circular list.
First the associative array is defined like this :
array = {item1:array(...), item2:array(...), ...}
When at the first element I browse the array of this element, once arrive at the last element of this array it should passe to the second element and brows it's array, and the same for the last one who must return to the first element.
so I initialize my array as follows:
// Build the associative array
Prot.prototype.additem = function(itemName, itemArray)
{
this.array[itemName] = itemArray; // itemArray is an array
}
// Init the currentItem of the associative array to browse (We don't necessarily start at the first)
Prot.prototype.init = function(itemName)
{
this.currentItem = this.array[itemName];
this.currentItemArray = 0;
}
Prot.prototype.next = function()
{
// here I browse the first array of the first key of my associative array
var index = this.currentItem.indexOf(this.currentItemArray);
index = index +1;
this.currentItemArray = this.currentItem[index];
if (index == (this.currentItemArray.length - 1))
{
// when arrives at the last element of the array of the first key I should pass to the second
return false;
}
else {
return true;
}
}
// I add a set interval at the end so no need for a loop
You'll need an array to know what is the "next" item array. So I would suggest storing the desired order in another array, having just those names.
Here is a possible implementation:
class Prot {
constructor() {
this.itemNames = [];
this.arrays = {};
this.hasData = false;
this.currentIndex = 0;
}
additem(itemName, itemArray) {
if (itemName in this.arrays) throw "duplicate entry";
this.arrays[itemName] = { data: itemArray, index: this.itemNames.length };
this.itemNames.push(itemName); // keep the order
if (itemArray.length) this.hasData = true;
}
init(itemName) {
this.currentItem = this.arrays[itemName];
this.currentIndex = 0;
}
next() {
if (!this.hasData) return;
if (!this.currentItem) this.currentItem = this.arrays[this.itemNames[0]];
var data = this.currentItem.data[this.currentIndex++];
while (this.currentIndex >= this.currentItem.data.length) {
this.currentItem = this.arrays[this.itemNames[(this.currentItem.index+1) % this.itemNames.length]];
this.currentIndex = 0;
}
return data;
}
}
// demo
let obj = new Prot;
// add the arrays:
obj.additem("a", [1, 2, 3]);
obj.additem("b", [4, 5]);
obj.additem("c", [6, 7, 8, 9]);
obj.additem("d", [0]);
// Start at "b":
obj.init("b");
// iterate from there...
for (let i = 0; i < 12; i++) {
console.log(obj.next());
}
There is no such thing as an associative array in JavaScript, but you can use an object instead. A very simple implementation of defining an object and referencing its properties in a circular way would be the following:
// define the object with 6 properties and assiociated values:
var obj={a:123, b:456, c:789, d:666, e:777, f:888};
function getcirc(obj){
// use a "static variable" inside the function:
if(typeof getcirc.i=="undefined") getcirc.i=0;
var keys=Object.keys(obj), k=keys[getcirc.i++%keys.length];
console.log(k,obj[k]);
}
// call the function repeatedly ...
for (var n=0;n<20;n++) getcirc(obj);

Recursive function returning undefined value

I want to fetch the object from multi level structure
I written function for it but even on return its not coming out from function and returning value, its continue with next recursion. I know its returning value to the previously called function and as its scope is block its getting overridden and that's why returning undefined value
var selectedObj = findObjectByUid( existingStructure, selectedUid);
function findObjectByUid( root, selectedUid ) {
if( root.uniqueId === selectedUid ) {
return root;
}
if( root.children && root.children.length > 0 ) {
for( var k in root.children ) {
if( root.children[ k ].uniqueId === selectedUid ) {
return root.children[ k ];
} else if( root.children.length ) {
return findObjectByUid( root.children[ k ], selectedUid );
}
}
}
}
Here i want to get back to my initial calling function when it got matching uid.
Actually you return with the first child, regardless of the found node.
You could take a temporary variable and store the result of the children check and if not falsy return this value.
BTW, you could take the child directly of the array for the recursion.
function findObjectByUid(root, selectedUid) {
if (root.uniqueId === selectedUid) return root;
if (!root.children || !root.children.length) return;
for (let child of root.children) {
let temp = findObjectByUid(child, selectedUid);
if (temp) return temp;
}
}
var selectedObj = findObjectByUid(existingStructure, selectedUid);
There are three problems with using this approach on arrays. First, the for...in also iterates over an object's prototype properties if those properties are enumerable. For example:
Array.prototype.voice = "James Earl Jones";
var tMinus = [
"Two",
"One",
"Blast off!"
];
var countdown = "";
for (var step in tMinus) {
countdown += tMinus[step] + "\n";
}
console.log(countdown);
// => "Two
// One
// Blast Off!
// James Earl Jones
// "
That can be solved by using hasOwnProperty to exclude prototype properties.
Example:
for (var step in tMinus) {
if (tMinus.hasOwnProperty(step)) {
countdown += tMinus[step] + "\n";
}
}
Here are corrected code. You had used return findObjectByUid in inner calling by which code was terminating before completing loop.
function findObjectByUid( root, selectedUid ,foundArr) {
if( root.uniqueId === selectedUid ) {
foundArr.push(root);
return root;
}
else if( root.children && root.children.length > 0 ) {
for( var k in root.children ) {
findObjectByUid( root.children[k], selectedUid,foundArr );
if(root.children[k]=== selectedUid){break;}
}
}
return foundArr.length>0?foundArr[0]:null;
}
Sample json and calling method
var root = {uniqueId:1,children:[{uniqueId:10},{uniqueId:11,children:[{uniqueId:21,children:[]},{uniqueId:22,children:[]},{uniqueId:23,children:[{uniqueId:31,children:[]},{uniqueId:32,children:[]}]}]},{uniqueId:12,children:[]},{uniqueId:13,children:[]}]};
findObjectByUid(root,32,[]);

Replace the root in Tree structure

I have following tree lets call it Tree One.
1
/ \
/ \
2 3
/
/
4
Now I want to replace the root node with 2. Then above tree will become something like this. Lets call it Tree Two
2
/ \
/ \
4 1
\
\
3
How can I implement above for arrays input as described above?
UPDATE
I already tried with Linkedlist. Which is below however, it is not working for above inputs(i.e. array).
function replaceRootNode(tree, x) {
var y = x.left;
x.left = y.right;
if (y.right) {
y.right.parent = x;
}
y.parent = x.parent;
if (!x.parent) {
tree.root = y;
} else {
if (x === x.parent.left) {
x.parent.left = y;
} else {
x.parent.right = y;
}
}
y.right = x;
x.parent = y;
}
UPDATE 2
My above solution using likedlist is based on Splay Tree. But I need it for arrays input.
Your definition was not complete so I added the following definitions:
New selected root does not have to be direct child of the current root (i.e. you can go in your example directly from state 1 to state 3).
If the new selected root had both left and right children, one of them is assigned to its previous parent.
What I did is to first transform it to a structured json and do the manipulation there. I used lodash to make code cleaner.
Note that my code has functions to convert from your format to json and back. When you look at the code, make sure you open the console, also, you have a few samples of more complex 'trees' commented out, you can try them as well.
// var tree = [1, [2, [4], [5]], [3]];
var tree = [1, [2, [4]],
[3]
];
// var tree = [1, [2, [4, [5, [6,[7]]]]], [3]];
var zipTree = _.partial(_.zipObject, ['root', 'left', 'right']);
var toTree = _.flow(zipTree, _.partial(_.pick, _, _.identity));
function toTreeRecurse(data) {
var result = toTree(data);
if (_.isArray(result.left)) {
result.left = toTreeRecurse(result.left);
}
if (_.isArray(result.right)) {
result.right = toTreeRecurse(result.right);
}
return (result);
}
function toArrayRecurse(tree) {
var result = [tree.root];
if (tree.left) {
result.push(toArrayRecurse(tree.left));
}
if (tree.right) {
result.push(toArrayRecurse(tree.right));
}
return (result);
}
function findValueParent(tree, value) {
if (!tree) {
return (null);
}
var result;
if (_.get(tree, 'left.root') === value) {
return (tree);
} else if (_.get(tree, 'right.root') === value) {
return (tree);
} else if (result = findValueParent(tree.left, value)) {
return (result);
} else if (result = findValueParent(tree.right, value)) {
return (result);
} else {
return (null);
}
}
function setRoot(tree, value) {
var rootParent = findValueParent(tree, value);
var newRoot;
if (rootParent) {
var fromSide, toSide;
if (findValueParent(tree.left, value) || (_.get(tree, 'left.root') === value)) {
fromSide = 'left';
toSide = 'right';
} else if (findValueParent(tree.right, value) || (_.get(tree, 'right.root') === value)) {
fromSide = 'right';
toSide = 'left';
}
newRoot = _.get(rootParent, fromSide);
if (_.get(newRoot, toSide)) {
_.set(rootParent, fromSide, _.get(newRoot, toSide));
} else {
delete rootParent[fromSide];
}
_.set(newRoot, toSide, tree);
return (newRoot);
} else {
console.log('value does not exist');
return (null);
}
}
console.log('original: ', JSON.stringify(tree));
console.log('original as json tree: ', JSON.stringify(toTreeRecurse(tree)));
console.log('setting root to 2: ', JSON.stringify(toArrayRecurse(setRoot(toTreeRecurse(tree), 2))));
console.log('setting root to 4: ', JSON.stringify(toArrayRecurse(setRoot(toTreeRecurse(tree), 4))));
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/3.10.1/lodash.min.js"></script>
<div>Demo, make sure you open the devtools to see the console print</div>
I don't believe there is a way to do this exclusively with arrays. Linked lists are definetly better suited for your issue.
So if you can only accept arrays as an input, you could do the following:
Convert your input array into a linked list.
Use the replaceRootNode function you provided to replace the node.
Convert linked list back to array.
Here is a code example I made to convert the array:
var arr = [2, [1, [3]], [4]];
var tree = {
root: null
};
arrayToList(arr, tree);
console.log(tree);
function arrayToList(array, tree){
var node = {
data: array[0],
left: null,
right: null
};
if (tree.root == null) {
tree.root = node;
}
if(array[1] instanceof Array) {
node.left = arrayToList(array[1], tree);
} else {
node.left = array[1];
}
if(array[2] instanceof Array) {
node.right = arrayToList(array[2], tree);
} else {
node.right = array[2];
}
return node;
}
Fiddle (output in console).
I'm using a Postorder traversing of the tree, which is Left > Right > Root. So it will output your trees mirrored compared to your examples above.
If you prefer to visit the right node first, you can do this simple edit: Fiddle. Though you typically always visit Left before Right.
Anyway, then you can replace your root node with your function (haven't tried it).
Then, you can do a similar function to convert the Re-arranged tree back into an array.

How to get a nested property for an object dynamically?

I need to create a function that search a property in an object and returns its value.
Object could have an arbitrary structure with property nested in other objects.
How could I change my script?
var item = {
id: 10,
properties: {
parent_id: 20,
x: {
y: 100
}
}
}
function getValue(what) {
console.log(item[what]);
}
getValue('id');
// ok return 10
getValue('properties.parent_id')
// undefined - ISSUE here I would like to have returned 20
You can provide a syntax to access these properties in the getValue function parameter. For example, to access properties.parent_id you can use 'properties.parent_id'.
Then the getValue function should be written as the following:
function getValue(prop) {
if (typeof(prop) !== 'string')
throw 'invalid input string';
props = prop.split('.');
var value = item[props[0]];
for(var i = 1, l = props.length; i < l; i++) {
value = value[props[i]];
}
return value;
}
Example:
getValue('properties.parent_id'); //returns 20
You need to create a "path", i.e. a sequence, of keys too access in order. One way is to choose an uncommong separator that is never going to be used for object keys, e.g. |:
element = obj;
path.split("|").forEach(function(key){
element = element[key];
});
if you cannot exclude any char from the keys then supporting escaping is mandatory; for example you could use , to separate keys but allowing #, to mean a comma is part of the key and ## meaning an at-sign is part of the key.
element = obj;
(path+",").match(/([^#,]|#.)*,/g).forEach(function(x){
element = element[x.slice(0,-1).replace(/#(.)/g, "$1")];
});
for example the path "1,2,x,y#,z,,w##" can be used to access
obj[1][2].x["y,z"][""]["w#"]
The code below makes flat obj so to access like that.
var flatten = function(obj,into,prefix){
into = into ||{};
prefix = prefix || '';
_.each(obj,function(val,key){
if(obj.hasOwnProperty(key)){
if(val && typeof val === 'object'){
flatten(val,into,prefix + key + '.');
}else{
into[prefix + key] = val;
}
}
});
return into;
};
The working JSFiddle is here http://jsfiddle.net/fper2d73/
The complete code is
var item = {
id: 10,
properties: {
parent_id: 20,
x: {
y: 100
}
}
}
var flatten = function(obj,into,prefix){
into = into ||{};
prefix = prefix || '';
_.each(obj,function(val,key){
if(obj.hasOwnProperty(key)){
if(val && typeof val === 'object'){
flatten(val,into,prefix + key + '.');
}else{
into[prefix + key] = val;
}
}
});
return into;
};
var _item = flatten(item);
function getValue(what) {
console.log(_item[what]);
}
getValue('id');
// returns 10
getValue('properties.parent_id')
// returns 20
getValue('properties.x.y')
//returns 100
for a deeply nested object you can use a recursive function to retrieve all the object which are nested inside parent Object.It can be applied to an object literal having three to more number of nested object
var parentObj = {
parentProp: 10,
childObj: {
childProp: 20,
grandChildObj: {
y: {
z:'lol',
places:['newyork','canada','dhaka']
}
}
}
}
var arr=[];
var container=[];
container.push(parentObj);
var count=0;
function getObjNum(obj){ //<= recursive function to retrieve all the nested objects inside parent object
var prop=Object.getOwnPropertyNames(obj);
for(i=0;i<prop.length;i++){
if(typeof(obj[prop[i]])=='object'){
if(!Array.isArray(obj[prop[i]])){
container.push(obj[prop[i]]);
count++;
getObjNum(obj[prop[i]]); // recursive call to getObjNum
}
}
}
}
getObjNum(parentObj); // sent the parent object to getObjNum
function getVal(str){
var split=str.split('.');
container.forEach(function(obj){
if(obj.hasOwnProperty(split[split.length-1])){
console.log(obj[split[split.length-1]]);
}
});
}
getVal('parentObj.parentProp');
getVal('y.z');

Categories