Click here to Skip to main content
15,885,216 members
Articles / Programming Languages / Javascript
Tip/Trick

LINQ for JavaScript

Rate me:
Please Sign up or sign in to vote.
4.77/5 (4 votes)
7 Aug 2014CPOL 16.3K   8   3
Adding LINQ goodness to Javascript?

Introduction

Wouldn't it be good to be able to use the power of C#/LINQ in your JavaScript app? Here it is, for TypeScript users.

Background

I want to filter, sort, reshape array without all those ungainly loop! So I wrote this strongly typed TypeScript utility.

Using the code

The Enumerable class is the one with where(), select(), and other statements. Array has a new method .enumerable() which return an enumerable.

Let's demonstrate with an example. Say you have the following JavaScript data

JavaScript
var a = [
  { selected: true, name: "Lilac" },
  { selected: true, name: "Red" },
  { selected: false, name: "Strumpet" }
];

and want an array with all name where selected is true, one can write

JavaScript
var b = a.enumerable().where(x => x.selected).select(x => x.name).array();

And now, without further ado, is the code:

JavaScript
interface Array<T> {
    enumerable(): Enumerable<T>; 
    enumerator(): Enumerator<T>; 
}
Array.prototype.enumerable = function<T>() { return Enumerable.From<T>(this); }
Array.prototype.enumerator = function() { return Enumerable.From(this).enumerator(); }

interface IEnumerable<T> {
    enumerator(): IEnumerator<T>;
}
interface IEnumerator<T> {
    current(): T;
    moveNext(): boolean;
    reset(): void;
}
class Enumerator<T> {
    private e: IEnumerator<T>;
    constructor(e: IEnumerator<T>) {
        this.e = e;
    }
    current() { return this.e.current(); }
    moveNext() { return this.e.moveNext(); }
    reset() { this.e.moveNext(); }
    next() { return this.moveNext() ? this.current() : undefined; }
}
class Enumerable<T> {
    private impl: IEnumerable<T>;

     /* either pass an IEnumerator or a function creating an enumerator */
    constructor(e?: IEnumerable<T>) {
        if (e !== null && e !== undefined) {
            this.impl = e;
        }
        else {
            this.impl = {
                enumerator: () => { 
                    return {
                        current: ():T => undefined,
                        moveNext: () => false,
                        reset: () => { },
                    };
                },
            };
        }
    }

    enumerator(): Enumerator<T> { return new Enumerator<T>(this.impl.enumerator()); }

    array(): T[] {
        var e = this.enumerator();
        var res = [];
        while (e.moveNext())
            res.push(e.current());
        return res;
    }

    static Properties(obj): Enumerable<any> {
        if (obj === null || obj === undefined)
            return new Enumerable();

        if (obj instanceof Array)
            return Enumerable.Range(obj.length);

        var props = [];
        for (var p in obj) {
            props.push(p);
        }
        return props.enumerable();
    }
    static From<T>(array: T[]): Enumerable<T> {
        return new Enumerable({
            enumerator: () => {
                var i = -1;
                return {
                    current: () => (i > -1 && i < array.length) ? array[i] : undefined,
                    moveNext: () => ++i < array.length,
                    reset: () => i = -1,
                };
            },
        });
    }
    static Repeat<T>(value: T, N: number): Enumerable<T> {
        return new Enumerable<T>({
            enumerator: () => {
                var i = -1;
                return {
                    current: (): T => i > -1 && i < N ? value : undefined,
                    moveNext: () => ++i < N,
                    reset: () => i = -1,
                };
            },
        });
    }

    static Range(countOrStart: number, count?: number): Enumerable<number> {
        var start = 0;
        var N = countOrStart;
        if (typeof count === "number") {
            N = count;
            start = countOrStart;
        }

        return new Enumerable<number>({
            enumerator: () => {
                var i = -1;
                return {
                    current: (): number => i > -1 && i < N ? start + i : undefined,
                    moveNext: () => ++i < N,
                    reset: () => i = -1,
                };
            },
        });
    }

    forEach(action: (x: T) => void) {
        var e = this.enumerator();
        while (e.moveNext()) {
            action(e.current());
        }
    }
    forEachIndex(action: (index: number, item: T) => void) {
        var e = this.enumerator();
        var i = 0;
        while (e.moveNext()) {
            action(i++, e.current());
        }
    }

    append(other: IEnumerable<T>): Enumerable<T> {
        return new Enumerable<T>({
            enumerator: () => {
                var e0 = this.enumerator();
                var e1 = other.enumerator();
                var cur = undefined;
                var part = 0;
                return {
                    current: () => cur,
                    reset: () => {
                        part = 0;
                        cur = undefined;
                        e0.reset();
                        e1.reset();
                    },
                    moveNext: () => {
                        cur = undefined;
                        var moved = false;
                        if (part === 0) {
                            moved = e0.moveNext();
                            if (!moved)
                                part = 1;
                            else
                                cur = e0.current();
                        }
                        if (part === 1) {
                            moved = e1.moveNext();
                            if (!moved)
                                part = 2;
                            else
                                cur = e1.current();
                        }
                        return moved;
                    }
                }
            },
        });
    }

    where(predicate: (x: T) => boolean): Enumerable<T> {
        return new Enumerable<T>({
            enumerator: () => {
                var e = this.enumerator();
                return {
                    current: ():T => e.current(),
                    reset: () => e.reset(),
                    moveNext: () => {
                        do {
                            var moved = e.moveNext();
                            if (!moved)
                                return false;
                        }
                        while (!predicate(e.current()));
                        return true;
                    },
                }
            }
        });
    }

	take(num: number): Enumerable<T> {
		return new Enumerable<T>({
			enumerator: () => {
				var e = this.enumerator();
				var i = 0;
				return {
					current: () => {
						if (i > num) return undefined;
						return e.current();
					},
					reset: () => { i = 0; e.reset(); },
					moveNext: () => { return i++ < num && e.moveNext(); },
				};
			},
		});
	}

	skip(num: number): Enumerable<T> {
		return new Enumerable<T>({
			enumerator: () => {
				var e = this.enumerator();
				var i = num;
				return {
					current: () => e.current(),
					reset: () => { i = num; e.reset(); },
					moveNext: () => {
						while (i > 0 && e.moveNext()) { i--; }
						if (i === 0)
							return e.moveNext();
						return false;
					},
				};
			},
		});
	}

    select<U>(cvt: (item: T) => U): Enumerable<U> {
        return new Enumerable<U>({
            enumerator: () => {
                var e = this.enumerator();
                return {
                    current: () => {
                        var u = e.current();
                        return u !== undefined ? cvt(u) : undefined;
                    },
                    reset: () => e.reset(),
                    moveNext: () => e.moveNext(),
                };
            },
        });
    }

    selectMany<U>(cvt: (item: T) => IEnumerable<U>): Enumerable<U> {
        return new Enumerable<U>({
            enumerator: () => {
                var e = this.enumerator();
                var sub: IEnumerator<U> = undefined;
                return {
                    reset: () => {
                        e.reset();
                        sub = undefined;
                    },
                    moveNext: (): boolean => {
                        while (sub === undefined || !sub.moveNext()) {
                            sub = undefined;
                            while (sub === undefined) {
                                if (!e.moveNext()) {
                                    sub = undefined;
                                    return false;
                                }
                                var u = e.current();
                                sub = u !== undefined ? cvt(u).enumerator() : undefined;
                            }
                        }
                        return true;
                    },
                    current: () => sub !== undefined ? sub.current() : undefined,
                }
            },
        });
    }

    distinct<U>(getKey?: (item: T) => U): Enumerable<T> {
        return new Enumerable<T>({
            enumerator: () => {
                if (getKey === undefined) {
                    getKey = x => <U><any>x;
                }
                var found = [];
                var isThere = (x: T): boolean => {
                    var k = getKey(x);
                    if (found.enumerable().contains(k)) {
                        return true;
                    }
                    else {
                        found.push(k);
                        return false;
                    }
                }
                var e = this.enumerator();
                return {
                    reset: () => {
                        found = [];
                        e.reset();
                    },
                    current: () => e.current(),
                    moveNext: () => {
                        do {
                            if (!e.moveNext())
                                return false;
                        }
                        while (isThere(e.current()));
                        return true;
                    },
                };
            }
        })
    }

    sort(cmpFn?: (a: T, b: T) => number): Enumerable<T> {
        var res = this.array();
        return res.sort(cmpFn).enumerable();
    }
    orderBy(getM: (x: T) => number): Enumerable<T> {
        return this.sort((a, b) => getM(a) - getM(b));
    }
    orderByString(getS: (x: T) => string): Enumerable<T> {
        return this.sort((a, b) => {
            var sa = '' + getS(a);
            var sb = '' + getS(b);
            if (sa > sb) return 1;
            if (sa < sb) return -1;
            return 0;
        });
    }

    max(cvt?: (item: T) => number) {
        var max = undefined;
        if (!cvt) cvt = item => <any>item;
        var e = this.enumerator();
        while (e.moveNext()) {
            var n = cvt(e.current());
            if (typeof n !== "number")
                continue;
            if (typeof max === "number") {
                if (n > max)
                    max = n;
            }
            else {
                max = n;
            }
        }
        return max;
    }

    min(cvt?: (item: T) => number) {
        var min = undefined;
        if (!cvt) cvt = item => <any>item;
        var e = this.enumerator();
        while (e.moveNext()) {
            var n = cvt(e.current());
            if (typeof n !== "number")
                continue;
            if (typeof min === "number") {
                if (n < min)
                    min = n;
            }
            else {
                min = n;
            }
        }
        return min;
    }

    contains(item: T) { return this.has(x => x === item); }
    has(predicate: (x: T) => boolean): boolean {
        var e = this.enumerator();
        do {
            if (!e.moveNext())
                return false;
        }
        while (!predicate(e.current()));
        return true;
    }
    first(predicate?: (x: T) => boolean): T {
        if (!predicate)
            predicate = x => true;
        var e = this.enumerator();
        var cur: T;
        do {
            if (!e.moveNext())
                return undefined;
            cur = e.current();
        }
        while (!predicate(cur));
        return cur;
    }

    aggregate(merge: (item1: T, item2: T) => T, emptyValue?:T): T {
        var e = this.enumerator();
        if (!e.moveNext()) {
            return emptyValue;
        }

        var res = e.current();
        while (e.moveNext()) {
            res = merge(res, e.current());
        }
        return res;
    }
}

History

8/8/2014: Initial release of the Enumerable class.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) http://www.ansibleww.com.au
Australia Australia
The Australia born French man who went back to Australia later in life...
Finally got over life long (and mostly hopeless usually, yay!) chronic sicknesses.
Worked in Sydney, Brisbane, Darwin, Billinudgel, Darwin and Melbourne.

Comments and Discussions

 
QuestionWhy interface not defined? Pin
Toater12-Aug-14 15:45
Toater12-Aug-14 15:45 
AnswerRe: Why interface not defined? Pin
Super Lloyd12-Aug-14 16:02
Super Lloyd12-Aug-14 16:02 
GeneralRe: Why interface not defined? Pin
Toater12-Aug-14 19:57
Toater12-Aug-14 19:57 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.