I am playing around with a binary tree. I am trying to use recursion to find all the nested children's values and push all the values into an array. I started with the left tree to see if it works. I tried to call childrenArray() but the console says childrenArray() is not defined. When I ask if (typeof BinaryTree.prototype.childrenArray === 'function'), it returns true. Please teach me and tell me why I wasn't able to execute my code?
var Tree = function(value) {
if (!(this instanceof Tree)) {
return new Tree(value);
}
this.value = value;
this.children = [];
};
Tree.prototype.addChild = function(value) {
var child = new Tree(value);
this.children.push(child);
};
Tree.prototype.contains = function(value) {
if (this.value === value) {
return true;
} else {
for (var i = 0; i < this.children.length; i++) {
if (this.children[i] && this.children[i].contains(value)) {
return true;
}
}
return false;
}
};
var BinaryTree = function(value) {
if (!(this instanceof BinaryTree)) {
return new BinaryTree(value);
}
Tree.call(this, value);
};
BinaryTree.prototype = Object.create(Tree.prototype);
BinaryTree.prototype.addChild = function(value) {
if (value < this.value) {
if (this.children[0] === undefined) {
this.children[0] = new BinaryTree(value);
}
this.children[0].addChild(value);
} else if (value > this.value) {
if (this.children[1] === undefined) {
this.children[1] = new BinaryTree(value);
}
this.children[1].addChild(value);
}
};
BinaryTree.prototype.contains = function(value) {
if (value < this.value) {
if (this.children[0] === undefined) {
return false;
}
return this.children[0].contains(value);
} else if (value > this.value) {
if (this.children[1] === undefined) {
return false;
}
return this.children[1].contains(value);
}
};
var a = new BinaryTree();
a.value = 10;
a.addChild(4);
a.addChild(11);
a.addChild(3);
BinaryTree.prototype.childrenArray = function() {
var results = [];
if (this.value) {
results.push(this.value);
}
if (this.children[0].length === 0) {
return results;
}
for (var i = 0; i < this.children[0].children.length; i++) {
if (this.children[i].value) {
results.push(this.children[i].value);
return this.childrenArray();
}
}
};
a.childrenArray();
As #melpomene mentioned, you are invoking childArray but you didn't define it anywhere. I assume the line return childArray(); ended up there by mistake, you probably meant to recursively return childrenArray for the left child of root.
I would like to mention that your loop itself (without the recursive call):
for(var i = 0; i < this.children[0].children.length; i++) { // <-- you are iterating over the children of root's left child
if(this.children[i].value) { // <-- but you are accessing root's current child
results.push(this.children[i].value);
}
}
is somewhat confusing. You are iterating over the children of root's left child children[0].children, but on each iteration you check if the root's children themselves children[i] have a value and you push that value.
This is incorrect and will break if, for example, the root only has a left child that has both children (i will be out of bounds).
Here's how you can approach this problem. The recursion to print the left tree in your case can be broken down into the following cases:
If the current node has no value, return an empty array
Else If the current node has no children, return an array only containing the current value
Else if the current node has a left child, run childrenArray on it recursively and add the results to the results array
Here's how that would look:
BinaryTree.prototype.childrenArray = function() {
var results = [];
// case 1:
if (this.value === undefined) {
return results;
}
// case 2:
results.push(this.value);
if (this.children.length === 0) {
return results;
}
// case 3:
var leftChild = this.children[0];
if (leftChild) {
results = results.concat(leftChild.childrenArray());
}
/* add code here for the right child to complete your function */
return results;
};
Full Example:
var BinaryTree = function(value) {
this.value = value;
this.children = [];
};
BinaryTree.prototype.addChild = function (value) {
if (value < this.value) {
if (this.children[0] === undefined) {
this.children[0] = new BinaryTree(value);
}
this.children[0].addChild(value);
} else if (value > this.value) {
if (this.children[1] === undefined) {
this.children[1] = new BinaryTree(value);
}
this.children[1].addChild(value);
}
};
BinaryTree.prototype.childrenArray = function() {
var results = [];
// case 1:
if (this.value === undefined) {
return results;
}
// case 2:
results.push(this.value);
if (this.children.length === 0) {
return results;
}
// case 3:
var leftChild = this.children[0];
if (leftChild) {
results = results.concat(leftChild.childrenArray());
}
/* add code here for the right child to complete your function */
return results;
};
var a = new BinaryTree(10);
a.addChild(4);
a.addChild(11);
a.addChild(3);
console.log(a.childrenArray()); // [10, 4, 3]
Last thing, based on your insertion logic (insert left if smaller, right if larger), you are building a Binary Search Tree not a Binary Tree. Binary tree's don't follow any particular insertion logic. Take a look at this question for clarification
Related
I have been trying to translate my code from es6 to es5 because of some framework restrictions at my work... Although I have been quite struggling to locate what the problem is. For some reason the code does not work quite the same, and there is no errors either ...
Can someone tell me If I have translated properly ?
This is the ES6 code :
function filterFunction(items, filters, stringFields = ['Title', 'Description'], angular = false) {
// Filter by the keys of the filters parameter
const filterKeys = Object.keys(filters);
// Set up a mutable filtered object with items
let filtered;
// Angular doesn't like deep clones... *sigh*
if (angular) {
filtered = items;
} else {
filtered = _.cloneDeep(items);
}
// For each key in the supplied filters
for (let key of filterKeys) {
if (key !== 'TextInput') {
filtered = filtered.filter(item => {
// Make sure we have something to filter by...
if (filters[key].length !== 0) {
return _.intersection(filters[key], item[key]).length >= 1;
}
return true;
});
}
// If we're at TextInput, handle things differently
else if (key === 'TextInput') {
filtered = filtered.filter(item => {
let searchString = "";
// For each field specified in the strings array, build a string to search through
for (let field of stringFields) {
// Handle arrays differently
if (!Array.isArray(item[field])) {
searchString += `${item[field]} `.toLowerCase();
} else {
searchString += item[field].join(' ').toLowerCase();
}
}
// Return the item if the string matches our input
return searchString.indexOf(filters[key].toLowerCase()) !== -1;
});
}
}
return filtered;
}
And this is the code I translated that partially 99% work ..
function filterFunction(items, filters, stringFields, angular) {
// Filter by the keys of the filters parameter
var filterKeys = Object.keys(filters);
// Set up a mutable filtered object with items
var filtered;
// Angular doesn't like deep clones... *sigh*
if (angular) {
filtered = items;
} else {
filtered = _.cloneDeep(items);
}
// For each key in the supplied filters
for (var key = 0 ; key < filterKeys.length ; key ++) {
if (filterKeys[key] !== 'TextInput') {
filtered = filtered.filter( function(item) {
// Make sure we have something to filter by...
if (filters[filterKeys[key]].length !== 0) {
return _.intersection(filters[filterKeys[key]], item[filterKeys[key]]).length >= 1;
}
return true;
});
}
// If we're at TextInput, handle things differently
else if (filterKeys[key] === 'TextInput') {
filtered = filtered.filter(function(item) {
var searchString = "";
// For each field specified in the strings array, build a string to search through
for (var field = 0; field < stringFields.length; field ++) {
// Handle arrays differently
console.log(field);
if (!Array.isArray(item[stringFields[field]])) {
searchString += item[stringFields[field]] + ' '.toLowerCase();
} else {
searchString += item[stringFields[field]].join(' ').toLowerCase();
}
}
// Return the item if the string matches our input
return searchString.indexOf(filters[filterKeys[key]].toLowerCase()) !== -1;
});
}
}
return filtered;
}
These two lines
searchString += `${item[field]} `.toLowerCase();
searchString += item[stringFields[field]] + ' '.toLowerCase();
are not equivalent indeed. To apply the toLowerCase method on all parts of the string, you'll need to wrap the ES5 concatenation in parenthesis:
searchString += (item[stringFields[field]] + ' ').toLowerCase();
or, as blanks cannot be lowercased anyway, just use
searchString += item[stringFields[field]].toLowerCase() + ' ';
Here is a translated code from babeljs itself, as commented above.
'use strict';
function filterFunction(items, filters) {
var stringFields = arguments.length <= 2 || arguments[2] === undefined ? ['Title', 'Description'] : arguments[2];
var angular = arguments.length <= 3 || arguments[3] === undefined ? false : arguments[3];
// Filter by the keys of the filters parameter
var filterKeys = Object.keys(filters);
// Set up a mutable filtered object with items
var filtered = void 0;
// Angular doesn't like deep clones... *sigh*
if (angular) {
filtered = items;
} else {
filtered = _.cloneDeep(items);
}
// For each key in the supplied filters
var _iteratorNormalCompletion = true;
var _didIteratorError = false;
var _iteratorError = undefined;
try {
var _loop = function _loop() {
var key = _step.value;
if (key !== 'TextInput') {
filtered = filtered.filter(function (item) {
// Make sure we have something to filter by...
if (filters[key].length !== 0) {
return _.intersection(filters[key], item[key]).length >= 1;
}
return true;
});
}
// If we're at TextInput, handle things differently
else if (key === 'TextInput') {
filtered = filtered.filter(function (item) {
var searchString = "";
// For each field specified in the strings array, build a string to search through
var _iteratorNormalCompletion2 = true;
var _didIteratorError2 = false;
var _iteratorError2 = undefined;
try {
for (var _iterator2 = stringFields[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) {
var field = _step2.value;
// Handle arrays differently
if (!Array.isArray(item[field])) {
searchString += (item[field] + ' ').toLowerCase();
} else {
searchString += item[field].join(' ').toLowerCase();
}
}
// Return the item if the string matches our input
} catch (err) {
_didIteratorError2 = true;
_iteratorError2 = err;
} finally {
try {
if (!_iteratorNormalCompletion2 && _iterator2.return) {
_iterator2.return();
}
} finally {
if (_didIteratorError2) {
throw _iteratorError2;
}
}
}
return searchString.indexOf(filters[key].toLowerCase()) !== -1;
});
}
};
for (var _iterator = filterKeys[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
_loop();
}
} catch (err) {
_didIteratorError = true;
_iteratorError = err;
} finally {
try {
if (!_iteratorNormalCompletion && _iterator.return) {
_iterator.return();
}
} finally {
if (_didIteratorError) {
throw _iteratorError;
}
}
}
return filtered;
}
p.s. Or there is a better way to use babeljs directly without manually converting it.
Given the following obj:
var inputMapping = {
nonNestedItem: "someItem here",
sections: {
general: "Some general section information"
}
};
I'm writing a function to get that data by passing in a string "nonNestedItem" or in the nested case "sections.general". I'm having to use an eval and I was wondering if there was maybe a better way to do this.
Here is what I have so far and it works okay. But improve!
function getNode(name) {
var n = name.split(".");
if (n.length === 1) {
n = name[0];
} else {
var isValid = true,
evalStr = 'inputMapping';
for (var i=0;i<n.length;i++) {
evalStr += '["'+ n[i] +'"]';
if (eval(evalStr) === undefined) {
isValid = false;
break;
}
}
if (isValid) {
// Do something like return the value
}
}
}
Linky to Jsbin
You can use Array.prototype.reduce function like this
var accessString = "sections.general";
console.log(accessString.split(".").reduce(function(previous, current) {
return previous[current];
}, inputMapping));
Output
Some general section information
If your environment doesn't support reduce, you can use this recursive version
function getNestedItem(currentObject, listOfKeys) {
if (listOfKeys.length === 0 || !currentObject) {
return currentObject;
}
return getNestedItem(currentObject[listOfKeys[0]], listOfKeys.slice(1));
}
console.log(getNestedItem(inputMapping, "sections.general".split(".")));
You don't need to use eval() here. You can just use [] to get values from an object. Use a temp object to hold the current value, then update it each time you need the next key.
function getNode(mapping, name) {
var n = name.split(".");
if (n.length === 1) {
return mapping[name];
} else {
var tmp = mapping;
for (var i = 0; i < n.length; i++) {
tmp = tmp[n[i]];
}
return tmp;
}
}
Simply put
I have a tree structure made of objects.
Is it possible to build that tree and add to each object a reference to their parent ?
I know referencing works with objects, but i'm not sure if it would in that case?
I would like to be able to write something like this
currentLevel = this.getParent();
another exemple would be
this.getChildList().addChild({name: test,parent: this})
Without having copies and creating multiple tree from the first one.
2nd question
How would referencing works with array? Are they considered objects or does it depends on their content?
3nd question
Would saving the tree in the browser's cache, via string-JSON serialisation destroy the references?
You can do this be creating a "TreeNode" class:
var TreeNode = (function(){
//keep track of parent node
TreeNode.prototype.parent = null;
//keep track of children
TreeNode.prototype.children = [];
function TreeNode(parent) {
if(parent !== undefined) {
if(this.setParent(parent)) {
this.parent.addChild(this);
}
}
//...
}
TreeNode.prototype.setParent = function(parent) {
//add some sort of check to make sure it is a `TreeNode`
if(parent instanceof TreeNode) {
this.parent = parent;
return true;
}
return false;
}
TreeNode.prototype.addChild = function(child) {
//add some sort of check to make sure it is a `TreeNode`
if(child instanceof TreeNode) {
this.children.push(child);
child.setParent(this);
}
}
TreeNode.prototype.getParent = function(){
return this.parent;
}
TreeNode.prototype.getChildren = function(){
return this.children;
}
return TreeNode;
})();
And then you can expand from that.
Example Code:
var node_a = new TreeNode();
var node_b = new TreeNode(node_a);
var node_c = new TreeNode(node_a);
console.log(node_a.getParent(), node_c.get_parent()); //null , node_a
console.log(node_a.getChildren()); //[node_b, node_c]
This is just a start, it needs waaaaaaaaaay more expansion :-)
Okay, so there are most likely frameworks out there, but I wrote a quick thing which supports JSON serialisation and the reverse (via it's own methods). I took base inspiration from Neal's answer. Example
var a = new MyTreeNode('a'), // make some nodes
b = new MyTreeNode('b'),
c = new MyTreeNode('c');
a.addChild(b).addChild(c); // a parent of b parent of c
c.getParent() === b; // true
var str = a.toJSON(); // "{"nodeName":"a","childNodes":[{"nodeName":"b","childNodes":[{"nodeName":"c","childNodes":[]}]}]}"
MyTreeNode.parseJSON(str); // MyTreeNode (same structure as before)
Full code
/* MyTreeNode(String nodeName)
Instance Properties
- nodeName, String
- childNodes, Array of MyTreeNodes
- parentNode, MyTreeNode
Instance Methods
- addChild(MyTreeNode node), child MyTreeNode
- removeChild(MyTreeNode node), child MyTreeNode
- getParent, parent MyTreeNode
- getChildList, Array of MyTreeNodes
- serialise, JSON-safe Object
- toJSON, String
Constructor Methods
- deserialise(Object serialised), MyTreeNode
- parseJSON(String JSONString), MyTreeNode
*/
var MyTreeNode = (function () {
function MyTreeNode(nodeName) {
nodeName && (this.nodeName = nodeName);
this.childNodes = [];
}
MyTreeNode.prototype.parentNode = null;
MyTreeNode.prototype.childNodes = [];
MyTreeNode.prototype.nodeName = '';
// getters
MyTreeNode.prototype.getChildList = function () {
return this.childNodes = [];
};
MyTreeNode.prototype.getParent = function () {
return this.parentNode;
};
// add/remove
MyTreeNode.prototype.removeChild = function (node) {
var i = this.childNodes.indexOf(node);
if (node.parentNode !== this || i == -1)
throw new ReferenceError('node is not a child of this');
this.childNodes.splice(i, 1);
node.parentNode = null;
return node;
};
MyTreeNode.prototype.addChild = function (node) {
if (node.parentNode) node.parentNode.removeChild(node);
node.parentNode = this;
this.childNodes.push(node);
return node;
};
// JSON
MyTreeNode.prototype.serialise = function () {
var o = {
nodeName: this.nodeName,
childNodes: []
}, i;
for (i = 0; i < this.childNodes.length; ++i) {
o.childNodes.push(this.childNodes[i].serialise());
}
return o;
};
MyTreeNode.prototype.toJSON = function () {
return JSON.stringify(this.serialise());
};
MyTreeNode.deserialise = function (o) {
var p = new MyTreeNode(o.nodeName), i;
for (i = 0; i < o.childNodes.length; ++i) {
p.addChild(MyTreeNode.deserialise(o.childNodes[i]));
}
return p;
};
MyTreeNode.parseJSON = function (str) {
var o = JSON.parse(str);
return MyTreeNode.deserialise(o);
};
return MyTreeNode;
}());
You could traverse your object and add parent properties to every subobject:
function addParents(obj) {
var name;
for (name in obj) {
if (typeof obj[name] === "object") {
addParents(obj[name]);
obj[name].parent = obj;
}
}
}
var obj = {
g: {
k: [
{
r : 1
},
{
r : 1
}
],
j: {
h: 1
}
}
};
addParents(obj);
console.log(obj.g.parent === obj); //true
console.log(obj.g.k.parent === obj.g); //true
console.log(obj.g.k[1].parent === obj.g.k); //true
console.log(obj.g.j.parent === obj.g); //true
And if you want to add objects later on, you could use something like this:
function addChild(obj, child, name){
obj[name] = child;
child.parent = obj;
}
addChild(obj.g, {t:1}, "xy");
console.log(obj.g.xy.parent === obj.g); //true
FIDDLE
I'm trying to work with a plugin which extends jQuery like so:
$.extend({
StatelessDeferred: function () {
var doneList = $.Callbacks("memory"),
promise = {
done: doneList.add,
// Get a promise for this deferred
// If obj is provided, the promise aspect is added to the object
promise: function (obj) {
var i,
keys = ['done', 'promise'];
if (obj === undefined) {
obj = promise;
} else {
for (i = 0; i < keys.length; i += 1) {
obj[keys[i]] = promise[keys[i]];
}
}
return obj;
}
},
deferred = promise.promise({});
deferred.resolveWith = doneList.fireWith;
return deferred;
}
});
Problem is (and I'm not even sure it's caused here), after the callback loads, inside a done callback, both this and $(this) are the same, so I end up for example with: this === $(this) === $(document).
I'm not really sure I understand what's being extended. The plugin works fine with it except for the false assignment.
Question:
Could the above extension be causing this === $(this) === $(document)?
EDIT:
Full plugin (120lines):
"use strict";
(function (window, $) {
$.extend({
StatelessDeferred: function () {
var doneList = $.Callbacks("memory"),
promise = {
done: doneList.add,
// Get a promise for this deferred
// If obj is provided, the promise aspect is added to the object
promise: function (obj) {
var i,
keys = ['done', 'promise'];
if (obj === undefined) {
obj = promise;
} else {
for (i = 0; i < keys.length; i += 1) {
obj[keys[i]] = promise[keys[i]];
}
}
return obj;
}
},
deferred = promise.promise({});
deferred.resolveWith = doneList.fireWith;
// All done!
return deferred;
}
});
var routes = [],
current_priority = 0,
methods = {
add: function (pattern, priority) {
var i = 0,
inserted = false,
length = routes.length,
dfr = $.StatelessDeferred(),
context = $(this),
escapepattern,
matchingpattern;
if (priority === undefined) {
priority = 0;
}
if (pattern !== undefined) {
// http://simonwillison.net/2006/Jan/20/escape/
escapepattern = pattern.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, "\\$&");
matchingpattern = escapepattern
.replace(/<int:\w+>/g, "(\\d+)")
.replace(/<path:\w+>/g, "(.+)")
.replace(/<\w+>/g, "([^/]+)");
while (!inserted) {
if ((i === length) || (priority >= routes[i][2])) {
routes.splice(i, 0, [new RegExp('^' + matchingpattern + '$'), dfr, priority, context]);
inserted = true;
} else {
i += 1;
}
}
}
return dfr.promise();
},
go: function (path, min_priority) {
var dfr = $.Deferred(),
context = $(this),
result;
if (min_priority === undefined) {
min_priority = 0;
}
setTimeout(function () {
var i = 0,
found = false,
slice_index = -1,
slice_priority = -1;
for (i = 0; i < routes.length; i += 1) {
if (slice_priority !== routes[i][2]) {
slice_priority = routes[i][2];
slice_index = i;
}
if (routes[i][2] < min_priority) {
break;
} else if (routes[i][0].test(path)) {
result = routes[i][0].exec(path);
dfr = routes[i][1];
context = routes[i][3];
current_priority = routes[i][2];
found = true;
break;
}
}
if (i === routes.length) {
slice_index = i;
}
if (slice_index > -1) {
routes = routes.slice(slice_index);
}
if (found) {
dfr.resolveWith(
context,
result.slice(1)
);
} else {
dfr.rejectWith(context);
}
});
return dfr.promise();
},
};
$.routereset = function () {
routes = [];
current_priority = 0;
};
$.routepriority = function () {
return current_priority;
};
$.fn.route = function (method) {
var result;
if (methods.hasOwnProperty(method)) {
result = methods[method].apply(
this,
Array.prototype.slice.call(arguments, 1)
);
} else {
$.error('Method ' + method +
' does not exist on jQuery.route');
}
return result;
};
}(window, jQuery));
So I can use this as a router and set a route like so:
$(".element").add("route", "/foo/bar/<path:params>", 2).done(function(params){
// do something, for example
console.log(this);
console.log($(this));
console.log("which will be the same = $('.element'));
});
Hope it's more clear now.
Thanks for having a look.
From the documentation:
If only one argument is supplied to $.extend(), this means the target argument was omitted. In this case, the jQuery object itself is assumed to be the target.
Most cases, jQuery is attached to your document with : $(document).ready()
I think what's happening is jQuery object is wrapped onto the document. Then you merged it with $.extend(myObject). This returns a single object that is both jQuery object and myObject.
I want to create a js class that resembles simple music playlist (Array). I want to instantiate this playlist with IDs, each ID being a track ID in my database. I have interface like this:
function Playlist() {
Playlist.prototype.current = 0;
Playlist.prototype.prev = function() {
if (this.current-1 < 0) {
return null;
}
return this[--this.current];
};
Playlist.prototype.next = function() {
if (this.current+1 >= this.length) { // length is index + 1
return null;
}
return this[++this.current];
};
Playlist.prototype.seek = function(id) {
for (i in this) {
if (this[i] == id) {
this.current = parseInt(i);
return i;
}
}
return false;
};
Playlist.prototype.getCurrent() {
return this.current;
};
};
The code above DOES NOT do what I want, because I imagine it as class that has it's method defined, that can be instantiated like this:
var newPlaylist = Playlist(2,3,5,10/* those are my ids */);
And currently the only way I've found is something like:
Playlist.prototype = new Array(2, 3, 5, 10/* those are my ids */);
Which does not make any sense since it can be instantiated as different objects. Any ideas are very welcome!
Best way - nested array;
function Playlist() {
this.current = 0;
this.list = Array.prototype.slice.call(arguments);;
};
Playlist.prototype.prev = function() {
if (this.current-1 < 0) {
return null;
}
return this.list[--this.current];
};
Playlist.prototype.next = function() {
if (this.current+1 >= this.list.length) { // length is index + 1
return null;
}
return this.list[++this.current];
};
Playlist.prototype.getCurrent = function() {
return this.current;
};
var newPlaylist = new Playlist(2,3,5,10/* those are my ids */);
But you can't use list[i] to get element by index, but you just need add at() method to your class that provide similar functionality
PlayList.prototype.at(i) {
return this.list[i];
}
Since you cannot subclass Array, you should build wrapper objects with your Playlist constructor:
Playlist = (function() {
function Playlist(list) {
this.list = list || [];
}
Playlist.prototype.current = 0;
Playlist.prototype.prev = function() {
if (this.current <= 0)
return null;
return this.list[--this.current];
};
Playlist.prototype.next = function() {
if (this.current+1 >= this.length)
return null;
return this.list[++this.current];
};
Playlist.prototype.seek = function(id) {
return this.list.indexOf(id);
};
return Playlist;
})();
Usage:
var newPlaylist = new Playlist([2,3,5,10]);