Create tree from js array - javascript

I'm trying to create tree from a list . My data is like ;
A-->B-->C-->D-->E..
A-->B-->C-->D-->F..
A-->F-->C-->D-->E..
.
.
.
I have all possible data path in array.I want to show this like
A
-B
-C
-D
-E
-F
-F
-C
-D
-E
How can i create and check with javascript ? I need createTreeFunction :)
function parseData(data)
{
$.each(data, function (i, p) {
var arr = p.split("--->");
createTreeFunction(p);
});
};
function parseData(data)
{
$.each(data, function (i, p) {
var arr = p.split("--->");
createTreeFunction(p);
});
};

Basically you can use at least two different structures for the children for building a tree, one with an array (Solution A) or with an object (Solution B).
The advantage of an array over an object is the direct iteration over the children. With an object, you need to get the keys first and then is an iteration possible.
Otherwise if you know one child, then the access over the key is faster. This is also true for inserting new nodes to the tree.
With a children array you need a function getChild for testing if a child is present.
A note, the supplied data does not keep the identifier unique.
Solution A with arrays for children:
function Node(id) {
this.id = id;
this.children = []; // array
}
Node.prototype.getChild = function (id) {
var node;
this.children.some(function (n) {
if (n.id === id) {
node = n;
return true;
}
});
return node;
};
var path = ['A-->B-->C-->D-->E', 'A-->B-->C-->D-->F', 'A-->F-->C-->D-->E'],
tree = new Node('root');
path.forEach(function (a) {
var parts = a.split('-->');
parts.reduce(function (r, b) {
var node = r.getChild(b);
if (!node) {
node = new Node(b);
r.children.push(node);
}
return node;
}, tree);
});
document.getElementById('out').innerHTML = JSON.stringify(tree, 0, 4);
<pre id="out"></pre>
Solution B with objects for children:
function Node(id) {
this.id = id;
this.children = {}; // object
}
var path = ['A-->B-->C-->D-->E', 'A-->B-->C-->D-->F', 'A-->F-->C-->D-->E'],
tree = new Node('root');
path.forEach(function (a) {
var parts = a.split('-->');
parts.reduce(function (r, b) {
if (!r.children[b]) {
r.children[b] = new Node(b);
}
return r.children[b];
}, tree);
});
document.getElementById('out').innerHTML = JSON.stringify(tree, 0, 4);
<pre id="out"></pre>
Both proposals uses Array#forEach and Array#reduce for iterating the given strings and for returning the reference to the actual id. If an id is not found, a new instance of node is taken and added to the array or object. The reference is returned for the next id check.

For this use cases i had invented very useful two Object methods; namely Object.prototype.getNestedValue() and Object.prototype.setNestedValue(). By utilizing these methods this job is nothing more than a single liner JS code. See below...
Object.prototype.getNestedValue = function(...a) {
return a.length > 1 ? (this[a[0]] !== void 0 && this[a[0]].getNestedValue(...a.slice(1))) : this[a[0]];
};
Object.prototype.setNestedValue = function(...a) {
return a.length > 2 ? typeof this[a[0]] === "object" && this[a[0]] !== null ? this[a[0]].setNestedValue(...a.slice(1))
: (this[a[0]] = typeof a[1] === "string" ? {} : new Array(a[1]),
this[a[0]].setNestedValue(...a.slice(1)))
: this[a[0]] = a[1];
};
var data = "A-->B-->C-->D-->E\nA-->B-->C-->D-->F\nA-->F-->C-->D-->E",
datarr = data.split("\n").map(e => e.split("-->")), // get your list in an array
o = {};
datarr.forEach(a => !o.getNestedValue(...a) && o.setNestedValue(...a,null));
console.log(JSON.stringify(o,null,2));

Related

Js how to set an item at a nested index in an empty array [duplicate]

I hope someone can help me with this Javascript.
I have an Object called "Settings" and I would like to write a function that adds new settings to that object.
The new setting's name and value are provided as strings. The string giving the setting's name is then split by the underscores into an array. The new setting should get added to the existing "Settings" object by creating new nested objects with the names given by each part of the array, except the last part which should be a string giving the setting's value. I should then be able to refer to the setting and e.g. alert its value. I can do this in a static way like this...
var Settings = {};
var newSettingName = "Modules_Video_Plugin";
var newSettingValue = "JWPlayer";
var newSettingNameArray = newSettingName.split("_");
Settings[newSettingNameArray[0]] = {};
Settings[newSettingNameArray[0]][newSettingNameArray[1]] = {};
Settings[newSettingNameArray[0]][newSettingNameArray[1]][newSettingNameArray[2]] = newSettingValue;
alert(Settings.Modules.Mediaplayers.Video.Plugin);
... the part that creates the nested objects is doing this ...
Settings["Modules"] = {};
Settings["Modules"]["Video"] = {};
Settings["Modules"]["Video"]["Plugin"] = "JWPlayer";
However, as the number of parts that make up the setting name can vary, e.g. a newSettingName could be "Modules_Floorplan_Image_Src", I'd like to do this dynamically using a function such as...
createSetting (newSettingNameArray, newSettingValue);
function createSetting(setting, value) {
// code to create new setting goes here
}
Can anyone help me work out how to do this dynamically?
I presume there has to be a for...loop in there to itterate through the array, but I haven't been able to work out a way to create the nested objects.
If you've got this far thanks very much for taking the time to read even if you can't help.
Put in a function, short and fast (no recursion).
var createNestedObject = function( base, names ) {
for( var i = 0; i < names.length; i++ ) {
base = base[ names[i] ] = base[ names[i] ] || {};
}
};
// Usage:
createNestedObject( window, ["shapes", "triangle", "points"] );
// Now window.shapes.triangle.points is an empty object, ready to be used.
It skips already existing parts of the hierarchy. Useful if you are not sure whether the hierarchy was already created.
Or:
A fancier version where you can directly assign the value to the last object in the hierarchy, and you can chain function calls because it returns the last object.
// Function: createNestedObject( base, names[, value] )
// base: the object on which to create the hierarchy
// names: an array of strings contaning the names of the objects
// value (optional): if given, will be the last object in the hierarchy
// Returns: the last object in the hierarchy
var createNestedObject = function( base, names, value ) {
// If a value is given, remove the last name and keep it for later:
var lastName = arguments.length === 3 ? names.pop() : false;
// Walk the hierarchy, creating new objects where needed.
// If the lastName was removed, then the last object is not set yet:
for( var i = 0; i < names.length; i++ ) {
base = base[ names[i] ] = base[ names[i] ] || {};
}
// If a value was given, set it to the last name:
if( lastName ) base = base[ lastName ] = value;
// Return the last object in the hierarchy:
return base;
};
// Usages:
createNestedObject( window, ["shapes", "circle"] );
// Now window.shapes.circle is an empty object, ready to be used.
var obj = {}; // Works with any object other that window too
createNestedObject( obj, ["shapes", "rectangle", "width"], 300 );
// Now we have: obj.shapes.rectangle.width === 300
createNestedObject( obj, "shapes.rectangle.height".split('.'), 400 );
// Now we have: obj.shapes.rectangle.height === 400
Note: if your hierarchy needs to be built from values other that standard objects (ie. not {}), see also TimDog's answer below.
Edit: uses regular loops instead of for...in loops. It's safer in cases where a library modifies the Array prototype.
function assign(obj, keyPath, value) {
lastKeyIndex = keyPath.length-1;
for (var i = 0; i < lastKeyIndex; ++ i) {
key = keyPath[i];
if (!(key in obj)){
obj[key] = {}
}
obj = obj[key];
}
obj[keyPath[lastKeyIndex]] = value;
}
Usage:
var settings = {};
assign(settings, ['Modules', 'Video', 'Plugin'], 'JWPlayer');
My ES2015 solution. Keeps existing values.
const set = (obj, path, val) => {
const keys = path.split('.');
const lastKey = keys.pop();
const lastObj = keys.reduce((obj, key) =>
obj[key] = obj[key] || {},
obj);
lastObj[lastKey] = val;
};
Example:
const obj = {'a': {'prop': {'that': 'exists'}}};
set(obj, 'a.very.deep.prop', 'value');
console.log(JSON.stringify(obj));
// {"a":{"prop":{"that":"exists"},"very":{"deep":{"prop":"value"}}}}
Using ES6 is shorten. Set your path into an array.
first, you have to reverse the array, to start filling the object.
let obj = ['a','b','c'] // {a:{b:{c:{}}}
obj.reverse();
const nestedObject = obj.reduce((prev, current) => (
{[current]:{...prev}}
), {});
Another recursive solution:
var nest = function(obj, keys, v) {
if (keys.length === 1) {
obj[keys[0]] = v;
} else {
var key = keys.shift();
obj[key] = nest(typeof obj[key] === 'undefined' ? {} : obj[key], keys, v);
}
return obj;
};
Example usage:
var dog = {bark: {sound: 'bark!'}};
nest(dog, ['bark', 'loudness'], 66);
nest(dog, ['woff', 'sound'], 'woff!');
console.log(dog); // {bark: {loudness: 66, sound: "bark!"}, woff: {sound: "woff!"}}
I love this ES6 immutable way to set certain value on nested field:
const setValueToField = (fields, value) => {
const reducer = (acc, item, index, arr) => ({ [item]: index + 1 < arr.length ? acc : value });
return fields.reduceRight(reducer, {});
};
And then use it with creating your target object.
const targetObject = setValueToField(['one', 'two', 'three'], 'nice');
console.log(targetObject); // Output: { one: { two: { three: 'nice' } } }
Lodash has a _.set method to achieve this
let obj = {}
_.set(obj, ['a', 'b', 'c', 'd'], 'e')
or
_.set(obj, 'a.b.c.d', 'e')
// which generate the following object
{
"a": {
"b": {
"c": {
"d": "e"
}
}
}
}
Here is a simple tweak to jlgrall's answer that allows setting distinct values on each element in the nested hierarchy:
var createNestedObject = function( base, names, values ) {
for( var i in names ) base = base[ names[i] ] = base[ names[i] ] || (values[i] || {});
};
Hope it helps.
Here is a functional solution to dynamically create nested objects.
const nest = (path, obj) => {
const reversedPath = path.split('.').reverse();
const iter = ([head, ...tail], obj) => {
if (!head) {
return obj;
}
const newObj = {[head]: {...obj}};
return iter(tail, newObj);
}
return iter(reversedPath, obj);
}
Example:
const data = {prop: 'someData'};
const path = 'a.deep.path';
const result = nest(path, data);
console.log(JSON.stringify(result));
// {"a":{"deep":{"path":{"prop":"someData"}}}}
Inspired by ImmutableJS setIn method which will never mutate the original. This works with mixed array and object nested values.
function setIn(obj = {}, [prop, ...rest], value) {
const newObj = Array.isArray(obj) ? [...obj] : {...obj};
newObj[prop] = rest.length ? setIn(obj[prop], rest, value) : value;
return newObj;
}
var obj = {
a: {
b: {
c: [
{d: 5}
]
}
}
};
const newObj = setIn(obj, ["a", "b", "c", 0, "x"], "new");
//obj === {a: {b: {c: [{d: 5}]}}}
//newObj === {a: {b: {c: [{d: 5, x: "new"}]}}}
Appreciate that this question is mega old! But after coming across a need to do something like this in node, I made a module and published it to npm.
Nestob
var nestob = require('nestob');
//Create a new nestable object - instead of the standard js object ({})
var newNested = new nestob.Nestable();
//Set nested object properties without having to create the objects first!
newNested.setNested('biscuits.oblong.marmaduke', 'cheese');
newNested.setNested(['orange', 'tartan', 'pipedream'], { poppers: 'astray', numbers: [123,456,789]});
console.log(newNested, newNested.orange.tartan.pipedream);
//{ biscuits: { oblong: { marmaduke: 'cheese' } },
orange: { tartan: { pipedream: [Object] } } } { poppers: 'astray', numbers: [ 123, 456, 789 ] }
//Get nested object properties without having to worry about whether the objects exist
//Pass in a default value to be returned if desired
console.log(newNested.getNested('generic.yoghurt.asguard', 'autodrome'));
//autodrome
//You can also pass in an array containing the object keys
console.log(newNested.getNested(['chosp', 'umbridge', 'dollar'], 'symbols'));
//symbols
//You can also use nestob to modify objects not created using nestob
var normalObj = {};
nestob.setNested(normalObj, 'running.out.of', 'words');
console.log(normalObj);
//{ running: { out: { of: 'words' } } }
console.log(nestob.getNested(normalObj, 'random.things', 'indigo'));
//indigo
console.log(nestob.getNested(normalObj, 'improbable.apricots'));
//false
Inside your loop you can use lodash.set and will create the path for you:
...
const set = require('lodash.set');
const p = {};
const [type, lang, name] = f.split('.');
set(p, [lang, type, name], '');
console.log(p);
// { lang: { 'type': { 'name': '' }}}
try using recursive function:
function createSetting(setting, value, index) {
if (typeof index !== 'number') {
index = 0;
}
if (index+1 == setting.length ) {
settings[setting[index]] = value;
}
else {
settings[setting[index]] = {};
createSetting(setting, value, ++index);
}
}
I think, this is shorter:
Settings = {};
newSettingName = "Modules_Floorplan_Image_Src";
newSettingValue = "JWPlayer";
newSettingNameArray = newSettingName.split("_");
a = Settings;
for (var i = 0 in newSettingNameArray) {
var x = newSettingNameArray[i];
a[x] = i == newSettingNameArray.length-1 ? newSettingValue : {};
a = a[x];
}
I found #jlgrall's answer was great but after simplifying it, it didn't work in Chrome. Here's my fixed should anyone want a lite version:
var callback = 'fn.item1.item2.callbackfunction',
cb = callback.split('.'),
baseObj = window;
function createNestedObject(base, items){
$.each(items, function(i, v){
base = base[v] = (base[v] || {});
});
}
callbackFunction = createNestedObject(baseObj, cb);
console.log(callbackFunction);
I hope this is useful and relevant. Sorry, I've just smashed this example out...
You can define your own Object methods; also I'm using underscore for brevity:
var _ = require('underscore');
// a fast get method for object, by specifying an address with depth
Object.prototype.pick = function(addr) {
if (!_.isArray(addr)) return this[addr]; // if isn't array, just get normally
var tmpo = this;
while (i = addr.shift())
tmpo = tmpo[i];
return tmpo;
};
// a fast set method for object, put value at obj[addr]
Object.prototype.put = function(addr, val) {
if (!_.isArray(addr)) this[addr] = val; // if isn't array, just set normally
this.pick(_.initial(addr))[_.last(addr)] = val;
};
Sample usage:
var obj = {
'foo': {
'bar': 0 }}
obj.pick('foo'); // returns { bar: 0 }
obj.pick(['foo','bar']); // returns 0
obj.put(['foo', 'bar'], -1) // obj becomes {'foo': {'bar': -1}}
A snippet for those who need to create a nested objects with support of array keys to set a value to the end of path. Path is the string like: modal.product.action.review.2.write.survey.data. Based on jlgrall version.
var updateStateQuery = function(state, path, value) {
var names = path.split('.');
for (var i = 0, len = names.length; i < len; i++) {
if (i == (len - 1)) {
state = state[names[i]] = state[names[i]] || value;
}
else if (parseInt(names[i+1]) >= 0) {
state = state[names[i]] = state[names[i]] || [];
}
else {
state = state[names[i]] = state[names[i]] || {};
}
}
};
Set Nested Data:
function setNestedData(root, path, value) {
var paths = path.split('.');
var last_index = paths.length - 1;
paths.forEach(function(key, index) {
if (!(key in root)) root[key] = {};
if (index==last_index) root[key] = value;
root = root[key];
});
return root;
}
var obj = {'existing': 'value'};
setNestedData(obj, 'animal.fish.pet', 'derp');
setNestedData(obj, 'animal.cat.pet', 'musubi');
console.log(JSON.stringify(obj));
// {"existing":"value","animal":{"fish":{"pet":"derp"},"cat":{"pet":"musubi"}}}
Get Nested Data:
function getNestedData(obj, path) {
var index = function(obj, i) { return obj && obj[i]; };
return path.split('.').reduce(index, obj);
}
getNestedData(obj, 'animal.cat.pet')
// "musubi"
getNestedData(obj, 'animal.dog.pet')
// undefined
Try this: https://github.com/silkyland/object-to-formdata
var obj2fd = require('obj2fd/es5').default
var fd = obj2fd({
a:1,
b:[
{c: 3},
{d: 4}
]
})
Result :
fd = [
a => 1,
b => [
c => 3,
d => 4
]
]
Here is a decomposition to several useful functions, that each preserve existing data. Does not handle arrays.
setDeep: Answers question. Non-destructive to other data in the object.
setDefaultDeep: Same, but only sets if not already set.
setDefault: Sets a key if not already set. Same as Python's setdefault.
setStructure: Helper function that builds the path.
// Create a nested structure of objects along path within obj. Only overwrites the final value.
let setDeep = (obj, path, value) =>
setStructure(obj, path.slice(0, -1))[path[path.length - 1]] = value
// Create a nested structure of objects along path within obj. Does not overwrite any value.
let setDefaultDeep = (obj, path, value) =>
setDefault(setStructure(obj, path.slice(0, -1)), path[path.length - 1], value)
// Set obj[key] to value if key is not in object, and return obj[key]
let setDefault = (obj, key, value) =>
obj[key] = key in obj ? obj[key] : value;
// Create a nested structure of objects along path within obj. Does not overwrite any value.
let setStructure = (obj, path) =>
path.reduce((obj, segment) => setDefault(obj, segment, {}), obj);
// EXAMPLES
let temp = {};
// returns the set value, similar to assignment
console.log('temp.a.b.c.d:',
setDeep(temp, ['a', 'b', 'c', 'd'], 'one'))
// not destructive to 'one'
setDeep(temp, ['a', 'b', 'z'], 'two')
// does not overwrite, returns previously set value
console.log('temp.a.b.z: ',
setDefaultDeep(temp, ['a', 'b', 'z'], 'unused'))
// creates new, returns current value
console.log('temp["a.1"]: ',
setDefault(temp, 'a.1', 'three'))
// can also be used as a getter
console.log("temp.x.y.z: ",
setStructure(temp, ['x', 'y', 'z']))
console.log("final object:", temp)
I'm not sure why anyone would want string paths:
They are ambiguous for keys with periods
You have to build the strings in the first place
Since I started with something from this page, I wanted to contribute back
Other examples overwrote the final node even if it was set, and that wasn't what I wanted.
Also, if returnObj is set to true, it returns the base object. By default, falsy, it returns the deepest node.
function param(obj, path, value, returnObj) {
if (typeof path == 'string') path = path.split(".");
var child = obj;
path.forEach((key, i) => {
if (!(key in child)) {
child[key] = (i < path.length-1) ? {} : value || {};
}
child = child[key];
});
return returnObj ? obj : child;
}
var x = {};
var xOut = param(x, "y.z", "setting")
console.log(xOut);
xOut = param(x, "y.z", "overwrite") // won't set
console.log(xOut);
xOut = param(x, "y.a", "setting2")
console.log(xOut);
xOut = param(x, "y.a", "setting2", true) // get object rather than deepest node.
console.log(xOut);
You can also do something where numeric keys are placed in arrays (if they don't already exist). Note that numeric keys won't convert to arrays for the first element of the path, since that's set by the type of your base-object.
function isNumber(n) {
return !isNaN(parseFloat(n)) && isFinite(n);
}
function param(obj, path, value, returnObj) {
if (typeof path == 'string') path = path.split(".");
var child = obj;
path.forEach((key, i) => {
var nextKey = path[i+1];
if (!(key in child)) {
child[key] = (nextKey == undefined && value != undefined
? value
: isNumber(nextKey)
? []
: {});
}
child = child[key];
});
return returnObj ? obj : child;
}
var x = {};
var xOut = param(x, "y.z", "setting")
console.log(xOut);
xOut = param(x, "y.z", "overwrite") // won't set
console.log(xOut);
xOut = param(x, "y.a", "setting2")
console.log(xOut);
xOut = param(x, "y.a", "setting2", true) // get object rather than deepest node.
xOut = param(x, "1.0.2.a", "setting")
xOut = param(x, "1.0.1.a", "try to override") // won't set
xOut = param(x, "1.0.5.a", "new-setting", true) // get object rather than deepest node.
console.log(xOut);
Naturally, when the numeric keys are greater than 0, you might see some undefined gaps.
Practical uses of this might be
function AddNote(book, page, line) {
// assume a global global notes collection
var myNotes = param(allNotes, [book, page, line], []);
myNotes.push('This was a great twist!')
return myNotes;
}
var allNotes = {}
var youthfulHopes = AddNote('A Game of Thrones', 4, 2, "I'm already hooked, at least I won't have to wait long for the books to come out!");
console.log(allNotes)
// {"A Game of Thrones": [undefined, undefined, undefined, undefined, [undefined, undefined, ["I'm already hooked, at least I won't have to wait long for the books to come out!"]]]}
console.log(youthfulHopes)
// ["I'm already hooked, at least I won't have to wait long for the books to come out!"]
function initPath(obj, path) {
path.split('.').reduce((o, key) => (
Object.assign(o, {[key]: Object(o[key])}),
o[key]
), obj);
return obj;
}
Usage
const obj = { a: { b: 'value1' } };
initPath(obj, 'a.c.d').a.c.d='value2';
/*
{
"a": {
"b": "value1",
"c": {
"d": "value2"
}
}
}
*/
simple answer. on es6, im using this
const assign = (obj, path, value) => {
let keyPath = path.split('.')
let lastKeyIndex = keyPath.length - 1
for (let i = 0; i < lastKeyIndex; ++i) {
let key = keyPath[i]
if (!(key in obj)) {
obj[key] = {}
}
obj = obj[key]
}
obj[keyPath[lastKeyIndex]] = value
}
example json
const obj = {
b: 'hello'
}
you can add new key
assign(obj, 'c.d.e', 'this value')
and you get like bellow
console.log(obj)
//response example
obj = {
b: 'hello',
c: {
d: {
e: 'this value'
}
}
}
function createObj(keys, value) {
let obj = {}
let schema = obj
keys = keys.split('.')
for (let i = 0; i < keys.length - 1; i++) {
schema[keys[i]] = {}
schema = schema[keys[i]]
}
schema[keys.pop()] = value
return obj
}
let keys = 'value1.value2.value3'
let value = 'Hello'
let obj = createObj(keys, value)
Eval is probably overkill but the result is simple to visualize, with no nested loops or recursion.
function buildDir(obj, path){
var paths = path.split('_');
var final = paths.pop();
for (let i = 1; i <= paths.length; i++) {
var key = "obj['" + paths.slice(0, i).join("']['") + "']"
console.log(key)
eval(`${key} = {}`)
}
eval(`${key} = '${final}'`)
return obj
}
var newSettingName = "Modules_Video_Plugin_JWPlayer";
var Settings = buildDir( {}, newSettingName );
Basically you are progressively writing a string "obj['one']= {}", "obj['one']['two']"= {} and evaling it;

Print a hierarchical tree structure in javascript

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

Create Nested Object which contains N' nested objectsfrom .txt file Javascript

After lots of tries and searching I decide to ask because I am stuck.I have a txt file like this:
CITYS
CITYS.AREAS
CITYS.AREAS.STREETS
CITYS.AREAS.STREETS.HOUSES
CITYS.AREAS.STREETS.HOUSES.ROOMS
CITYS.AREAS.STREETS.HOUSES.ROOMS.KITCHEN
CITYS.AREAS.STREETS.HOUSES.ROOMS.LIVINGROOMS
CITYS.AREAS.STREETS.HOUSES.ROOMS.LIVINGROOMS.TV
CITYS.AREAS.STREETS.HOUSES.ROOMS.LIVINGROOMS.TABLE
CITYS.AREAS.STREETS.HOUSES.ROOMS.LIVINGROOMS.TABLE.VASE
CITYS.AREAS.STREETS.HOUSES.ROOMS.LIVINGROOMS.TABLE.ASTREY
CITYS.AREAS.STREETS.HOUSES.ROOMS.BATHROOMS
CITYS.AREAS.STREETS.HOUSES.ROOMS.BATHROOMS.BATHTUBE
CITYS.AREAS.STREETS.HOUSES.ROOMS.BATHROOMS.BATHTUBE.SHAMPOO
CITYS.AREAS.STREETS.HOUSES.ROOMS.BATHROOMS.BATHTUBE.CONTITIONER
CITYS.AREAS.STREETS.HOUSES.GARDEN
CITYS.AREAS.STREETS.HOUSES.GARDEN.POOL
CITYS.AREAS.STREETS.HOUSES.GARDEN.POOL.WATER
CITYS.AREAS.STREETS.HOUSES.GARDEN.TREE.....
CITYS.AREAS.STREETS.CARS
CITYS.AREAS.STREETS.CARS.BRAND...
CITYS.AREAS.STREETS.CARS.BRAND.LOGO.....
CITYS.AREAS.STREETS.CARS.COLOR..
CITYS.AREAS.STREETS.CARS.TYPE..
And I want to convert it into a nested object like this
CITYS:{
AREAS:{
STREETS:{
HOUSES:{
ROOMS: {
LIVINGROOMS: {TV,TABLE:{VASE,ASTREY}},
BATHROOMS: {BATHTUBE:{SHAMPOO,CONTITIONER},MIRROR},
...
},
GARDEN:{
......
},
},
CARS:{
BRAND:{LOGO},
COLOR:{},
TYPE:{},
......
},
},
}
}
I am tring to do somthing like that (IN ARRAY)
for(var line = 0; line < lines.length; line++){
var n = lines[line];
var ninpieces = n.split(".");
var name=ninpieces[ninpieces.length-1];
var nametostore=ninpieces[ninpieces.length-2] ;
CreateObject(name,nametostore);
};
CreateObject=function(name,nametostore){
this.a= name;
this.b= nametostore;
newpar=this['b'];
newchild=this['a'];
this[newchild]=new Array();
if (typeof this[newpar] != "object") {
this[newpar]=new Array();
}
this[newchild].push(name);
this[newpar].push(this[newchild])
stractureobj.push(this[newpar])
}
Is a combination of things that I found here in stackoverflow but it's not working.
You can use the following code. This "algorithm" temporarily stores also properties by their fully dotted names, as synonyms for the corresponding nested objects. This way it can quickly retrieve where to inject the next line's object.
Note that the algorithm performs fastest if the input is sorted. This you can do with lines.sort() if necessary.
function addNestedObject(obj, lines) {
var map = { '': obj }; // Set starting point for empty path
function addLine(line) {
var name = line.split(".").pop();
var path = line.substr(0, line.length-name.length-1);
if (!map[path]) addLine(path); // recurse to create parent
if (!map[line]) map[line] = map[path][name] = {}; // set name & line synonym
}
// Process each line with above private function.
for (var line of lines.slice().sort()) addLine(line);
return obj; // Might be useful to have as return value as well
};
// Sample input
var lines = [
'CITYS.AREAS',
'CITYS.AREAS.STREETS',
'CITYS.AREAS.STREETS.HOUSES.ROOMS.LIVINGROOMS.TV',
'CITYS.AREAS.STREETS.HOUSES.ROOMS.LIVINGROOMS.TABLE',
'CITYS.AREAS.STREETS.HOUSES.ROOMS.LIVINGROOMS.TABLE.VASE',
'CITYS.AREAS.STREETS.HOUSES.ROOMS.LIVINGROOMS.TABLE.ASTREY',
'CITYS.AREAS.STREETS.HOUSES.ROOMS.BATHROOMS',
'CITYS.AREAS.STREETS.HOUSES.ROOMS.BATHROOMS.BATHTUBE',
'CITYS.AREAS.STREETS.HOUSES.ROOMS.BATHROOMS.BATHTUBE.SHAMPOO',
'CITYS.AREAS.STREETS.HOUSES.ROOMS.BATHROOMS.BATHTUBE.CONTITIONER',
'CITYS.AREAS.STREETS.HOUSES.GARDEN',
'CITYS.AREAS.STREETS.HOUSES.GARDEN.POOL',
'CITYS.AREAS.STREETS.HOUSES.GARDEN.POOL.WATER',
'CITYS',
];
var stractureobj = { 'otherProperty': 42 };
// Convert lines to nested object and add to stractureobj
addNestedObject(stractureobj, lines);
// Output in snippet
document.querySelector('pre').textContent=JSON.stringify(stractureobj, null, 4);
<pre></pre>
The above uses an object stractureobj, with already its own properties, to which the nested structure must be added.
If you are only interested to have an object with just the nested structure, and nothing else, you could call it with the empty object and assign the return value:
var stractureobj = addNestedObject({}, lines);
Which comes down to the same as this:
var stractureobj = {};
addNestedObject(stractureobj, lines);
You can use String.prototype.split() with RegExp /\n/ as parameter to split text file at new line characters, Array.prototype.filter() with parameter Boolean to remove empty items from array; set stractureobj to an empty object; use single for loop, Array.prototype.reduce() to set properties of stractureobj
for (var line = 0
, stractureobj = {}
, lines = textFileContents.split(/\n/).filter(Boolean)
; line < lines.length
; line++) {
var n = lines[line];
if (line === 0) {
stractureobj[lines[line]] = {}
} else {
var ninpieces = n.split(/\./).filter(Boolean);
ninpieces.reduce(function(obj, prop, index) {
var curr = ninpieces[index + 1];
if (!obj[prop] && !!curr) {
obj[prop] = {
[curr]: {}
};
} else {
if (obj[prop] && curr
&& !obj[prop][curr]) {
obj[prop][curr] = {}
}
}
return obj[prop]
}, stractureobj)
}
};
for (var line = 0
, stractureobj = {}
, lines = document.querySelector("pre")
.textContent.split(/\n/).filter(Boolean)
; line < lines.length
; line++) {
var n = lines[line];
if (line === 0) {
stractureobj[lines[line]] = {}
} else {
var ninpieces = n.split(/\./).filter(Boolean);
ninpieces.reduce(function(obj, prop, index) {
var curr = ninpieces[index + 1];
if (!obj[prop] && !!curr) {
obj[prop] = {
[curr]: {}
};
} else {
if (obj[prop] && curr && !obj[prop][curr]) {
obj[prop][curr] = {}
}
}
return obj[prop]
}, stractureobj)
}
};
document.querySelectorAll("pre")[1].textContent = JSON.stringify(stractureobj, null, 2)
pre:nth-of-type(1) {
display: none;
}
<pre>CITYS
CITYS.AREAS
CITYS.AREAS.STREETS
CITYS.AREAS.STREETS.HOUSES
CITYS.AREAS.STREETS.HOUSES.ROOMS
CITYS.AREAS.STREETS.HOUSES.ROOMS.KITCHEN
CITYS.AREAS.STREETS.HOUSES.ROOMS.LIVINGROOMS
CITYS.AREAS.STREETS.HOUSES.ROOMS.LIVINGROOMS.TV
CITYS.AREAS.STREETS.HOUSES.ROOMS.LIVINGROOMS.TABLE
CITYS.AREAS.STREETS.HOUSES.ROOMS.LIVINGROOMS.TABLE.VASE
CITYS.AREAS.STREETS.HOUSES.ROOMS.LIVINGROOMS.TABLE.ASTREY
CITYS.AREAS.STREETS.HOUSES.ROOMS.BATHROOMS
CITYS.AREAS.STREETS.HOUSES.ROOMS.BATHROOMS.BATHTUBE
CITYS.AREAS.STREETS.HOUSES.ROOMS.BATHROOMS.BATHTUBE.SHAMPOO
CITYS.AREAS.STREETS.HOUSES.ROOMS.BATHROOMS.BATHTUBE.CONTITIONER
CITYS.AREAS.STREETS.HOUSES.GARDEN
CITYS.AREAS.STREETS.HOUSES.GARDEN.POOL
CITYS.AREAS.STREETS.HOUSES.GARDEN.POOL.WATER
CITYS.AREAS.STREETS.HOUSES.GARDEN.TREE
CITYS.AREAS.STREETS.CARS
CITYS.AREAS.STREETS.CARS.BRAND
CITYS.AREAS.STREETS.CARS.BRAND.LOGO
CITYS.AREAS.STREETS.CARS.COLOR
CITYS.AREAS.STREETS.CARS.TYPE
</pre>
<pre></pre>
I guess in JS it's essential to have "dynamic" access to nested values both to get or set them. I think this is a missing functionality. So i decided to develop two reusable Object methods. They are Object.prototype.getNestedValue() and Object.prototype.setNestedValue() They are very handy tools for these use cases and just turn your job nothing more than a very simple task. OK let's get into them to see what they are.
setNestedValue() takes a number of arguments. All arguments except the last one are used as object properties if it's a "string" type or array index if it's a "number" type. The last argument is the value of the last object property or array index at the very last in line. Accordingly.
var o = {};
o.setNestedValue("a",3,"b","value");
or
var o = {};
o.setNestedValue(...["a",3,"b"],"value");
are typical use cases. Lets see a simple example.
Object.prototype.setNestedValue = function(...a) {
a.length > 2 ? typeof this[a[0]] === "object" && this[a[0]] !== null ? this[a[0]].setNestedValue(...a.slice(1))
: (this[a[0]] = typeof a[1] === "string" ? {} : new Array(a[1]),
this[a[0]].setNestedValue(...a.slice(1)))
: this[a[0]] = a[1];
return this;
};
var o = {};
o.setNestedValue("a",3,"x","value");
o.setNestedValue("a",2,"y","value");
o.setNestedValue("a",1,"z","value");
o.setNestedValue("a",0,"w","value");
console.log(JSON.stringify(o,null,2));
OK now it's the time for your solution;
Object.prototype.getNestedValue = function(...a) {
return a.length > 1 ? (this[a[0]] !== void 0 && this[a[0]].getNestedValue(...a.slice(1))) : this[a[0]];
};
Object.prototype.setNestedValue = function(...a) {
a.length > 2 ? typeof this[a[0]] === "object" && this[a[0]] !== null ? this[a[0]].setNestedValue(...a.slice(1))
: (this[a[0]] = typeof a[1] === "string" ? {} : new Array(a[1]),
this[a[0]].setNestedValue(...a.slice(1)))
: this[a[0]] = a[1];
return this;
};
var data = "CITYS\nCITYS.AREAS\nCITYS.AREAS.STREETS\nCITYS.AREAS.STREETS.HOUSES\nCITYS.AREAS.STREETS.HOUSES.ROOMS\nCITYS.AREAS.STREETS.HOUSES.ROOMS.KITCHEN\nCITYS.AREAS.STREETS.HOUSES.ROOMS.LIVINGROOMS\nCITYS.AREAS.STREETS.HOUSES.ROOMS.LIVINGROOMS.TV\nCITYS.AREAS.STREETS.HOUSES.ROOMS.LIVINGROOMS.TABLE\nCITYS.AREAS.STREETS.HOUSES.ROOMS.LIVINGROOMS.TABLE.VASE\nCITYS.AREAS.STREETS.HOUSES.ROOMS.LIVINGROOMS.TABLE.ASTREY\nCITYS.AREAS.STREETS.HOUSES.ROOMS.BATHROOMS\nCITYS.AREAS.STREETS.HOUSES.ROOMS.BATHROOMS.BATHTUBE\nCITYS.AREAS.STREETS.HOUSES.ROOMS.BATHROOMS.BATHTUBE.SHAMPOO\nCITYS.AREAS.STREETS.HOUSES.ROOMS.BATHROOMS.BATHTUBE.CONTITIONER\nCITYS.AREAS.STREETS.HOUSES.GARDEN\nCITYS.AREAS.STREETS.HOUSES.GARDEN.POOL\nCITYS.AREAS.STREETS.HOUSES.GARDEN.POOL.WATER\nCITYS.AREAS.STREETS.HOUSES.GARDEN.TREE\nCITYS.AREAS.STREETS.CARS\nCITYS.AREAS.STREETS.CARS.BRAND\nCITYS.AREAS.STREETS.CARS.BRAND.LOGO\nCITYS.AREAS.STREETS.CARS.COLOR\nCITYS.AREAS.STREETS.CARS.TYPE",
datarr = data.split("\n").map(e => e.split(".")), // get your list in an array
o = {};
datarr.forEach(a => o.setNestedValue(...a,""));
console.log(JSON.stringify(o,null,2));
Allright.. that's it... It's so simple.

JS reference multi-layered dynamic object string

Suppose I have a string that references a deep Javascript object, such as:
var string = 'response.basicInfo.gender';
I want to build a function that safely checks if that object exists, by splitting the string by .s and building the object, checking each level as it goes, and then dealing with the value of the object, if it actually exists.
var parseHelper = function(response, items) {
for (item in items) {
var parts = String(item).split('.');
for (var i = 0; i < parts.length; ++i) {
// ... etc. build the string
}
}
}
parseHelper(response, {
'basicInfo.gender': function(val){
return (val == 'M') ? 'Male' : (val == 'F') ? 'Female' : val;
},
})
While the above function is incomplete, let's suppose we use it to build the string and check if each exists:
// so let's suppose we build the string:
var builtString = "response['basicInfo']";
// Now we want to check if it exists
if (response['basicInfo']) {
// And if we are fine that it exists, go on to the next item
var builtString = "response['basicInfo']['gender']";
// etc.
}
I don't have a problem building that function, I just don't know how to evaluate a string like "response['basicInfo']['gender']" and turn it into an actual reference to the object. My only guess would be eval(), but eval is evil...
Update
I know you can reference a global object by going window['blah'], but this response object I want to reference is not in the global scope, so do I use this? And even if I can do this, how do I reference it with multiple layers?
Plus 1 to Bergi, who linked to a page with six links, one of which had an answer I adapted to solve the problem:
Convert JavaScript string in dot notation into an object reference
Here's the full solution.
// We want to run a parse function to convert
// the object response.basicInfo.gender (which is 'M')
// to 'Male', etc.
// Sets the value of a string representing a deep object.
setDeep: function(root, path, value) {
var parts = path.split('.'), obj = root;
for (var i = 0; i < parts.length - 1; ++i) {
obj = obj[parts[i]] || { };
}
obj[parts[parts.length - 1]] = value;
return obj;
},
// Gets the value of a string representing a deep object.
getDeep: function(root, path) {
var parts = path.split('.'), obj = root, target;
for (var i = 0; i < parts.length; ++i) {
target = obj[parts[i]];
if (typeof target == "undefined") return void 0;
obj = target;
}
return obj;
},
// Loops through multiple string representations
// of deep objects and runs the values through
// assigned parsing functions for each of them,
// returning the root object.
parseHelper: function(obj, items) {
for (item in items) {
var val = getDeep(obj, item);
var func = items[item];
if (val !== undefined) {
val = func(val);
}
setDeep(obj, item, val);
}
return obj;
},
// response.basicInfo.gender = 'M';
// response.foo.bar = true;
response = parseHelper(response, {
'basicInfo.gender': function(val){
return (val == 'M') ? 'Male' : (val == 'F') ? 'Female' : val;
},
'foo.bar': function(val) {
return (val) ? false : true;
},
});
// response.basicInfo.gender = 'Male';
// response.foo.bar = false;

JavaScript Iterator Class

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

Categories