public class PersonList : List<Person> {
public string Name;
public PersonList(string name){
this.Name = name;
}
public int GetSumYears() {
var total = 0;
foreach (var p in this)
total += p.BirthDate.Year;
return total;
}
}
Among other thing a bug in IE8. I personally focus on ECMAScript 5, so this is not my concern for this post, I also want to use ECMAScript 5, property get.A blog post by Juriy Zaytsev, How ECMAScript 5 still does not allow to subclass an array will tell you every thing about it.
I also like to mention a post from Andrea Giammarchi Habemus Array ... unlocked length in IE8, subclassed Array for every browser, which proposes a solution to implement a Stack object which I re-used to implement my List() object.
This post is my implementation of the C# List<T> in JavaScript. First here is the kind of code I want to be able to write.
The source code is now part of my library fJs.lib on github.
var l = new List();
for(var i=0; i<5; i++){
l.add(i);
}
l.addRange(5, 6, 7, 8, 9);
print(l.toString()); // 0,1,2,3,4,5,6,7,8,9
l.removeAt(0);
l.remove(9);
print(l.toString()); // 1,2,3,4,5,6,7,8
var l2 = l.filter(function(v){ return v % 2 == 0; });
print(l2.toString()); // 2,4,6,8
var l3 = l.map(function(v){ return v*v; });
print(l3.toString()); // 1,4,9,16,25,36,49,64,81,81
print(l instanceof List); // true
print(l instanceof Array); // true
My first idea was to start with something like that
function List() {
var
_list = [];
_list.add = function(v){
this.push(v);
}
return _list;
}
var l = new List();
print(l instanceof Array); // true
print(l instanceof List); // false
But the object returned by the List() constructor is not an instance of List, which is confusing. So I re-used the Stack implementation of Andrea Giammarchi which solve this problem. Creating the object is a little bit more complex, but then adding the methods remain simple.
Here is the List object properties and methods.
List
properties:
count
method:
add
addRange
all
any
concat
contains
exists
findAll
findIndex
remove
removeAll
removeAt
reverse
filter
map
The source code
Here is the source code with some unit tests. I ran the code on NodeJS.
//
// Class List a match for the C# .NET List<T> - Frederic Torres 2011
// Mit Style License
//
// based on
// - Andrea Giammarchi's Stack
// http://webreflection.blogspot.com/2008/05/habemus-array-unlocked-length-in-ie8.html
// - How ECMAScript 5 still does not allow to subclass an array
// http://perfectionkills.com/how-ecmascript-5-still-does-not-allow-to-subclass-an-array/
//
var List = (function(){
var
MAX_SIGNED_INT_VALUE = Math.pow(2, 32) - 1;
function __isFunction(f) {
return typeof f === 'function';
}
function __toUint32(value) {
return value >>> 0;
}
function __isInt(v){
return String(__toUint32(v)) === v;
}
function __removeAt(that, index) {
that.splice(index ,1);
}
function _list(length) {
if (arguments.length === 1 && typeof length === "number") {
this.length = -1 < length && length === length << 1 >> 1 ? length : this.push(length);
}
else if (arguments.length) {
this.push.apply(this, arguments);
}
Object.defineProperty(this, "count", {
get: function(){ return this.length; },
});
}
function _array() { };
_array.prototype = [];
_list.prototype = new _array();
_list.prototype.length = 0;
_list.prototype.toString = function () {
return this.slice(0).toString();
}
_list.prototype.add = function (v) {
this.push(v);
}
_list.prototype.addRange = function () {
var i;
for(i=0; i < arguments.length; i++)
this.add(arguments[i]);
};
_list.prototype.clear = function (v) {
this.length = 0;
}
_list.prototype.removeAt = function (index) {
if(this.count==0)
throw new Error("Cannot removeAt from empty List");
if(index>=0 && index < this.count)
__removeAt(this, index);
else
throw new Error("invalid index "+index+" for List");
}
_list.prototype.remove = function (val) {
var
elementRemoved = 0,
index = this.indexOf(val);
if(index===-1)
return 0;
while(index!==-1){
__removeAt(this, index);
elementRemoved++;
index = this.indexOf(val);
}
return elementRemoved;
}
_list.prototype.contains = function (val) {
return this.indexOf(val) !== -1;
}
_list.prototype.concat = function (l) {
var
i;
for(i in this)
if(__isInt(i))
this.add(l[i]);
}
_list.prototype.findIndex = function (lambda) {
var
i,
r = new List();
if(!__isFunction(lambda))
throw new Error("exists() requires a function as parameter");
for(i in this)
if((__isInt(i))&&(lambda(this[i])))
r.add(i);
return r;
}
_list.prototype.exists = function (lambda) {
var
i,
r = new List();
if(!__isFunction(lambda))
throw new Error("exists() requires a function as parameter");
for(i in this)
if((__isInt(i)) && (lambda(this[i])))
return true;
return false;
}
_list.prototype.removeAll = function (lambda) {
var
goOn = true,
i;
if(!__isFunction(lambda))
throw new Error("exists() requires a function as parameter");
while(goOn){
goOn = false;
for(i in this){
if((__isInt(i)) && (lambda(this[i]))) {
__removeAt(this, i);
goOn = true;
break;
}
}
}
}
_list.prototype.all = function (lambda) {
var
i,
r = new List();
if(!__isFunction(lambda))
throw new Error("exists() requires a function as parameter");
for(i in this)
if((__isInt(i)) && (!lambda(this[i])))
return false;
return true;
}
_list.prototype.any = function (lambda) {
var
i,
r = new List();
if(!__isFunction(lambda))
throw new Error("exists() requires a function as parameter");
for(i in this)
if((__isInt(i)) && (lambda(this[i])))
return true;
return false;
}
_list.prototype.reverse = function () {
var
values = [],
i;
for(i in this)
if(__isInt(i))
values.unshift(this[i]);
this.clear();
this.push.apply(this, values);
}
_list.prototype.filter = function (lambda) {
var
i,
r = new List();
if(!__isFunction(lambda))
throw new Error("filter() requires a function as parameter");
for(i in this)
if(__isInt(i))
if(lambda(this[i]))
r.add(this[i]);
return r;
}
_list.prototype.findAll = function (lambda) {
// Just to have the same .net method
return this.filter(lambda);
}
_list.prototype.map = function (lambda) {
var
i,
r = new List();
if(!__isFunction(lambda))
throw new Error("map() requires a function as parameter");
for(i in this)
if(__isInt(i))
r.add(lambda(this[i]));
return r;
}
_list.prototype.constructor = _list;
return _list;
})();
The unit tests.
For now it is the unit tests of the poor until I include this in my own JavaScript library and probably add it on github.
function isNodeJs() {
return (typeof require === "function" && typeof Buffer === "function" && typeof Buffer.byteLength === "function" && typeof Buffer.prototype !== "undefined" && typeof Buffer.prototype.write === "function");
}
function print(s){
console.log(s);
}
if(isNodeJs()){
var Assert = {
isTrue : function(e){ if(!e) throw new Error("IsTrue failed"); },
isFalse : function(e){ if(!!e) throw new Error("IsTrue failed"); },
areEqual : function(v1,v2){ if(v1!==v2) throw new Error("expected:"+v1+" is not equal to actual:"+v2); }
}
var l = new List(1, 2, 3);
Assert.areEqual(3, l.count);
Assert.areEqual("1,2,3", l.toString());
var l = new List();
Assert.areEqual(0, l.count);
l.add(1);
l.add(2);
l.add(3);
Assert.areEqual("1,2,3", l.toString());
Assert.areEqual(3, l.count);
var l = new List(1, 2, 3);
l.removeAt(1);
Assert.areEqual(2, l.count);
Assert.areEqual("1,3", l.toString());
var l = new List(1, 2, 3);
Assert.areEqual(1,l.remove(2));
Assert.areEqual(2, l.count);
Assert.areEqual("1,3", l.toString());
var l = new List(1, 2, 3, 1, 1);
Assert.areEqual(3,l.remove(1));
Assert.areEqual(2, l.count);
Assert.areEqual("2,3", l.toString());
var l1 = new List(1, 2, 3);
var l2 = new List(4, 5, 6);
l1.concat(l2);
print(l1.toString());
Assert.areEqual("1,2,3,4,5,6", l1.toString());
var l = new List(1, 2, 3);
Assert.isTrue(l instanceof List);
Assert.isTrue(l instanceof Array);
var l = new List(1, 2, 3, 4);
var ll = l.filter(function(v){ return v % 2 == 0; });
Assert.areEqual("2,4", ll.toString());
var l = new List(1, 2, 3, 4);
var ll = l.map(function(v){ return v*v; });
Assert.areEqual("1,4,9,16", ll.toString());
var l = new List();
l.addRange(1, 2, 3);
Assert.areEqual("1,2,3", l.toString());
var l = new List(1, 2, 3);
l.clear();
Assert.areEqual(0, l.count);
Assert.areEqual("", l.toString());
l.add(1);
Assert.areEqual("1", l.toString());
Assert.areEqual(1, l.count);
l.addRange(2, 3, 4);
Assert.areEqual("1,2,3,4", l.toString());
var l = new List(1, 2, 3);
Assert.isTrue(l.contains(2));
Assert.isFalse(l.contains(12));
var l = new List(1, 2, 3);
Assert.isTrue( l.exists(function(v){ return v == 2; }));
Assert.isFalse(l.exists(function(v){ return v == 12; }));
var l = new List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
l.name='fred';
Assert.isTrue ( l.all(function(v){ return v > 0; }));
Assert.isFalse( l.all(function(v){ return v > 5; }));
var l = new List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
l.name='fred';
Assert.isTrue ( l.any(function(v){ return v > 0; }));
Assert.isFalse( l.any(function(v){ return v > 15; }));
var l = new List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
Assert.areEqual( "1,3,5,7,9", l.findIndex(function(v){ return v % 2 == 0; }).toString() );
var l = new List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
l.removeAll(function(v){ return v % 2 == 0; });
Assert.areEqual( "1,3,5,7,9", l.toString() );
var l = new List(1, 2, 3, 4, 5);
l.reverse();
Assert.areEqual( "5,4,3,2,1", l.toString() );
print("---------------------");
var l = new List();
for(var i=0; i < 5; i++){
l.add(i);
}
l.addRange(5, 6, 7, 8, 9);
print(l.toString()); // 0,1,2,3,4,5,6,7,8,9
l.removeAt(0);
l.remove(9);
print(l.toString()); // 1,2,3,4,5,6,7,8
var l2 = l.filter(function(v){ return v % 2 == 0; });
print(l2.toString()); // 2,4,6,8
var l3 = l.map(function(v){ return v*v; });
print(l3.toString()); // 1,4,9,16,25,36,49,64,81,81
print(l instanceof List); // true
print(l instanceof Array); // true
}
No comments:
Post a Comment