Sort Javascript arrays by multiple properties


Keywords: javascript, typescript, sort, sorting, JSLINQ, javascript linq, JLinq
Publish Date: 7/14/2012
Description: Sorting arrays in Javascript Typscript

I was given a requirement to sort a javascript object by multipl properties. I needed to sort a list of vehicles by year, then make, then model, then mileage. With help from my super javascript guru friends... Here is something.

 

Here is the js source. and modifications below..

//-----------------------------------------------------------------------
// Part of the LINQ to JavaScript (JSLINQ) v2.10 Project - http://jslinq.codeplex.com
// Copyright (C) 2009 Chris Pietschmann (http://pietschsoft.com). All rights reserved.
// This project is licensed under the Microsoft Reciprocal License (Ms-RL)
// This license can be found here: http://jslinq.codeplex.com/license
//-----------------------------------------------------------------------
(function () {
    JSLINQ = window.JSLINQ = function (dataItems) {
        return new JSLINQ.fn.init(dataItems);
    };

    var getType = function (obj) {
        return typeof (obj); // o.prototype.toString.call(obj).replace('object ', '');
    };

    var _undefined;

    JSLINQ.fn = JSLINQ.prototype = {
        init: function (dataItems) {
            this.items = dataItems;
        },

        // The current version of JSLINQ being used
        jslinq: "2.10",

        ToArray: function () { return this.items; },
        Where: function (clause) {
            var item;
            var newArray = new Array();

            // The clause was passed in as a Method that return a Boolean
            for (var index = 0; index < this.items.length; index++) {
                if (clause(this.items[index], index)) {
                    newArray[newArray.length] = this.items[index];
                }
            }
            return new JSLINQ(newArray);
        },
        Select: function (clause) {
            var item;
            var newArray = new Array();

            // The clause was passed in as a Method that returns a Value
            for (var i = 0; i < this.items.length; i++) {
                if (clause(this.items[i])) {
                    newArray[newArray.length] = clause(this.items[i]);
                }
            }
            return new JSLINQ(newArray);
        },
        OrderBy: function (clause) {
            var tempArray = new Array();
            for (var i = 0; i < this.items.length; i++) {
                tempArray[tempArray.length] = this.items[i];
            }
            return new JSLINQ(
            tempArray.sort(function (a, b) {
                var x = clause(a);
                var y = clause(b);
                return ((x < y) ? -1 : ((x > y) ? 1 : 0));
            })
        );
        },
        OrderByDescending: function (clause) {
            var tempArray = new Array();
            for (var i = 0; i < this.items.length; i++) {
                tempArray[tempArray.length] = this.items[i];
            }
            return new JSLINQ(
            tempArray.sort(function (a, b) {
                var x = clause(b);
                var y = clause(a);
                return ((x < y) ? -1 : ((x > y) ? 1 : 0));
            })
        );
        },
        SelectMany: function (clause) {
            var r = new Array();
            for (var i = 0; i < this.items.length; i++) {
                r = r.concat(clause(this.items[i]));
            }
            return new JSLINQ(r);
        },
        Count: function (clause) {
            if (clause == null)
                return this.items.length;
            else
                return this.Where(clause).items.length;
        },
        Distinct: function (clause) {
            var item;
            var dict = new Object();
            var retVal = new Array();
            for (var i = 0; i < this.items.length; i++) {
                item = clause(this.items[i]);
                // TODO - This doens't correctly compare Objects. Need to fix this
                if (dict[item] == null) {
                    dict[item] = true;
                    retVal[retVal.length] = item;
                }
            }
            dict = null;
            return new JSLINQ(retVal);
        },
        Any: function (clause) {
            for (var index = 0; index < this.items.length; index++) {
                if (clause(this.items[index], index)) { return true; }
            }
            return false;
        },
        All: function (clause) {
            for (var index = 0; index < this.items.length; index++) {
                if (!clause(this.items[index], index)) { return false; }
            }
            return true;
        },
        Reverse: function () {
            var retVal = new Array();
            for (var index = this.items.length - 1; index > -1; index--)
                retVal[retVal.length] = this.items[index];
            return new JSLINQ(retVal);
        },
        First: function (clause) {
            if (clause != null) {
                return this.Where(clause).First();
            }
            else {
                // If no clause was specified, then return the First element in the Array
                if (this.items.length > 0)
                    return this.items[0];
                else
                    return null;
            }
        },
        Last: function (clause) {
            if (clause != null) {
                return this.Where(clause).Last();
            }
            else {
                // If no clause was specified, then return the First element in the Array
                if (this.items.length > 0)
                    return this.items[this.items.length - 1];
                else
                    return null;
            }
        },
        ElementAt: function (index) {
            return this.items[index];
        },
        Concat: function (array) {
            var arr = array.items || array;
            return new JSLINQ(this.items.concat(arr));
        },
        Intersect: function (secondArray, clause) {
            var clauseMethod;
            if (clause != undefined) {
                clauseMethod = clause;
            } else {
                clauseMethod = function (item, index, item2, index2) { return item == item2; };
            }

            var sa = secondArray.items || secondArray;

            var result = new Array();
            for (var a = 0; a < this.items.length; a++) {
                for (var b = 0; b < sa.length; b++) {
                    if (clauseMethod(this.items[a], a, sa[b], b)) {
                        result[result.length] = this.items[a];
                    }
                }
            }
            return new JSLINQ(result);
        },
        DefaultIfEmpty: function (defaultValue) {
            if (this.items.length == 0) {
                return defaultValue;
            }
            return this;
        },
        ElementAtOrDefault: function (index, defaultValue) {
            if (index >= 0 && index < this.items.length) {
                return this.items[index];
            }
            return defaultValue;
        },
        FirstOrDefault: function (defaultValue) {
            return this.First() || defaultValue;
        },
        LastOrDefault: function (defaultValue) {
            return this.Last() || defaultValue;
        },
        Range: function (from, to) {
            return JSLINQ(this.items.slice(from, to));
        },
        //sort options = [{Prop:'SortProperty',IsAsc:true},ect....]
        OrderBySortOptions: function (sortOptions) {
            var sortFunc = function (a, b) {
                var i, result;
                for (i = 0; i < sortOptions.length; i++) {
                    var x, y, currentOption;
                    currentOption = sortOptions[i];
                    if (currentOption.IsAsc) {
                        x = a[currentOption.Prop];
                        y = b[currentOption.Prop];
                    } else {
                        x = b[currentOption.Prop];
                        y = a[currentOption.Prop];
                    }
                    if (x !== y) {
                        if (typeof (x) === typeof (1) && typeof (y) === typeof (1)) {//sort numeric
                            result = ((x - y) < 0) ? -1 : ((x - y) > 0) ? 1 : 0;
                        } else {//sort alphanumeric
                            result = ((x < y) ? -1 : ((x > y) ? 1 : 0));
                        }
                        return result;
                    }
                }
				return 0;
            };
            return JSLINQ(this.items.sort(sortFunc));
        },
        Min: function (prop) {
            var arr = (prop === _undefined) ? this.items : JSLINQ(this.items).Select(function (x) { return x[prop]; }).items;
            return JSLINQ(arr).OrderBy(function (x) { return x; }).First();
        },
        Max: function (prop) {
            var arr = (prop === _undefined) ? this.items : JSLINQ(this.items).Select(function (x) { return x[prop]; }).items;
            return JSLINQ(arr).OrderBy(function (x) { return x; }).Last();
        },
        Average: function (prop, numberOfDecimalPlaces) {
            var arr = (prop === _undefined || prop === null) ? this.items : JSLINQ(this.items).Select(function (x) { return x[prop]; }).items;
            var result = 0;
            var sum = 0;
            var count = 0;
            for (var i = 0; i < arr.length; i += 1) {
                if (typeof (arr[i]) === typeof (1)) {
                    sum += arr[i];
                    count += 1;
                }
            }
            result = (sum / count);
            if (numberOfDecimalPlaces !== _undefined) {
                if (typeof (numberOfDecimalPlaces) === typeof (1)) {
                    result = Math.round(result * Math.pow(10, numberOfDecimalPlaces)) / Math.pow(10, numberOfDecimalPlaces);
                }
            }
            return result;
        }
    };
    JSLINQ.fn.init.prototype = JSLINQ.fn;
})();
Here is the Typescript version!! With a couple new methods as well.

//-----------------------------------------------------------------------
// Part of the LINQ to JavaScript (JSLINQ) v2.10 Project - http://jslinq.codeplex.com
// Copyright (C) 2009 Chris Pietschmann (http://pietschsoft.com). All rights reserved.
// This project is licensed under the Microsoft Reciprocal License (Ms-RL)
// This license can be found here: http://jslinq.codeplex.com/license
//-----------------------------------------------------------------------

//SortOption is used to sort an array by multiple item properties
//Example: Sort by Age then by Name where two records with the same Age value would sub sort by Name asc or

class SortOption {
    propSelector: (item: any) => any;
    isAsc: bool;
    constructor(propSelector: (item: any) => any, isAsc: bool) {
        this.propSelector = propSelector;
        this.isAsc = isAsc;
    }
}

class JLinq {
    items: any[];
    
    constructor(list: any[]) {
        this.items = [];
        for (var i = 0; i < list.length; i++) {
            this.items.push(list[i]);
        }
    }
    where = function (clause: (item: any) => bool): JLinq {
        var result = [];
        for (var i = 0; i < this.items.length; i++) {
            var item = this.items[i];
            if (clause(item)) {
                result.push(item);
            }
        }
        return new JLinq(result);
    };
    //This does not compare complex objects properly, its fine for 1 dimensional arrays
    distinct = function (clause: (item: any) => any): JLinq {
        var obj = {};
        var result = [];
        for (var i = 0; i < this.items.length; i++) {
            var item = clause(this.items[i]);
            if (obj[item] == null) {
                obj[item] = true;
                result.push(item);
            }
        }
        return new JLinq(result);
    };
    orderBy = function (clause: (item: any) => any): JLinq {
        var sortFunc = function (a, b) {
            var x = clause(a);
            var y = clause(b);
            if (typeof (x) === typeof (1) && typeof (y) === typeof (1)) {
                return ((x - y) < 0) ? -1 : ((x - y) > 0) ? 1 : 0;
            } else {//sort alphanumeric
                return ((x < y) ? -1 : ((x > y) ? 1 : 0));
            }
            return 0;
        };
        return new JLinq(this.items.sort(sortFunc));
    };
    orderByDescending = function (clause: (item: any) => any): JLinq {
        var sortFunc = function (a, b) {
            var x = clause(b);
            var y = clause(a);
            if (typeof (x) === typeof (1) && typeof (y) === typeof (1)) {
                return ((x - y) < 0) ? -1 : ((x - y) > 0) ? 1 : 0;
            } else {//sort alphanumeric
                return ((x < y) ? -1 : ((x > y) ? 1 : 0));
            }
            return 0;
        };
        return new JLinq(this.items.sort(sortFunc));
    };
    orderBySortOptions = function (sortOptions: SortOption[]): JLinq {
        var sortFunc = function (a, b) {
            var result = 0;
            for (var i = 0; i < sortOptions.length; i++) {
                var x;
                var y;
                var currentOption = sortOptions[i];
                if (currentOption.isAsc) {
                    x = currentOption.propSelector(a);
                    y = currentOption.propSelector(b);
                } else {
                    x = currentOption.propSelector(b);
                    y = currentOption.propSelector(a);
                }
                if (x !== y) {
                    if (typeof (x) === typeof (1) && typeof (y) === typeof (1)) {
                        //sort numeric
                        result = ((x - y) < 0) ? -1 : ((x - y) > 0) ? 1 : 0;
                    } else {
                        //sort alphanumeric
                        result = ((x < y) ? -1 : ((x > y) ? 1 : 0));
                    }
                    return result;
                }
            }
            return result;
        };
        return new JLinq(this.items.sort(sortFunc));
    };
    any = function (clause?: (item: any) => bool): bool {
        if (clause != null) {
            return this.where(clause).any();
        } else {
            return this.items.length > 0;
        }
    };
    average = function (clause: (item: any) => number, numberOfDecimalPlaces: number): number {
        var result = 0;
        if (clause != null) {
            var jl = new JLinq(this.select(clause).items);
            var numAry = jl.items;

            var sum = jl.sum(function (x) { return x; });
            var count = numAry.length;
            result = (sum / count);
            result = Math.round(result * Math.pow(10, numberOfDecimalPlaces)) / Math.pow(10, numberOfDecimalPlaces);
        }
        return result;
    };
    select = function (clause: (item: any) => any): JLinq {
        var result = [];
        for (var i = 0; i < this.items.length; i++) {
            result.push(clause(this.items[i]));
        }
        return new JLinq(result);
    };
    first = function (clause?: (item: any) => bool): any {
        if (clause != null) {
            return this.where(clause).first(null);
        } else {
            return (this.items.length > 0
            ? this.items[0]
            : null);
        }
    };
    last = function (clause?: (item: any) => bool): any {
        if (clause != null) {
            return this.where(clause).last();
        } else {
            return (this.items.length > 0
            ? this.items[this.items.length - 1]
            : null);
        }
    };
    min = function (clause: (item: any) => number): number {
        var result = this.select(clause).orderBy(function (x) { return x; }).first();
        return result;
    };
    max = function (clause: (item: any) => number): number {
        var result = this.select(clause).orderBy(function (x) { return x; }).last();
        return result;
    };
    sum = function (clause: (item: any) => number): number {
        var ary = this.select(clause).items;
        var sum = 0;
        for (var i in ary) {
            sum += ary[i];
        }
        return sum;
    };
}

Here is the rendered javascript ready to paste

//-----------------------------------------------------------------------
// Part of the LINQ to JavaScript (JSLINQ) v2.10 Project - http://jslinq.codeplex.com
// Copyright (C) 2009 Chris Pietschmann (http://pietschsoft.com). All rights reserved.
// This project is licensed under the Microsoft Reciprocal License (Ms-RL)
// This license can be found here: http://jslinq.codeplex.com/license
//-----------------------------------------------------------------------
//SortOption is used to sort an array by multiple item properties
//Example: Sort by Age then by Name where two records with the same Age value would sub sort by Name asc or
var SortOption = (function () {
    function SortOption(propSelector, isAsc) {
        this.propSelector = propSelector;
        this.isAsc = isAsc;
    }
    return SortOption;
})();
var JLinq = (function () {
    function JLinq(list) {
        this.where = function (clause) {
            var result = [];
            for(var i = 0; i < this.items.length; i++) {
                var item = this.items[i];
                if(clause(item)) {
                    result.push(item);
                }
            }
            return new JLinq(result);
        };
        //This does not compare complex objects properly, its fine for 1 dimensional arrays
        this.distinct = function (clause) {
            var obj = {
            };
            var result = [];
            for(var i = 0; i < this.items.length; i++) {
                var item = clause(this.items[i]);
                if(obj[item] == null) {
                    obj[item] = true;
                    result.push(item);
                }
            }
            return new JLinq(result);
        };
        this.orderBy = function (clause) {
            var sortFunc = function (a, b) {
                var x = clause(a);
                var y = clause(b);
                if(typeof (x) === typeof (1) && typeof (y) === typeof (1)) {
                    return ((x - y) < 0) ? -1 : ((x - y) > 0) ? 1 : 0;
                } else {
                    //sort alphanumeric
                    return ((x < y) ? -1 : ((x > y) ? 1 : 0));
                }
                return 0;
            };
            return new JLinq(this.items.sort(sortFunc));
        };
        this.orderByDescending = function (clause) {
            var sortFunc = function (a, b) {
                var x = clause(b);
                var y = clause(a);
                if(typeof (x) === typeof (1) && typeof (y) === typeof (1)) {
                    return ((x - y) < 0) ? -1 : ((x - y) > 0) ? 1 : 0;
                } else {
                    //sort alphanumeric
                    return ((x < y) ? -1 : ((x > y) ? 1 : 0));
                }
                return 0;
            };
            return new JLinq(this.items.sort(sortFunc));
        };
        this.orderBySortOptions = function (sortOptions) {
            var sortFunc = function (a, b) {
                var result = 0;
                for(var i = 0; i < sortOptions.length; i++) {
                    var x;
                    var y;
                    var currentOption = sortOptions[i];
                    if(currentOption.isAsc) {
                        x = currentOption.propSelector(a);
                        y = currentOption.propSelector(b);
                    } else {
                        x = currentOption.propSelector(b);
                        y = currentOption.propSelector(a);
                    }
                    if(x !== y) {
                        if(typeof (x) === typeof (1) && typeof (y) === typeof (1)) {
                            //sort numeric
                            result = ((x - y) < 0) ? -1 : ((x - y) > 0) ? 1 : 0;
                        } else {
                            //sort alphanumeric
                            result = ((x < y) ? -1 : ((x > y) ? 1 : 0));
                        }
                        return result;
                    }
                }
                return result;
            };
            return new JLinq(this.items.sort(sortFunc));
        };
        this.any = function (clause) {
            if(clause != null) {
                return this.where(clause).any();
            } else {
                return this.items.length > 0;
            }
        };
        this.average = function (clause, numberOfDecimalPlaces) {
            var result = 0;
            if(clause != null) {
                var jl = new JLinq(this.select(clause).items);
                var numAry = jl.items;
                var sum = jl.sum(function (x) {
                    return x;
                });
                var count = numAry.length;
                result = (sum / count);
                result = Math.round(result * Math.pow(10, numberOfDecimalPlaces)) / Math.pow(10, numberOfDecimalPlaces);
            }
            return result;
        };
        this.select = function (clause) {
            var result = [];
            for(var i = 0; i < this.items.length; i++) {
                result.push(clause(this.items[i]));
            }
            return new JLinq(result);
        };
        this.first = function (clause) {
            if(clause != null) {
                return this.where(clause).first(null);
            } else {
                return (this.items.length > 0 ? this.items[0] : null);
            }
        };
        this.last = function (clause) {
            if(clause != null) {
                return this.where(clause).last();
            } else {
                return (this.items.length > 0 ? this.items[this.items.length - 1] : null);
            }
        };
        this.min = function (clause) {
            var result = this.select(clause).orderBy(function (x) {
                return x;
            }).first();
            return result;
        };
        this.max = function (clause) {
            var result = this.select(clause).orderBy(function (x) {
                return x;
            }).last();
            return result;
        };
        this.sum = function (clause) {
            var ary = this.select(clause).items;
            var sum = 0;
            for(var i in ary) {
                sum += ary[i];
            }
            return sum;
        };
        this.items = [];
        for(var i = 0; i < list.length; i++) {
            this.items.push(list[i]);
        }
    }
    return JLinq;
})();

Comments (0):

Leave a Comment:




    Captcha Challenge