views:

549

answers:

6

Do you know a js 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 librairies (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  
+1  A: 

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

mamoo
Thanks for the js 1.7 link. I had forgot about that. I'd love to see an implementation of this in ECMA4.
Alsciende
A: 

Prototype's Enumerable mix-in provides generic enumeration features, though not iterators per se. Prototype mixes Enumerable into Array for you, and provides several other enumerables as well.

Rather than iterators it takes the functional approach of your providing a function to be called on each iteration (like ECMAScript 5th edition's forEach does) e.g.:

["one", "two", "three"].each(function(item) {
    alert(item);
});

...alerts "one", then "two", then "three". Enumerable also provides quite a few other features for common use cases, like any (do any elements match a predicate), all (do all elements match the predicate), etc.

T.J. Crowder
`each` is not an _Iterator class_. See my edited question.
Alsciende
A: 

Most popular libraries do. here are links to 2 apis:

prototype, jQuery

npup
`each` is not an _Iterator class_. See my edited question.
Alsciende
A: 

You should avoid iterators because of the Nested Hashtable structure beneath the javascript. You never know how it is implemented and can become a big and invisible performance leak. (specialy on IE)

fmsf
Please elaborate, or provide a link for further reading.
Alsciende
I remember this from a google tech talk. I think it was either "The good parts of javascript" or another one about javascript performance.
fmsf
Too bad, because I have no idea what you are talking about :)
Alsciende
http://www.youtube.com/watch?v=hQVTIJBZook
fmsf
A: 

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; });
Dested
LINQ looks great, but how is it relevant to Iterators? It looks like it's made for querying datasets.
Alsciende
+1  A: 

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('&nbsp;&nbsp;&nbsp;&nbsp;# 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>
npup
Great job. I like how you iterated over strings, why not? For a justification of the question, I'm going to edit my question.
Alsciende