prevent circular reference in Array in Javascript - javascript

I have a Datastructure like the following.
array=[
{id:"a",children:[
{id:"b",children:[
{id:"d",children:[]}]},
{id:"c",children:[]}]}
]
If I want to insert the element {id:"e", children:["a","f"] at "c" ("a","e" are no Strings, but copies of the nodes) I want to check, that it exists in the upper Tree and would therefore create a circular reference. So I think I have to reverse walk the array. But as I'm pretty new to Javascript and Node, I have no idea how to do that.
Would it be agood idea, to create an array, I store all the dependencies in? something like this:
[
a:[],
b:[a],
c:[a,b]
d:[a,b]
]
then I could lookup the parent in the array and would see that in c, a and b are allready in dependency

You could use a hash table, if the id is unique.
var array = [{ id: "a", children: [{ id:"b", children: [{ id: "d", children: [] }] }, { id: "c", children: [] }] }],
hash = Object.create(null);
// creating circular reference
array[0].children[1].children.push(array[0]);
array.forEach(function iter(a) {
if (hash[a.id]) {
console.log(a.id, 'circular reference found');
return;
}
hash[a.id] = true;
a.children.forEach(iter);
});
console.log(array);

Related

javascript replace property name in n-ary tree object

Supose I have a n-ary tree structure (in json) like this:
[
{
"text": "Some title",
"children": [
{
"text": "Some title",
"children": [
...
]
},
...
]
}
]
Where I neither know how many children the nodes will have nor the tree's depth.
What I would like to do is change the name of property text to name, across all children.
I've tryed this, with a recursive function func:
func(tree) {
if (!tree) return;
for (let node of tree) {
node.name = node.text
delete node.text;
return func(node.children);
}
}
But it didn't work. How would I do that?
I would say, the main problem with your code is that node variable holds the value of corresponding array items and it doesn't keep the reference to those items themselves, so, basically, mutations you attempt to make are never applied to original array (but only to temporary variable reassigned upon each loop iteration)
If you prefer to mutate original array and feel comfortable using for(-loops for that purpose, you'd be much better off using for(..in-loop to access array items by their keys:
const src = [
{
text: "Some title",
children: [
{
text: "Some title",
children: []
},
]
}
],
func = tree => {
for(const nodeIdx in tree){
const {text:name, children} = tree[nodeIdx]
func(children)
tree[nodeIdx] = {name, children}
}
}
func(src)
console.log(src)
.as-console-wrapper{min-height:100%;}
However, I would avoid mutating source data and return new array instead (e.g. with Array.prototype.map():
const src = [
{
text: "Some title",
children: [
{
text: "Some title",
children: []
},
]
}
],
func = tree =>
tree.map(({text:name,children}) => ({
name,
...(children && {children: func(children)})
}))
console.log(func(src))
.as-console-wrapper{min-height:100%;}
You would use the in operator here.
for (let node **in** tree) {
node.name = node.text
delete node.text;
return func(node.children);
}

Find deep nested array depth

I have a nested array. Like below:
I want to find the depth of this nested array, which means the child element has most deep nested children.
let arr = [
{
name: 'tiger',
children: [{
name: 'sinba',
children: [{
name: 'cute',
children: []
}]
}]
},
{
name: 'lion',
children: []
}
]
In this case, the depth is 3, the tiger has 3 level. So the depth is 3
How could i achieve this? I try to use recursive, but don't know how to find the element which
has most nested children.
Thanks in advance.
Assuming that there are no circular references, you could try something like this
let arr = [{
name: 'tiger',
children: [{
name: 'sinba',
children: [{
name: 'cute',
children: []
}]
}]
},
{
name: 'lion',
children: []
}
]
function count(children) {
return children.reduce((depth, child) => {
return Math.max(depth, 1 + count(child.children)); // increment depth of children by 1, and compare it with accumulated depth of other children within the same element
}, 0); //default value 0 that's returned if there are no children
}
console.log(count(arr))
Our function would not work if there were some circular references, so there might be a need to adjust it accordingly. Detecting circular references is a whole ordeal. If nothing is done about it, the function will throw a Maximum call stack size exceeded error.
In order to handle it without any additional functionality implementation you could use already existing native JSON.stringify to do so. The stringify option will throw an exception only if you try to serialize BigInt values which we can handle ourselves or when objects are cyclic, which is excatly what we wanted.
let arr = [{
name: 'tiger',
children: []
}]
function testCircular(arr){
try {
BigInt.prototype.toJSON = function() { return this.toString() } // Instead of throwing, JSON.stringify of BigInt now produces a string
JSON.stringify(arr);
return false;
}
catch (e) {
// will only enter here in case of circular references
return true;
}
}
function count(children) {
if (testCircular(children)) return Infinity;
return children.reduce((depth, child) => {
return Math.max(depth, 1 + count(child.children)); // increment depth of children by 1, and compare it with accumulated depth of other children within the same element
}, 0); //default value 0 that's returned if there are no children
}
console.log(count(arr)) // normally counting
arr[0].children = arr; // creates circular reference
console.log(count(arr)) // counting for circular

Loop through an array of objects and get where object.field equals value

I'm currently working on a small application where I have to loop through an enormous array of objects. What would be the most efficient method to perform this?
var array = [
{
id: "1",
name: "Alpha"
},
{
id: "2",
name: "Beta"
},
...
];
I'd like to get each object where name equals "Alpha". I'm currently using a simple if statement to filter the objects with a different name value out, but I wonder if there's a more efficient way to do this, performance-wise.
It's worth to mention that I'll push the matching results into a new array.
No, there is no more efficient way.
The alternative is to build and maintain some kind of internal data structure which allows you to find the desired elements faster. As usual, the trade off is between the work involved in maintaining such a structure vs the time it saves you.
I don't have any way about which I would know it's more effective.
But if you had your objects ordered by name you could stop your search imideatly upon reaching an object whose name is not equal to "Alpha".
To find the first object you're looking for you can use binary search and from this Object you go up and down until at both ends you reach an object which isn't named "Alpha" or the end of array.
This is only a way of optimizing and will require time to sort the array and also will take more time when adding an element.
There's a JavaScript function exactly for this kind of task. Filter
From the Docs
The filter() method creates a new array with all elements that pass the test implemented by the provided function.
Here is a small example by code for getting all element from array which has a certain 'name' field:
const arr = [
{name: 'Abc'},
{name: 'Xyz'},
{name: 'Lmn'},
{name: 'Xyz'},
{name: 'Xyz'}
];
let response = findByName('Xyz');
console.log(response);
function findByName (name) {
return arr.filter((element) => {
return element.name = name;
});
}
If you need more than one time a collection with a given name, you could use an object with the names as hashes and have instantly access to the items.
var array = [{ id: "1", name: "Alpha" }, { id: "2", name: "Beta" }, { id: "3", name: "Beta" }, { id: "4", name: "Gamma" }, { id: "5", name: "Beta" }, { id: "2", name: "Alpha" }],
hash = Object.create(null);
array.forEach(function (a) {
if (!hash[a.name]) {
hash[a.name] = [];
}
hash[a.name].push(a);
});
console.log(hash);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Extracting data from a complex array without lots of FOR loops

I have a fairly complex array generated from Google's natural language API. I feed it a paragraph of text and out comes lots of language information regarding such paragraph.
My end goal is to find "key words" from this paragraph, so, to achieve this I want to put all the "entities" into a flat array, count the duplicates, and then consider words with the highest amount of duplicates to be "key words". If it doesn't find any then I'll cherry pick words from entities I consider most significant.
I already know the entities that could exist:
var entities = [
'art',
'events',
'goods',
'organizations',
'other',
'people',
'places',
'unknown'
];
Here is an example structure of the array I'm working with.
input = [
{
language: {
entities: {
people: [
{
name: "Paul",
type: "Person",
},
{
name: "Paul",
type: "Person",
},
],
goods: [
{
name: "car",
type: "Consumer_good",
}
], //etc
}
}
}
];
output = ["Paul", "Paul", "car"...];
My question is - what is the best way to convert my initial array into a flat array to then find the duplicates without using a whole bunch of FOR loops?
There is no way around loops or array functions if you work with dynamic input data.
You can access all the values using this format:
input[0]["language"]["entities"]["people"][0].name
input = [
{
language: {
entities: {
people: [
{
name: "Paul",
type: "Person",
},
{
name: "Paul",
type: "Person",
},
],
goods: [
{
name: "car",
type: "Consumer_good",
}
], //etc
}
}
}
];
console.log(input[0]["language"]["entities"]["people"][0].name);
Then you could do something like this:
for (var entry in input[0]["language"]["entities"]) {
console.log(entry);
}
OR, if I understood you wrong,
You can use this to turn the javascript Object into an array using this (requires jquery):
var myObj = {
1: [1, 2, 3],
2: [4, 5, 6]
};
var array = $.map(myObj, function(value, index) {
return [value];
});
console.log(array[0][0]);
console.log(array[0]);
console.log(array);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
This will output
1
[1, 2, 3]
[[1,2,3],[4,5,6]]
You could iterate through input.language.entities in a recursive way and collect all the .name properties into an array. Then you have only one for loop :-).
After doing that, you can iterate through it to find the duplicates. If you sort it alphabetical before it is easier (if two or more consecutive entries are equal, there are duplicates).
But it could be a bit dangerous if google changes the api or if it delivers crap data because of a malfunction.
Isn't input.language.entities already flat enough to work with it?
I ended up doing something like this. It's not pretty but it gets the job done.
var result = [];
var known_entities = ['art','events','goods','organizations','other','people','places','unknown'];
for(i=0; i < known_entities.length; i++){
var entity = known_entities[i];
if(language.entities[entity]){
for(var j in language.entities[entity]){
var word = language.entities[entity][j].name
result.key_words.push(word);
}
}
}

flatten and uneven array into an object

I have this Array and Object representing the same data:
arrayExample = [
{name: "max", age: 21},
{name: "max.David", age: 27},
{name: "max.Sylvia"},
{name: "max.David.Jeff"},
{name: "max.Sylvia.Anna", age: 20},
{name: "max.David.Buffy"},
{name: "max.Sylvia.Craig"},
{name: "max.Sylvia.Robin"}
];
ObjectExample = {
name: "max",
age: 21,
children: [
{
name: "Sylvia",
children: [
{name: "Craig"},
{name: "Robin"},
{name: "Anna", age: 20}
]
},
{
name: "David",
age: 27,
children: [
{name: "Jeff"},
{name: "Buffy"}
]
}
]
};
my objective is to extend the Array class to have 2 functions flatten which transform the objectExample into the arrayExample and uneven which do the opposite, I'm thinking maybe lodash would help here but I still didn't find the correct way to do this here's where I'm now:
to flatten from objectExample to arrayExample first the objectExample structure must be specific meaning the parents must share a property with all their children sure the parents and children could have other property that should be ported to the proper item in the new arrayExample, also for the uneven function it should create an object that all the parents share the same property with their children and other property should be copied respectively.
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.
update:
my specific problem is that I need to create a d3js tree layout for angular-ui-router configurations states object which I can extract into a json file as I said before, the structure for the ui-router is like the arrayExample, and the required structure for the d3js tree layout is like the objectExample, one way to go about this is to manually rewrite it and it wont take too much time but that solution is not what I want I need to make a build task for this for generic routes that will always have the name attribute in their config object that could be used to find children of each route or state, for more information check ui-router for routes config object and this d3 videos for d3 tre layout:
part 1.
part 2.
correction: extending the Object class with a flatten function to flatten an object into an array and the Array class with unEven function to unEven an array into an object not like I wrote before:
my objective is to extend the Array class to have 2 functions.
update 2:
To make this more clear, both flatten and unEven are like the map function except flatten is for an object not an array and it return an array, and the unEven function is for an array but return an object.
Here's a function that will produce the flattened output:
Working demo: http://jsfiddle.net/jfriend00/w134L7c6/
var ObjectExample = {
name: "max",
age: 35,
status: "single",
hometown: "Scottsdale",
children: [
{
name: "Sylvia",
children: [
{name: "Craig", age: 16},
{name: "Robin"},
{name: "Anna"}
]
},
{
name: "David",
age: 54,
children: [
{name: "Jeff"},
{name: "Buffy"}
]
}
]
};
// call this on an object with a name property
// and an optional children property (which would be an array of objects)
function flatten(obj, key, outputArray, rootName) {
var name, item;
outputArray = outputArray || [];
rootName = rootName || "";
if (rootName) {
rootName += ".";
}
if (obj.hasOwnProperty(key)) {
name = rootName + obj[key];
item = {};
item[key] = name;
for (var prop in obj) {
if (obj.hasOwnProperty(prop) && prop !== "children") {
item[prop] = obj[prop];
}
}
outputArray.push(item)
if (obj.children) {
for (var i = 0; i < obj.children.length; i++) {
flatten(obj.children[i], key, outputArray, name);
}
}
}
return outputArray;
}
var result = flatten(ObjectExample, "name");
Produces this output:
[{"name":"max","age":35,"status":"single","hometown":"Scottsdale"},
{"name":"max.Sylvia"},
{"name":"max.Sylvia.Craig","age":16},
{"name":"max.Sylvia.Robin"},
{"name":"max.Sylvia.Anna"},
{"name":"max.David","age":54},
{"name":"max.David.Jeff"},
{"name":"max.David.Buffy"}]
You could adapt this function to be a method on the Array prototype if you really want to (not something I would recommend, particularly since the input isn't even an array).
I do not know what you mean when you say "the rootName could have more then one". ObjectExample is an object and thus cannot have more than one name at the top level. If you started with an array of ObjectExample like structures, then you could just loop over the array calling flatten() on each object in the top level array and it would accumulate the results.

Categories