how to return to a binary tree multiple nodes in a array or
a set
simple solution
There are a few issues in your code:
It expects internal nodes to have exactly two children. You should make use of the method getNumberOfChildren
path shouldn't be a global variable, as that will pollute any further call of getPaths. I would suggest to use a generator function, so that the caller can take care of putting the results in their own array variable.
Obviously root.getValue() === pathsToFind is never going to be true as pathsToFind is an array, and the root's value is an integer. You can use includes, but that will bring a bad time complexity when the array is long. So I'd suggest to turn that array into a Set.
It wasn't explained in the question, but it seems like you don't want to include a path to a node, when that node is on a path to another node that needs to be included. In other words, when the array of values has pairs where one is the ancestor of the other, the ancestor can be ignored.
Here is how I would suggest to implement it (with generator):
function* generatePaths(root, targetSet) {
if (!root) return;
const val = root.getValue();
let foundPathsWithThisValue = false;
for (let child = 0; child < root.getNumberOfChildren(); child++) {
for (const path of generatePaths(root.getChild(child), targetSet)) {
path.push(val);
yield path;
foundPathsWithThisValue = true;
}
}
if (!foundPathsWithThisValue && targetSet.has(val)) {
yield [val];
}
}
function getPaths(root, targetValues) {
return Array.from(generatePaths(root, new Set(targetValues)), path =>
path.reverse().join("->")
);
}
// No change below
class Node {
constructor(value) {
this.value = value;
this.children = [];
}
addChild(node) {
this.children.push(node);
}
getValue() {
return this.value;
}
getChild(i) {
return this.children[i];
}
getNumberOfChildren() {
return this.children.length;
}
print(indent = 0) {
console.log("-".repeat(indent), this.value);
for (const child of this.children) {
child.print(indent + 4);
}
}
}
const root = new Node(0);
const leftRoot = new Node(10);
const rightRoot = new Node(500);
root.addChild(leftRoot);
root.addChild(rightRoot);
const leftChild = new Node(150);
const rightChild = new Node(250);
leftRoot.addChild(leftChild);
leftRoot.addChild(rightChild);
const leftleftChild = new Node(200);
leftChild.addChild(leftleftChild);
const rightrightChild = new Node(300);
rightChild.addChild(rightrightChild);
// will output [ '0 -> 10 -> 150 -> 200', '0 -> 500' ]
console.log(getPaths(root, [150, 200, 500]));
You are free to use your accessors instead of root.children. But I guess it is clearer like this, at least for the answer.
Be careful, your property 'root' has the same name as the global variable root
function getPaths(root, pathsToFind) {
let match = false;
if (root === null) return [];
if (pathsToFind.includes(root.getValue())) {
match = true;
}
const pathChildren = root.children.map(child => getPaths(child, pathsToFind))
.filter(child => child.length > 0)
.map(value => `${root.getValue()} -- ${value}`);
if (pathChildren.length > 0) {
return pathChildren
} else {
return match ? [`${root.getValue()}`] : []
}
}
Related
I'm trying to make a binary search tree. If I start with an array my function makes a binary search tree out of that array (everything fine here). like for an array like let a = [2,4,5,3,9,7,3,8,5]; the tree looks like the picture. my problem is with the insert() function. If I start with an empty array and add a node to it, it works. However, when I add a second node, that second node won't be added to my tree, and my tree is shown as having only 1 node in it (the root node). Here the snippet:
const Node = (data, left = null, right = null) => {
return {data, left, right};
};
const Tree = array => {
const remDupsAndSort = array => {
const mergeSort = array => {
if(array.length <= 1) return array;
let leftArr = array.slice(0, array.length / 2);
let rightArr = array.slice(array.length / 2);
return merge(mergeSort(rightArr), mergeSort(leftArr))
};
const merge = (leftArr, rightArr) => {
let sorted = [];
while(leftArr.length && rightArr.length){
if(leftArr[0] < rightArr[0]){
sorted.push(leftArr.shift());
}else{
sorted.push(rightArr.shift());
}
};
return [...sorted, ...leftArr, ...rightArr]
};
return mergeSort([... new Set(array)])
};
array = remDupsAndSort(array);
const buildTree = (array, start, end) => {
if(start > end) return null;
let mid = Math.floor((start + end) / 2);
let node = Node(array[mid]);
node.left = buildTree(array, start, mid - 1);
node.right = buildTree(array, mid + 1, end);
return node;
};
const insert = value => {
if(!root) return root = Node(value);
current = root;
while(current){
if(value < current){
current = current.left
}else{
current = current.right
}
}
current = Node(value)
// if(!current){
// current = Node(value)
// // }else{
// if(value < current){
// current.left = insert(value, current.left)
// }else{
// current.right = insert(value, current.right)
// }
// }
return root
};
const prettyPrint = (node = root, prefix = '', isLeft = true) => {
if(node){
if (node.right !== null) {
prettyPrint(node.right, `${prefix}${isLeft ? '│ ' : ' '}`, false);
}
console.log(`${prefix}${isLeft ? '└── ' : '┌── '}${node.data}`);
if (node.left !== null) {
prettyPrint(node.left, `${prefix}${isLeft ? ' ' : '│ '}`, true);
}
}else{
console.log(node)
}
}
let root = buildTree(array, 0, array.length - 1);
return {root, prettyPrint, insert}
};
let a = [2,4,5,3,9,7,3,8,5];
let b = [];
let c = [1,2,4,5,6,7]
let f = Tree(a)
let d = Tree(b)
let e = Tree(c)
d.insert(4)
// d.insert(8) ---> doesn't work
// d.prettyPrint()
// f.insert(1) ---> doesn't work
f.prettyPrint()
// e.prettyPrint()
// console.log(d.root)
if I run d.prettyPrint() I'll get └── 4 just as expected. But if I run d.insert(8) after that 8 isn't added to the tree and the code returns └── 4 again. To make matters more confusing if I console.log(d.root) it returns null even though my prettyPrint function returns └── 4 as the root.
Clearly I expect the nodes be added to the tree. On one of my attempts I tried to write the code like this:
const insert = (value, current = root) => {
if(!current){
current = Node(value)
}else{
if(value < current){
current.left = insert(value, current.left)
}else{
current.right = insert(value, current.right)
}
}
return current
};
even though I assigned current = root the code returned null for d.insert(4)
There are these issues in your insert function:
current is implicitly defined as a global variable -- this wouldn't parse in strict mode. It should be declared as a local variable, using let
The value is compared with a node object instead of with the data of that node. So value < current should be changed to value < current.data
The assignment of the new node object to current -- after the loop -- will not mutate the tree. It merely assigns that object to that variable. Such assignment can never change the tree, just like current = current.right does not mutate the tree either. You need instead to assign the new node to either the left or right property of the parent of current. So this assignment should happen one step earlier.
Correction:
const insert = value => {
if(!root) return root = Node(value);
let current = root; // Define with `let`
while(current){
if(value < current.data){ // Compare with the data, not the node
// Mutate the parent node when inserting
if (!current.left) return current.left = Node(value);
current = current.left
}else{
if (!current.right) return current.right = Node(value);
current = current.right
}
}
};
new Set(getObjects())
getObjects returns an iterable object. Every iteration a new object is created. But Set gets and adds all of new objects immediately.
How to make it so only when a new object is needed, a new object is gotten and added?
I tried Proxy but no handler fires when calling a function with receiver as this.
const getEntitySet = (nativeEntityCollection) => {
const cache = new Set;
let src = {
[Symbol.iterator]: ((itr) => {
return () => itr;
})(nativeEntityCollection[Symbol.iterator]()),
ref: nativeEntityCollection
};
let addAll = () => {
for (const entity of src) {
cache.add(entity)
}
done()
};
let generate = function*() {
yield* cache;
for (const entity of src) {
cache.add(entity);
yield entity;
}
done()
};
const done = () => {
src = null;
addAll = null;
generate = Reflect.get(cache, Symbol.iterator).bind(cache)
};
const generateRef = () => generate();
return new Proxy(cache, {
get(set, prop) {
if (prop === Symbol.iterator) {
return generateRef;
}
addAll?.();
return Reflect.get(cache, prop);
}
});
};
The receiver issue you mention can be reproduced when using other methods on the proxy object than Symbol.iterator, like has.
This can be fixed by using bind (as you already did elsewhere):
return Reflect.get(cache, prop).bind(cache);
This will fix that issue.
Other remarks
An access to a method will trigger addAll, which is a pity, as it is not certain that this access will really need that to happen. For instance, if we just did const f = myproxy.has, then there is actually no need to greedily consume the iterable.
With the proxy pattern you would have to return a replacing function for such method access, and only have addAll called within that returned function. I would suggest to use an inheritance pattern instead of the proxy pattern. This seems more suitable for implementing the desired behaviour.
In the case of the has method, we could even consider to partially consume the iterable up to the point that the target element is found, as any further consumption of that iterable could never change the outcome of this method call.
Implementation
Here is an alternative "subclassing" implementation you could consider:
class LazySet extends Set {
#iterator
constructor(iterable) {
super();
this.#iterator = iterable[Symbol.iterator]();
this.#iterator.return = () => ({}); // Disable undesired return behaviour
}
* values() {
yield* super.values();
if (!this.#iterator) return;
let size = super.size;
for (const value of this.#iterator) {
super.add(value); // lazy
if (size === super.size) continue; // duplicate
yield value;
size++;
}
this.#iterator = null; // release reference
}
* entries() {
for (const value of this.values()) yield [value, value];
}
* keys() {
yield* this.values();
}
* [Symbol.iterator]() {
yield* this.values();
}
forEach(cb, thisArg) {
for (const value of this.values()) {
cb.call(thisArg, value, this);
}
}
has(needle) {
if (super.has(needle)) return true;
if (!this.#iterator) return false;
for (const value of this.#iterator) {
super.add(value); // lazy
if (value === needle) return true;
}
this.#iterator = null;
return false;
}
delete(needle) {
for (const _ of this.values()) {}; // consume iterator
return super.delete(needle);
}
get size() {
for (const _ of this.values()) {}; // consume iterator
return super.size;
}
}
// Demo
function* generator() {
for (let i = 0; i < 10; i++) {
console.log(` (about to yield ${i})`);
yield i;
}
}
let set = new LazySet(generator());
console.log("Does set have 3?:");
console.log(set.has(3));
console.log("-----------------");
console.log("first 5 values in set:");
let i = 0;
for (const value of set) {
console.log(value);
if (++i === 5) break;
}
console.log("-----------------");
console.log("All values in set:");
console.log(...set);
console.log("size:", set.size);
I'm working on a problem where given an array of file paths I would like to print the file structure. For example with the given array ["/a/b/c", "a/a/a", "/a/b/d"], the ideal structure would look like :
a
b
c
d
a
a
But my structure ends up looking more like this:
a
b
c
a
a
a
b
From what I can gather this is being caused by my tree not recognizing when a node already exists in a tree. So it is adding the node "a" three times as opposed to recognizing that an "a" already exists and traversing into it.
let paths = ["/a/b/c", "a/a/a", "/a/b/d"]
class TreeNode {
constructor(value) {
this.value = value;
this.children = [];
}
addChild(element) {
this.children.push(element)
}
}
const head = new TreeNode('Head');
let cur = head;
paths.forEach(element => {
cur = head;
let filePath = element.split('/');
filePath.shift();
filePath.forEach(element => {
let newNode = new TreeNode(element);
if(!cur.children.includes(newNode)) {
cur.addChild(newNode);
cur = cur.children[cur.children.length - 1];
} else {
cur = cur.children.indexOf(newNode);
}
})
})
var spaceAppend = function(num) {
let i = 0;
let space = "";
while(i < num) {
space += " ";
i++;
}
return space;
}
var traverse = function(node, level = 0){
if(node === null)
return;
console.log(spaceAppend(level), node.value)
if(node.children) {
for(const n of node.children) {
traverse(n, level + 1);
}
}
}
traverse(head)
Is there an issue with my tree implementation?
Some issues:
.includes() is not the right way to find a matching value. Use .find() instead.
.indexOf() will return an index, so that is not the right value you want to assign to cur in the else block.
shift may throw away an essential part of the path when it does not start with /. You can ease the processing by using .match() instead of .split(), so that you get exactly the non-empty parts of the path.
Less of an issue:
There is no need to define cur outside of the outer loop.
JavaScript has a native function for something like spaceAppend. You can use .repeat().
new TreeNode(element) is also called when you actually don't need it. Only create a new node when you know there is no matching node.
You could replace the inner .forEach() loop with .reduce(), which gives a better scope-handling for the cur variable.
Here is your code with those remarks taken into account:
class TreeNode {
constructor(value) {
this.value = value;
this.children = [];
}
addChild(element) {
this.children.push(element);
}
}
let paths = ["/a/b/c", "a/a/a", "/a/b/d"];
const head = new TreeNode('Head');
paths.forEach(element => {
// Use .match() to only get non-empty elements
let filePath = element.match(/[^\/]+/g);
filePath.reduce((cur, element) => {
// Use .find() instead of .includes()
let node = cur.children.find(child => child.value === element);
// Only create the node when needed:
if (!node) {
node = new TreeNode(element);
cur.addChild(node);
}
// Walk down one step in the tree
return node; // ...becomes the value of `cur`
}, head); // Initial value of reduction
});
const traverse = function(node, level=0) {
if (node === null) return;
// Use .repeat():
console.log(" ".repeat(level), node.value);
if (node.children) {
for (const n of node.children) {
traverse(n, level + 1);
}
}
}
traverse(head);
Is the starter array meant to be ["/a/b/c", "/a/a/a", "/a/b/d"] ("/a/a/a" instead of ("a/a/a")?
I think the crux of the problem you're having is the line
if(!cur.children.includes(newNode)) { ... }
When a new node is created, even if it has the same value as a previous one, it will not result in equity when comparing the two TreeNode objects. You need to compare the value of the nodes, not the nodes themselves.
So an example with a simplified version of your node object:
class TreeNode {
constructor(value) {
this.value = value;
}
}
a1 = new TreeNode('a');
a2 = new TreeNode('a');
console.log("a1 == a2");
console.log(a1 == a2); // false
console.log("a1.value == a2.value");
console.log(a1.value == a2.value); // true
I adjusted the inner forEach loop with one that compares the values instead of the TreeNode objects
filePath.forEach(element => {
let newNode = new TreeNode(element);
let tempNode = null;
for (var i = 0; i < cur.children.length; i++) {
if (cur.children[i].value == newNode.value) {
tempNode = cur.children[i];
}
}
if (tempNode == null) {
cur.addChild(newNode);
cur = newNode;
} else {
cur = tempNode;
}
});
Full code snippet on codepen
Object equality in javascript isn't particularly nice to deal with see this other answer for more information
Here is a solution using lodash and object-treeify. While it's simpler code, there is obviously a trade-off introducing additional dependencies.
This solution works by first converting the paths into a tree structure and then visualizing it using object-treeify
// const lodash = require('lodash');
// const objectTreeify = require('object-treeify');
const myPaths = ['/a/b/c', 'a/a/a', '/a/b/d'];
const treeify = (paths) => objectTreeify(paths.reduce((p, c) => {
lodash.set(p, c.match(/[^/]+/g));
return p;
}, {}), {
spacerNoNeighbour: ' ',
spacerNeighbour: ' ',
keyNoNeighbour: '',
keyNeighbour: ''
});
console.log(treeify(myPaths));
/* =>
a
b
c
d
a
a
*/
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="https://bundle.run/lodash#4.17.20"></script>
<script src="https://bundle.run/object-treeify#1.1.31"></script>
Disclaimer: I'm the author of object-treeify
I'm writing a BST in JS and trying to concatenate a string passed into a member function without success. console.log() works but it automatically starts a new line for each entry.
"use strict";
class Node {
constructor(dt) {
this.data = dt;
this.left = null;
this.right = null;
}
}
class BST {
constructor() {
this.root = null;
}
insert(parent, key) {
if (!this.root) return this.root = new Node(key);
else {
if (key < parent.data) {
if (!parent.left) {
parent.left = new Node(key);
} else {
this.insert(parent.left, key);
}
} else if (key > parent.data) {
if (!parent.right) {
parent.right = new Node(key);
} else {
this.insert(parent.right, key);
}
}
}
}
// Doesn't work
printIN(parent, string) {
if (parent) {
this.printIN(parent.left);
console.log(parent.data);
string += " " + parent.data;
// return string += " " + parent.data;
this.printIN(parent.right);
}
return string;
}
// This works.
// printIN(parent) {
// if (parent) {
// this.printIN(parent.left);
// console.log(parent.data);
// this.printIN(parent.right);
// }
// }
}
let treeA = new BST();
let tree = null;
tree = treeA.insert(treeA.root, 5);
tree = treeA.insert(treeA.root, 7);
tree = treeA.insert(treeA.root, 3);
tree = treeA.insert(treeA.root, 14);
let string = [""];
string = treeA.printIN(treeA.root, string);
console.log();
console.log(string);
// treeA.printIN(treeA.root);
I want to print out the numbers on one single line, instead of them starting on a new line each time. I thought using string concatenation is the logical solution, but I can't seem to make it work.
// Doesn't work
printIN(parent, string) {
if (parent) {
this.printIN(parent.left);
console.log(parent.data);
string += " " + parent.data;
// return string += " " + parent.data;
this.printIN(parent.right);
}
return string;
Another answer gave you a solution to your string problem. The snippet below has an alternative. But more importantly, it offers some significant clean-up to your OOP approach.
There are two main reasons this is necessary.
First, you have member functions of your object, which still require the object to be passed to them. That's redundant and confusing. Any OOP function which doesn't make reference to this should be a static function. But if you're going to do OOP (I might argue against it in fact), then you shouldn't use static functions as though they were methods. Thus insert and printIN should not have the parent parameter.
Second, you are dealing with a recursive structure. Nodes of a tree should have the same design as the tree itself. This makes all sorts of things easier. But you have a class Node and one called BST. And BST then has a root node, which should then be recursive. There is simply no reason for this that I can see. A BST by nature is a root node, with a data value (usually, although we can make that optional as you do) and potentially left and right nodes that are themselves BSTs.
Fixing the code to work like this simplifies it substantially. Here is one implementation:
class BST {
constructor(data) {
this.data = data;
}
insert(key) {
if (!this.data) {
this.data = key;
} else {
if (key < this.data) {
if (!this.left) {
this.left = new BST(key);
} else {
this.left.insert(key);
}
} else if (key > this.data) {
if (!this.right) {
this.right = new BST(key);
} else {
this.right.insert(key);
}
}
}
}
printIN() {
return (this.left ? this.left.printIN() + ' ' : '') +
this.data +
(this.right ? ' ' + this.right.printIN() : '');
}
}
let treeA = new BST();
treeA.insert(5);
treeA.insert(7);
treeA.insert(3);
treeA.insert(14);
console.log(treeA.printIN());
There is no Node class here. Everything is done in BST. Moreover, BST objects don't need a root node and the methods are simpler.
I would note, though that printIN is less useful than another function which we could use the same way:
toArray() {
return [
...(this.left ? this.left.toArray() : []),
this.data,
...(this.right ? this.right.toArray() : [])
];
}
which would allow
console.log(treeA.toArray()); //=> [3, 5, 7, 14]
and you could get your string just by joining that array with a space. This function strikes me as much more useful than the printIN one you have.
We could also add a static function:
static fromArray(xs) {
return xs .reduce (function (tree, x) {
tree.insert(x)
return tree
}, new BST())
}
that would allow us to write
tree = BST .fromArray ([8, 6, 7, 5, 3, 0, 9]);
tree .printIN() //=> [0, 3, 5, 6, 7, 8, 9]
I mentioned that I wouldn't even do this with OOP. But I don't do much that way these days, preferring functional approaches. So, if you're curious, here's another take on the problem altogether, using just functions, and trees that aren't modified when you do an insert, but instead return new trees:
const bst = (xs) =>
xs .reduce ((tree, x) => bstInsert (x, tree), {})
const bstInsert = (x, {left, data, right} = {}) =>
data == undefined
? {data: x}
: x < data
? {data, left: bstInsert (x, left), ...(right ? {right} : {})}
: {data, ...(left ? {left} : {}), right: bstInsert (x, right)}
const inorder = ({left, data, right} = {}) => [
...(left ? inorder(left) : []),
data,
...(right ? inorder(right) : [])
]
inorder (bst ([8, 6, 7, 5, 3, 0, 9])) //=> [0, 3, 5, 6, 7, 8, 9]
I'm not recommending that technique, specifically, but FP can be a powerful alternative to OOP.
Try this technique.
printIN(parent, result = []) {
if (parent) {
this.printIN(parent.left, result);
result.push(parent.data)
this.printIN(parent.right, result);
}
return result;
}
Pass, as a second argument, the array of strings that will serve as the resulting list of node values as per the in-order bst traversal.
Use a default value for this argument of [] then, if omitted, one will be created, but when recursing, you can pass the existing array in. When you visit a node, push the value into the array. Call printIN on the root node and do not provide the result argument (a new array will be created and returned).
If you want to log all on one line, use Array.prototype.join to transform the array of strings into a single comma-separated string, and log that.
Working example:
class Node {
constructor(dt) {
this.data = dt;
this.left = null;
this.right = null;
}
}
class BST {
constructor() {
this.root = null;
}
insert(parent, key) {
if (!this.root) return this.root = new Node(key);
else {
if (key < parent.data) {
if (!parent.left) {
parent.left = new Node(key);
} else {
this.insert(parent.left, key);
}
} else if (key > parent.data) {
if (!parent.right) {
parent.right = new Node(key);
} else {
this.insert(parent.right, key);
}
}
}
}
printIN(parent, result = []) {
if (parent) {
this.printIN(parent.left, result);
result.push(parent.data)
this.printIN(parent.right, result);
}
return result;
}
}
let treeA = new BST();
let tree = null;
tree = treeA.insert(treeA.root, 5);
tree = treeA.insert(treeA.root, 7);
tree = treeA.insert(treeA.root, 3);
tree = treeA.insert(treeA.root, 14);
console.log(treeA.printIN(treeA.root).join());
Do you know a JavaScript library that implements a generic Iterator class for collections (be it Arrays or some abstract Enumerable) with a full set of features, like the Google Common or the Apache Commons?
Edit: Enumerable#each is not an Iterator class. I'm looking for an Iterator, something that would let us write something like:
var iterator = new Iterator(myCollection);
for (var element = iterator.next(); iterator.hasNext(); element = iterator.next()) {
// iterator
}
Edit : mamoo reminded us of the Iterator implementation in Mozilla's Javascript 1.7. So the goal now is to find an implementation of this Iterator function in Javascript 1.5 (ECMA 4).
Edit2 : Why using an iterator when libraries (and ECMA 5) provide a each method? First, because each usually messes with this because the callback is call -ed (that's why each accepts a second argument in Prototype). Then, because people are much more familiar with the for(;;) construct than with the .each(callback) construct (at least, in my field). Lastly, because an iterator can iterate over plain objects (see JavaScript 1.7).
Edit3 : I accepted npup's anwser, but here is my shot at it :
function Iterator(o, keysOnly) {
if (!(this instanceof arguments.callee))
return new arguments.callee(o, keysOnly);
var index = 0, keys = [];
if (!o || typeof o != "object") return;
if ('splice' in o && 'join' in o) {
while(keys.length < o.length) keys.push(keys.length);
} else {
for (p in o) if (o.hasOwnProperty(p)) keys.push(p);
}
this.next = function next() {
if (index < keys.length) {
var key = keys[index++];
return keysOnly ? key : [key, o[key]];
} else throw { name: "StopIteration" };
};
this.hasNext = function hasNext() {
return index < keys.length;
};
}
var lang = { name: 'JavaScript', birthYear: 1995 };
var it = Iterator(lang);
while (it.hasNext()) {
alert(it.next());
}
//alert(it.next()); // A StopIteration exception is thrown
var langs = ['JavaScript', 'Python', 'C++'];
var it = Iterator(langs);
while (it.hasNext()) {
alert(it.next());
}
//alert(it.next()); // A StopIteration exception is thrown
Ok, the enumerable pattern is not a real iterator then.
Is this (below) useful for you? It conforms to the sematics you gave at least. As usual there are tradeoffs to be made here and there, and I didn't think very hard when deciding this time :).
And maybe you would like to be able to send in a number or two and iterate over a range in that way. But this could maybe be a start (there's support for iterating over hashes, arrays and strings).
It's a whole demo page which runs itself and does some debug output, but the (possibly) interesting stuff is in the
window.npup = (function() {
[...]
})();
spot.
Maybe it is just me who doesn't get it at all, but what would you use such a java-like Iterator for in a real situation?
Best
/npup
<html>
<head>
<title>untitled</title>
</head>
<body>
<ul id="output"></ul>
<script type="text/javascript">
window.log = (function (outputAreaId) {
var myConsole = document.getElementById(outputAreaId);
function createElem(color) {
var elem = document.createElement('li');
elem.style.color = color;
return elem;
}
function appendElem(elem) {
myConsole.appendChild(elem);
}
function debug(msg) {
var elem = createElem('#888');
elem.innerHTML = msg;
appendElem(elem);
}
function error(msg) {
var elem = createElem('#f88');
elem.innerHTML = msg;
appendElem(elem);
}
return {
debug: debug
, error: error
};
})('output');
window.npup = (function () {
// Array check as proposed by Mr. Crockford
function isArray(candidate) {
return candidate &&
typeof candidate==='object' &&
typeof candidate.length === 'number' &&
typeof candidate.splice === 'function' &&
!(candidate.propertyIsEnumerable('length'));
}
function dontIterate(collection) {
// put some checks chere for stuff that isn't iterable (yet)
return (!collection || typeof collection==='number' || typeof collection==='boolean');
}
function Iterator(collection) {
if (typeof collection==='string') {collection = collection.split('');}
if (dontIterate(collection)) {throw new Error('Oh you nasty man, I won\'t iterate over that ('+collection+')!');}
var arr = isArray(collection);
var idx = 0, top=0;
var keys = [], prop;
if (arr) {top = collection.length;}
else {for (prop in collection) {keys.push(prop);}}
this.next = function () {
if (!this.hasNext()) {throw new Error('Oh you nasty man. I have no more elements.');}
var elem = arr ? collection[idx] : {key:keys[idx], value:collection[keys[idx]]};
++idx;
return elem;
};
this.hasNext = function () {return arr ? idx<=top : idx<=keys.length;};
}
return {Iterator: Iterator};
})();
var element;
log.debug('--- Hash demo');
var o = {foo:1, bar:2, baz:3, bork:4, hepp: {a:1,b:2,c:3}, bluff:666, bluff2:777};
var iterator = new npup.Iterator(o);
for (element = iterator.next(); iterator.hasNext(); element = iterator.next()) {
log.debug('got elem from hash: '+element.key+' => '+element.value);
if (typeof element.value==='object') {
var i2 = new npup.Iterator(element.value);
for (var e2=i2.next(); i2.hasNext(); e2=i2.next()) {
log.debug(' # from inner hash: '+e2.key+' => '+e2.value);
}
}
}
log.debug('--- Array demo');
var a = [1,2,3,42,666,777];
iterator = new npup.Iterator(a);
for (element = iterator.next(); iterator.hasNext(); element = iterator.next()) {
log.debug('got elem from array: '+ element);
}
log.debug('--- String demo');
var s = 'First the pants, THEN the shoes!';
iterator = new npup.Iterator(s);
for (element = iterator.next(); iterator.hasNext(); element = iterator.next()) {
log.debug('got elem from string: '+ element);
}
log.debug('--- Emptiness demo');
try {
log.debug('Try to get next..');
var boogie = iterator.next();
}
catch(e) {
log.error('OW: '+e);
}
log.debug('--- Non iterables demo');
try{iterator = new npup.Iterator(true);} catch(e) {log.error('iterate over boolean: '+e);}
try{iterator = new npup.Iterator(6);} catch(e) {log.error('iterate over number: '+e);}
try{iterator = new npup.Iterator(null);} catch(e) {log.error('iterate over null: '+e);}
try{iterator = new npup.Iterator();} catch(e) {log.error('iterate over undefined: '+e);}
</script>
</body>
</html>
JQuery has the each() method:
http://api.jquery.com/jQuery.each/
but probably there's something similar even in other libraries such as Moo or Dojo.
Javascript 1.7 implements the Iterator function:
https://developer.mozilla.org/en/Core_JavaScript_1.5_Guide/Iterators_and_Generators
This is my attempt (jsfiddle) for ECMAScript 262 5th edition (aka Javascript). (Uses for example Object.keys and Array.isArray)
//Usage
b=Iterator(a);
while(b()){
console.log(b.value);
}
The code:
function Iterator(input,keys) {
// Input:
// input : object|array
// keys : array|undefined|boolean
function my() {
++my.index;
if (my.index >= my.keys.length) {
my.index = my.keys.length -1;
my.key = my.value = undefined;
return false;
}
my.key = my.useIndex ? my.index : my.keys[my.index];
my.value = my.input[my.key];
return my.index < my.keys.length;
}
if (input === null || typeof input !== 'object') {
throw new TypeError("'input' should be object|array");
}
if (
!Array.isArray(keys)
&& (typeof keys !== 'undefined')
&& (typeof keys !== 'boolean')
) {
throw new TypeError("'keys' should be array|boolean|undefined");
}
// Save a reference to the input object.
my.input = input;
if (Array.isArray(input)) {
//If the input is an array, set 'useIndex' to true if
//the internal index should be used as a key.
my.useIndex = !keys;
//Either create and use a list of own properties,
// or use the supplied keys
// or at last resort use the input (since useIndex is true in that
// case it is only used for the length)
my.keys = keys===true ? Object.keys(input) : keys || input;
} else {
my.useIndex = false;
my.keys = Array.isArray(keys) ? keys : Object.keys(input);
}
// Set index to before the first element.
my.index = -1;
return my;
}
Examples:
function Person(firstname, lastname, domain) {
this.firstname = firstname;
this.lastname = lastname;
this.domain = domain;
}
Person.prototype.type = 'Brillant';
var list = [
new Person('Paula','Bean','some.domain.name'),
new Person('John','Doe','another.domain.name'),
new Person('Johanna','Doe','yet.another.domain.name'),
];
var a,b;
var data_array = ['A','B','C','D','E','F'];
data_array[10]="Sparse";
console.log('Iterate over own keys in an object, unknown order');
a = Iterator(list[0]);
while(a()) console.log(" ",a.key, a.value);
console.log('Iterate over keys from anywhere, in specified order');
a = Iterator(list[0], ['lastname','firstname','type']);
while(a()) console.log(" ",a.key, a.value);
console.log('Iterate over all values in an array');
a = Iterator(list);
while(a()) console.log(a.key, a.value.firstname, a.value.lastname);
//Some abusing, that works for arrays (if the iterator.keys is modified
//it can also be used for objects)
console.log('Add more entries to the array, reusing the iterator...');
list.push(new Person('Another','Name','m.nu'));
while(a()) console.log(a.key, a.value.firstname, a.value.lastname);
console.log('Reset index and print everything again...');
a.index=-1; //Reset the index.
while(a()) console.log(a.key, a.value.firstname, a.value.lastname);
//With arrays, if setting 'keys' to true it will only print the
//elements that has values (If the array has more own enumerable values
//they too will be included)
console.log('Print sparce arrays...');
a = Iterator(data_array,true);
while(a()) console.log(a.key, a.value);
In the time since this question was asked JavaScript has added actual Iterators. Some built-in types, such as Array, Map, and String now have a default iteration behavior, but you can add your own to any object by including a next() function which returns one of two objects:
{done:true} /*or*/
{done:false, value:SOMEVALUE}
One way to access an object Iterator is with the:
for ( var of object ) { }
loop. Here is a (reasonably silly) example where we define an Iterator and then use it in such a loop to produce a string 1, 2, 3:
"use strict";
function count ( i ) {
let n = 0;
let I = {};
I[Symbol.iterator] = function() {
return { next: function() { return (n > i) ? {done:true}
: {done:false, value:n++} } } };
let s = "";
let c = "";
for ( let i of I ) { /* use the iterator we defined above */
s += c + i;
c = ", "
}
return s;
}
let s = count(3);
console.log(s);
Ive used LINQ to Javascript in a few projects.
http://jslinq.codeplex.com/Wikipage
var myList = [
{FirstName:"Chris",LastName:"Pearson"},
{FirstName:"Kate",LastName:"Johnson"},
{FirstName:"Josh",LastName:"Sutherland"},
{FirstName:"John",LastName:"Ronald"},
{FirstName:"Steve",LastName:"Pinkerton"}
];
var exampleArray = JSLINQ(myList)
.Where(function(item){ return item.FirstName == "Chris"; })
.OrderBy(function(item) { return item.FirstName; })
.Select(function(item){ return item.FirstName; });
I'm still a learner of js.class.
Though being close to Ruby, helps me.
http://jsclass.jcoglan.com/enumerable.html
MarkT
Since this hasn't been mention yet arrays have higher-order functions built in.
Map works like iterator that can only do a single pass.
[1,2,3,4,5].map( function(input){ console.log(input); } );
This code passes each element in the list into a function, in this case its a simple printer.
1
2
3
4
5