Array.prototype.map = function(fn) {
    var res = new Array(this.length);
    switch (fn.length) {
        case 1:
            for (var i = 0; i < this.length; i++)
                if (this[i] != undefined)
                    res[i] = fn.call(this, this[i]);
            return res;
        case 2:
            for (var i = 0; i < this.length; i++)
                if (this[i] != undefined)
                    res[i] = fn.call(this, i, this[i]);
            return res;
        default:
            throw new Error("Invalid function.");
    }
}

Array.prototype.foreach = function(fn) {
    switch (fn.length) {
        case 1:
            for (var i = 0; i < this.length; i++)
                if (this[i] != undefined)
                    fn(this[i]);
            break;
        case 2:
            for (var i = 0; i < this.length; i++)
                if (this[i] != undefined)
                    fn(i, this[i]);
            break;
        default:
            throw new Error("Invalid function.");
    }
}

Array.prototype.filter = function(fn) {
    var res = new Array();
    switch (fn.length) {
        case 1:
            for (var i = 0; i < this.length; i++)
                if (this[i] != undefined)
                    if (fn(this[i]))
                        res.push(this[i]);
            return res;
        case 2:
            for (var i = 0; i < this.length; i++)
                if (this[i] != undefined)
                    if (fn(i, this[i]))
                        res.push(this[i]);
            return res;
        default:
            throw new Error("Invalid function.");
    }
}

Array.prototype.accumulate = function(start, fn) {
    var res = start;
    for (var i = 0; i < this.length; i++)
        if (this[i] != undefined)
            res = fn.length == 2 ? fn(res, this[i]) : fn(res, i, this[i]);
    return res;
}

Array.fromObject = function(obj) {
    if (obj instanceof Array)
        return obj;
    var res = new Array(obj.length);
    for (var i = 0; i < obj.length; i++)
        res[i] = obj[i];
    return res;
}

Object.prototype.extend = function(obj) {
    for (var i in obj)
        this[i] = obj[i];
    return this;
}

var $uid = function() {
    return ($uid.cnt++).toString() + "-" + Math.random() + "-" + (new Date()).getTime();
}

$uid.cnt = 0;

Array.prototype.group = function(fn) {
    var res = [];
    if (fn.length == 2) {
        this.foreach(function(i, el) {
            var group = fn(i, el);
            if ((typeof group) == 'number' || (typeof group) == 'string') {
                if (res[group] == undefined)
                    res[group] = [el];
                else
                    res[group].push(el);
            }
        });
    } else {
        this.foreach(function(el) {
            var group = fn(el);
            if ((typeof group) == 'number' || (typeof group) == 'string') {
                if (res[group] == undefined)
                    res[group] = [el];
                else
                    res[group].push(el);
            }
        });
    }
    return res;
}

Array.prototype.indexOf = function(p) {
    if (typeof p == 'function') {
        if (p.length == 1) {
            for (var i = 0; i < this.length; i++) {
                if (this[i] != undefined && p(this[i]))
                    return i;
            }
        } else {
            for (var i = 0; i < this.length; i++) {
                if (this[i] != undefined && p(i, this[i]))
                    return i;
            }
        }
    } else {
        for (var i = 0; i < this.length; i++)
            if (this[i] == p)
                return i;
    }
    return null;
}

Array.prototype.removeEmpty = function() {
    return this.filter(function(v) {
        return true;
    });
}

Array.prototype.randomElement = function() {
    return this[Math.floor(Math.random() * this.length)];
}

Array.prototype.shuffle = function() {
    var res = Array.fromObject(this);
    function swap(x1, x2) {
        var tmp = res[x1];
        res[x1] = res[x2]
        res[x2] = tmp;
    }
    for (var i = 0; i < res.length; i++)
        swap(i, Math.floor(Math.random() * res.length));
    return res;
}

var $cache = function(fn) {
    return (function() {
        var previous_result = null;
        var previous_args = null;
        return function() {
            var current_args = Array.fromObject(arguments);
            if (previous_args != null
                    && previous_args.length == current_args.length
                    && previous_args.accumulate(true, function(res, i, v) {
                return res && current_args[i] == v;
            }))
                return previous_result;
            else {
                previous_args = current_args;
                return previous_result = fn.apply(this, current_args);
            }
        };
    })();
}

var $range = function(start, end) {
    var res = [];
    var inc = start > end ? -1 : 1;
    if (arguments.length == 3)
        inc = arguments[2];
    for (var i = start; i <= end; i += inc)
        res.push(i);
    return res;
}

