I have an array of objects that can contain children of the same object type, like this:
var exampleArray = [
{
alias: 'alias1',
children: [
{
alias: 'child1'
},
{
alias: 'child2',
children: [
{
alias: 'child4'
},
{
alias: 'child5'
}
]
},
{
alias: 'child3'
}
]
},
{
alias: 'alias2'
},
{
alias: 'alias3',
children: [
{
alias: 'child6'
},
{
alias: 'child7'
}
]
}
];
The base object has other properties but they are not important to the question(s) at hand. For now, lets just assume the objects can be:
{
alias: 'string',
children: []
}
The children are optional.
I am looking for the best methods / fastest methods for managing some things with an object like this. I have created some recursive methods to do some of the things I want, but I want to know if there are better ways to go about doing the following tasks:
hasAlias(arr, alias) - I need to determine if the entire object contains any object with the give alias.
Currently, I do this recursively but given that this array can grow finitely, the recursive method is eventually going to hit stack limits.
getParent(arr, alias) - I need to be able to obtain the parent that contains an element with the given alias. Given that alias' are unique to the entire array, there will never be two of the same alias. Again I do this recursively right now but I want to find better methods of doing this.
deleteObject(arr, alias) - I am unsure how to accomplish this one currently. I need to be able to pass an array and an alias and have that object (and all its children) removed from the given array. I started a recursive method of doing this but stopped and decided to post here instead.
I am using Node.js and have lodash available for faster methods of doing things. I'm still fairly new to JavaScript so I am unsure if there are better ways to go about doing things with larger scale arrays like this.
Back in the days of FORTRAN which didn't support recursion, one achieved similar effects by changing a data set to simulate a level of "recursion". Applying this principle to the example object structure, a function to lookup an object by its "alias" (a name or id by another word) could be written without recursion like this:
function findAlias( parent, alias) // parent object, alias value string
{ function frame( parent)
{ return {parent: parent, children: parent.children,
index: 0, length: parent.children.length};
}
var stack, tos, child, children, i;
stack = [];
if( parent.children)
stack.push( frame( parent));
search:
while( stack.length)
{ tos = stack.pop(); // top of generation stack
children = tos.children;
for( i = tos.index; i < tos.length; ++i)
{ child = children[i];
if( child.alias == alias)
{ return { parent: tos.parent, child: child, childIndex: i}
}
if( child.children)
{ tos.index = i + 1;
stack.push(tos); // put it back
stack.push( frame(child));
continue search;
}
}
}
return null;
}
In short one ends up creating a stack of smallish data objects which are pushed and popped in the same function instead of making recursive calls. The example above returns and object with parent and child object values. The child value is the one with the supplied alias property, and the parent object is the one with the child in its children array.
It returns null if the alias could not be found so can be used for hasAlias functionality. If it doesn't return null it performs the getParent functionality. You do have to create a root node however:
// create a rootnode
var rootNode = { alias: "root", children: exampleArray};
var found = findAlias(rootNode, "alias3");
if( found)
{ console.log("%s is a child of %s, childIndex = %s",
found.child.alias, found.parent.alias, found.childIndex);
}
else
console.log("not found");
[Edit: add childIndex to search return object, update test example code, add conclusion.]
Conclusion
Using recursive function calls when supported for a tree walking application makes sense in terms of self documenting code and maintainability. A non recursive variation may pay for itself it if it can be shown that it has significant benefits in reducing server load under volume pressure tests, but requires sound documentation.
Regardless of internal coding, a tree walking function which returns an object with details of parent, child, and child index values might contribute to overall program efficiency by reducing the total number of treewalks ever performed:
truthiness of the search return value substitutes for an hasAlias function
the return object from the search can be passed to update, remove or insert functions without requiring repeated tree searches in each function.
The guaranteed fastest way is obviously to have
- an index for the aliases (thats actually a unique id)
- have a parent backlink on each child item (if it has a parent)
You look up against the id index
var index = {}
(function build(parent) {
index[parent.alias] = parent;
(parent.children || []).forEach( item => {
item.parent = parent
build(item)
})
})(objectRoot)
function hasAlias(alias) { return alias in index }
function getAlias(alias) { return index[alias] }
function getParent(alias) { return index[alias] && index[alias].parent}
Deleting an alias would mean removing it and its children from the index and from the parent that still remains in the index
function deleteAlias(alias) {
function deleteFromIndex(item) {
delete index[item.alias]
(item.children || []).forEach(deleteFromIndex)
}
var item = index[alias]
item.parent.children.splice(item.parent.children.indexOf(item))
deleteFromIndex(item)
}
I might approach your main array slightly differently, and keep it as a flat array that references other items rather than incorporates them entirely.
var flat = [
{
alias : "str1",
children : [ flat[1], flat[2] ],
parent : null
},
{
alias : "str1",
children : [],
parent : flat[0]
},
{
alias : "str1",
children : [],
parent : flat[0]
}
]
This is kind of a "linked list" approach. There are pros and cons to linked lists, but you'll be able to iterate over all items quickly.
Related
The problem isn't the code, it's that I don't understand why what I have works, although it does what I need it to do. I'm building an app that keeps track of jobs. The jobs, each an object, are stored in an array in a JSON file. I'm adding the functionality to edit a job's key/value pairs in the JSON file.
Anyway, my function editJob takes in an object as an argument that has an id and a variable amount of other properties. The goal is then to locate the job in JSON that matches the id, then update that job's properties based only on the editItems object.The code below allows for that. I just don't understand the line below the Object.keys code. Why would I not compare the located job's keys to the editItems keys?
I don't know why it works and am worried it will break at some point because it's not properly coded.
function editJob (editItems) {
// editItems is an object like this: ex. { id: 3, customer: 'Artemis', source: 'Google', description: 'Fixed toilet' }
return this.jobs.map(job => {
let editedJobs = Object.assign({}, job);
if (editedJobs.id === editItems.id) {
Object.keys(editItems).forEach(k => {
if (editedJobs[k] === job[k]) { // WHY DOES THIS WORK. why job[k] and not editItems[k]???
editedJobs[k] = editItems[k];
}
});
}
return editedJobs;
});
}
Since you just did editedJobs = Object.assign({}, job), the expression editedJobs[k] === job[k] will be true for every k. You can just omit it. You would achieve the same thing by
function editJob (editItems) {
return this.jobs.map(job => {
return job.id === editItems.id
? Object.assign({}, job, editItems)
: job;
});
}
In es2015, if I have base class to represent a List that looks like this:
class List {
constructor(data){
this.data = data
}
sortBy(attribute){
return this.data.sort((a,b) => {
return (a[attribute] < b[attribute]) ? 1 : -1;
})
}
get count() { return this.data.length }
}
Then I might want to subclass that base class with a less generic kind of data, namely, if I am an elf, toys:
class ToyList extends List {
constructor(toys){
super(toys);
this.toys = toys;
}
}
At this point ToyList is no different from List, except for the name. But if you look at an instantiation of ToyList, it has both data and toys properties. These refer to the same array, in terms of conceptualizing the point of a ToyList, data doesn’t make much sense.
If I make a ToyList, I have both .data and a .toys attributes:
tl = new ToyList(['truck', 'plane', 'doll'])
Object { data: Array[3], toys: Array[3] }
Then my tl has both a data and a toys attribute. They’re both references to the same array, but what I would like is for the subclass to only have the toys reference.
Here’s another example which utilizes a method on the base class:
class Todos extends List {
constructor(todos){
super(todos);
this.todos = todos;
}
get byPriority(){
return this.todos.sortBy('priority')
}
}
var thingsToDo = [
{task: 'wash the dog', priority: 10},
{task: 'do taxes', priority: 1},
{task: 'clean the garage', priority: 0}
]
var todos = new Todos(thingsToDo);
todos.byPriority
This would be nice, because then I could just refer to .byPriority to get a sorted version of the list which is very specific to this particular kind of data. But I can’t see how I can make that happen, because
But what I get is:
TypeError: this.todos.sortBy is not a function
So to summarize, what I want is a way to refer to to base class properties with a name which is specific to the semantics of the subclass, without losing the methodology of the base class.
referencing our discurrion in the comments, a better implementation (imo), extensible and avoiding the problem you asked about
var AP = Array.prototype; //just lazy
class List {
constructor(elements){
for(var i = 0, j = (elements && elements.length)|0; i<j; ++i)
this[i] = elements[i];
//use length instead of count, stay compatible with the Array-methods
//will make your life easier
this.length = i;
}
length: 0
sortBy(attr){
return this.sort(function(a,b){
return (a[attribute] < b[attribute]) ? 1 : -1
});
}
//some functions have to be wrapped, to produce a List of the right type
filter(fn){
return new (this.constructor)(AP.filter.call(this, fn));
}
clone(){ return new (this.constructor)(this) }
}
//some functions can simply be copied from Array
//no need to re-implement or even wrap them.
List.prototype.sort = AP.sort;
List.prototype.push = AP.push;
List.prototype.pop = AP.pop;
the subclass
class ToyList extends List {
constructor(toys){
//maybe you want to filter the input, before you pass it to the list
//or convert it, or whatever, it's all up to you
super(toys && AP.filter.call(toys, v=>v instanceof Toy));
}
//... additional functionality
}
and an example usage
class Toy {
constructor(name){
this.name = name;
}
}
var a = new ToyList([
new Toy("foo"),
new Toy("bar"),
"not a toy",
new Toy("baz")
])
console.log(a instanceof ToyList, a);
var b = a.filter(toy => toy.name.charAt(0) === "b");
console.log(b instanceof ToyList, b);
Edit: added your Example with the Todos
class Todos extends List {
//don't even need a constructor, since I simply want to pass
//the array to the parent-constructor
//don't use getter for functionality, use methods!
byPriority(){
return this.sortBy('priority');
}
}
var thingsToDo = [
{task: 'wash the dog', priority: 10},
{task: 'do taxes', priority: 1},
{task: 'clean the garage', priority: 0}
]
var todos = new Todos(thingsToDo);
todos.byPriority()
ToyList has both data and toys properties. These refer to the same array, in terms of conceptualizing the point of a ToyList, data doesn’t make much sense.
There's your actual problem: your ToyList doesn't make sense as a subclass of List.
If (for any reasons) your class should be similar to List, but not have a data property, then it's not a subclass any more. It would violate the Liskov substitution principle.
Now what are your options?
as you already considered, you can make it a subclass in which the more specific .toys property is an alias for .data. This is perfectly fine, but you can't avoid having that data property there as well.
you might want to outiright scrap that data property and store elements directly on the object. Your List class looks like "Array but with useful helper functions". If that was your intention, you should consider making it an actual subclass of Array. #Thomas's answer goes in that direction.
you might want to favor composition over inheritance. You've already used the concept - your List instances contain Arrays in their data properties. If you have a Wishlist or Toylist or whatever, that deal specifically with whishes or toys and have corresponding methods for them, you can simply store a List instance in their .toys slot.
You actually seemed to expect your TodoList to work like that, given the invocation of this.todos.sortBy('priority') (where this.todos would be a List). On an subclass, just this.sortBy('priority') would do the job.
I didn't really get how your ToyList is a specialisation of List. If there is nothing special about it but the name, maybe you actually don't need a different class alltogether. If JavaScript had generics or type variables, you'd use a List<Toy>, but it doesn't so you can just use Lists directly.
I think you have a lot of different problems.
Your list has a problem with the definition of sortBy, you need to take 3 cases in account, like this:
class List {
constructor(data){ this.data = data; }
sortBy(attribute){
console.log("sortBy");
return this.data.sort( (a,b) => {
if (a[attribute] < b[attribute]) return -1;
if (a[attribute] > b[attribute]) return 1;
return 0;
});
}
get count() { return this.data.length; }
}
Now you can extend the List, and if you want to name data as toys then define a get method named toys() to return the data. It may strange to you, but if you subclass List then you should use data (if not, don't subclass it). There is an alternative: you can delete data attribute and then create toys but alas, designing a sortBy method in List would be difficult (or use a second parameter to name the array to sort?). So, let's use the first suggestion:
class ToyList extends List {
constructor(toys){ super(toys); }
get toys() { return this.data; }
}
Let do the same for Todos:
class Todos extends List {
constructor(todos){ super(todos); }
get todos() { return data; }
get byPriority(){
return this.sortBy('priority');
}
}
The definition of byPriority is a little bit weird as it has a border effect (sorting the elements). I (personally) would write it as a standard method.
Then let's make some tests:
var thingsToDo = [
{task: 'wash the dog', priority: 10},
{task: 'do taxes', priority: 1},
{task: 'clean the garage', priority: 3}
];
var tl = new ToyList(['truck', 'plane', 'doll']);
for (var i=0; i<3; i++) {
console.log(tl.toys[i]); // access the *pseudo* toys attribute
}
var todos = new Todos(thingsToDo);
var r = todos.byPriority; // access the *pseudo* byPriority attribute (border effect: sorting internal data)
for (var i=0; i<3; i++) {
console.log(todos.data[i].priority);
}
May I suggest you to have a little more read about OOP and inheritance? The point of need to subclass but removing data attribute is certainly a bad design.
IMO, a better approach, if possible, would be to treat the List class are pure virtual, Meaning you will never create an instance of that class, but only just extend from it.
Pure virtual classes are not supposed to have constructors, and the methods are to assume certain properties exist. However, you could infact use the constructor to set the name that the base class should use for the 'data' property.
class List {
constructor(keyName) { this.keyName = keyName }
sortBy(attr) { return this[this.keyName].sort(...) }
}
class ToyList extends List {
constructor('toys') {
super(toys)
this.toys = toys;
}
}
I am using MONGOHQ and NODE.JS to make an application, I would really like the help of someone because I have a big problem, I tried everything and look everywhere, so this is my last option.
I have this query on a node JS post:
{ propertyId: '53f4f097f28c16cf87a15664' }
This query is being used here :
db.collection('animal').find(req.body.query).toArray(function (err, animals) {
res.send(
(err === null) ? { status: 'ok', result: animals } : { status: err }
);
});
And its working FINE !
Now the problem :
What I want is to add another query to the find, without modifying the req.body.query.
What I have is a list of _id (not ObjectIds), called myArray, and it's like :
[ '12312123' , '78787878' ]
So I am doing this :
db.collection('animal').find({ $and : [ req.body.query, { '_id' : { $in : myArray} } ] }).toArray(function (err, animals) {
res.send(
(err === null) ? { status: 'ok', result: animals } : { status: err }
);
});
And its giving me an empty list.
But instead, if I do the same query on mongohq like :
find({$and : [ { propertyId: '53f4f097f28c16cf87a15664' }, { '_id' : { $in :['12312123', '78787878']} } ] })
Its giving me a list with 1 element that matches the criteria:
{
_id: "12312123",
propertyId: "53f4f097f28c16cf87a15664",
active: "true"
}
Does anyone have an idea ?
I would be glad to hear !
Thanks
Michael
Your question doesn't specify why you don't want to modify req.body.query, but there could be many reasons for that, so here's how you could make a copy of it before modifying it, that way you don't even need to bother with $and:
var query = req.body.query;
//make a copy of the query object
var queryCopy = {};
for (var i in query) {
queryCopy[i] = query[i];
}
//add your additional conditions
queryCopy._id: { $in : myArray} };
var result = db.collection('animal').find(queryCopy);
//result.toArray(...)
Note that this approach only creates a shallow copy of the object, which is fine in this case because you're not modifying any nested objects or arrays. If you want you could add a function for creating shallow copies, here's one you could use (I prefer to use Object.keys() for this generic function instead of the var i in... approach above because it doesn't unnecessarily copy prototype properties [although sometimes you might want that] but it doesn't make a difference in your case.)
function shallowCopy(obj) {
var copy = {},
keys = Object.keys(obj),
len = keys.length;
for (var i=0; i < len; i++) {
copy[keys[i]] = obj[keys[i]];
}
return copy;
}
If you DO need to create a deep copy of an object because you want to modify nested objects and arrays without affecting the original object, then you'll need to use a function specifically designed for that purpose, since Javascript doesn't provide a native function to do that. Here's a deep copy function (from my answer to a question about that) - use the more robust version since that has proper support for arrays, regular expressions, and dates:
https://stackoverflow.com/a/13333781/560114
I'm currently working with an object Literal to store temporary information to send to clients, it's like a history container for the last 10 sets of data.
So the issue that I', having is figuring out the most efficient way to splice on object as well as push an object in at the start, so basically i have an object, this object has 0 data inside of it.
I then insert values, but what I need to do is when the object reaches 10 keys, I need to pop the last element of the end of object literal, push all keys up and then insert one value at the start.
Take this example object
var initializeData = {
a : {},
b : {},
c : {},
d : {},
e : {},
f : {},
g : {},
h : {},
i : {},
j : {}
}
When I insert an element I need j to be removed, i to become the last element, and a to become b.
So that the new element becomes a.
Can anyone help me solve this issue, I am using node.js but native JavaScript is fine obviously.
Working with arrays after advice from replies, this is basically what I am thinking your telling me would be the best solution:
function HangoutStack(n)
{
this._array = new Array(n);
this.max = n;
}
HangoutStack.prototype.push = function(hangout)
{
if(this._array.unshift(hangout) > this.max)
{
this._array.pop();
}
}
HangoutStack.prototype.getAllItems = function()
{
return this._array;
}
Sounds like it would be a lot easier to use an array. Then you could use unshift to insert from the beginning and pop to remove from the end.
Edit: Example:
var items = []
function addItem(item) {
items.unshift(item)
if (items.length > 10) {
items.pop()
}
}
Alternatively, you could use push/shift instead of unshift/pop. Depends on which end of the array you prefer the new items to sit at.
What you need is called a Circular Buffer.
Have a look at this implementation in javascript
Yeah, use an Array.
var arr = [];
// adding an item
if (arr.unshift(item) > 10) {
arr.pop();
}
If you need the 'name' of the item, like "a" or "b" in your object example, just wrap each item in another object that contains the name and the object.
Objects in js are like dictionaries -- they have no inherent order to the items. It's just a collection of things. You can try to make up an order (like a through z in your example), but then you have to manage that order yourself when things change. It's just much easier to use an array.
I have a JSON tree that contains nodes and children - the format is:
jsonObject =
{
id:nodeid_1,
children: [
{
id:nodeid_2,
children:[]
},
{
id:nodeid_3,
children:[
{
id:nodeid_4,
children:[]
},
{
id:nodeid_5,
children:[]
}
}
}
I don't know the depth of this tree, a node is capable of having many children that also have many children and so on.
My problem is that I need to add nodes into this tree by using a nodeID. For example, a function that can take a nodeID and the node object (including its children), would be able to replace that node within the tree - which as a result would become a bigger tree.
I have only come across recursive functions that allow me to traverse all the nodes within a JSON tree and a modification I have made of one of these functions returns me the node object - but doesn't help me as I need to modify the original tree:
var findNode = {
node:{},
find:function(nodeID,jsonObj) {
if( typeof jsonObj == "object" ) {
$.each(jsonObj, function(k,v) {
if(v == nodeID) {
findNode.node = $(jsonObj).eq(0).toArray()[0];
} else {
findNode.find(nodeID,v);
}
});
} else {
//console.log("jsobObj is not an object");
}
}
}
which allows me to do the following test:
findNode.find("nodeid_3",json);
alert(findNode.node);
So to sum up - how can I modify a value of a JSON tree that has an unknown depth?
Thanks in advance
If you want to modify a node, like you said, you can just modify the properties of that node.
var node = findNode.find("nodeid_3",json);
node.id = "nodeid_3_modified";
node.children = [];
Also, why are you using jQuery for this?
Here's an alternative without using jQuery should work:
function findNode(object, nodeId) {
if (object.id === nodeId) return object;
var result;
for (var i = 0; i < object.children.length; i++) {
result = findNode(object.children[i], nodeId);
if (result !== undefined) return result;
}
}
That is not JSON, it is a Javascript object literal. JSON is a certain way to encode a simple Javascript object into a string; your example is not written that way. Also, your code does not manipulate JSON objects (which are actually strings, not objects); it manipulates Javascript objects (which is a way simpler task).
That said, I'm not sure what your actual question is, but if it's about adding new elements to the children array, you can use Array.push:
findNode.find("nodeid_3",json);
findNode.node.children.push(child);
(This assumes that findNode.find actually works, which I'm pretty sure it does not.)