zoukankan      html  css  js  c++  java
  • 对JavaScript对象数组按指定属性和排序方向进行排序

    引子

    在以数据为中心的信息系统中,以表格形式展示数据是在常见不过的方式了。对数据进行排序是必不可少的功能。排序可以分为按单个字段排序和按多个字段不同排序方向排序。单字段排序局限性较大,不能满足用户对数据的关注点变化的需求,而多字段排序就可以较好的弥补这个缺陷。

    多字段排序,实现的方式从大的层面上可以分为后端实现和前端实现。

    后端排序

    后端实现排序可以在数据库层面实现或者在应用程序层面实现。

    数据库层面实现多字段排序非常简单,使用SQL的排序指令“Order By”即可——Order By field1 asc, field2 desc, field3 asc -- ...

    应用程序层面是指Web应用层(这里不讨论C/S架构),比如PHP、Java Web、ASP.NET等。应用程序层面实现就是使用PHP、Java、.NET(C#/VB)这些后端服务语言来实现对数据的排序。以ASP.NET C# 为例,因为C#中的LINQ内置了对集合类型的诸多操作,并且支持多属性排序,所以使用LINQ能够很方便的实现此目的——from f in foos orderby f.Name descending, f.Num ascending select f(可以发现LINQ的排序语法几乎与SQL的一模一样)。如果其它语言没有内置类似的支持,则按照排序算法来实现,这是通用的,与编程语言无关。

    前端排序

    在JavaScript中,数组有一个排序方法“sort”,当数组是一个简单数组(数组元素是简单类型——字符串、数值和布尔)时,使用该方法可以很方便的到达排序目的。但是当数组元素是非简单类型,比如名/值对的Object,并且想要按照指定的某几个属性按不同的排序方向进行排序时,简单的调用“sort”方法就不能实现此目的了。

    不过好在“sort”方法预留了自定义排序的接口,可以实现想要的排序方式。

    来看看数组的“sort”方法是怎样的。

    sort函数原型

    // 对数组的元素做原地的排序,并返回这个数组。
    // 默认按照字符串的Unicode码位点(code point)排序。
    Array.prototype.sort([compareFunction]:number); // number:-1 | 0 | 1。
    
    // 典型的比较函数(升序排序)。
    function compareFunction(item1, item2) {
        if(item1 > item2) {
            return 1; // 如果是降序排序,返回-1。
        }else if(item1 === item2) {
            return 0;
        }else {
            return -1; // 如果是降序排序,返回1。
        }
    }
    

    说明:如果没有指明compareFunction,那么元素会被转换为字符串的诸个字符并按照Unicode位点顺序排序。例如,"Cherry"会被排列到"banana"之前。当对数字进行排序的时候, 9 会出现在 80 之前,因为他们会先被转换为字符串,而 "80" 比 "9" 要靠前。

    • 如果 compareFunction(a, b) 小于 0 ,那么 a 会被排列到 b 之前;

    • 如果 compareFunction(a, b) 等于 0 ,a 和 b
      的相对位置不变。备注:ECMAScript标准并不保证这一行为,而且也不是所有浏览器都会遵守(例如 Mozilla 在 2003

    年之前的版本);

    • 如果 compareFunction(a, b) 大于 0 , b 会被排列到 a 之前。

    • compareFunction(a, b) 必须总是对相同的输入返回相同的比较结果,否则排序的结果将是不确定的。

    注:以上规则得出的排序结果是升序的,如果想要得到降序的结果,则在比较结果大于 0 时返回小于 0 的结果,比较结果小于 0 时 返回大于 0 的结果即可。

    要实现多属性排序,关键就在于比较函数的实现。根据以上规则, 实现多属性不同方向排序,依然要返回两个比较项的大小关系。那么多属性对象的大小关系如何确定呢?这个可以分两步走。

    第一步,记录下两个排序项按照各个排序属性及方向进行比较得到的结果。

    var propOrders = { "prop1":"asc", "prop2":"desc", "prop3":"asc"};
    
    function cmp(item1, item2, propOrders) {
        var cps = []; // 用于记录各个排序属性的比较结果,-1 | 0 | 1 。
        var isAsc = true; // 排序方向。     
        for(var p in propOrders) {
            isAsc = propOrders[p] === "asc";
            if(item1[p] > item2[p]) {
                cps.push(isAsc ? 1 : -1);
                break; // 可以跳出循环了,因为这里就已经知道 item1 “大于” item2 了。
            } else if(item1[p] === item2[p]) {
                cps.push(0);
            } else {
                cps.push(isAsc ? -1 : 1);
                break; // 可以跳出循环,item1 “小于” item2。
            }        
        }     
        
        /*
         .
         .
         .
        */
    }

    第二步,根据各排序属性比较结果综合判断得出两个比较项的最终大小关系。

        /* 
         .
         .
         . 
        */
        
        for(var j = 0; j < cps.length; j++) {
            if(cps[j] === 1 || cps[j] === -1) {
                return cps[j];
            }
        }
        return 0;

    有了上述思路后,实现整个比较函数就容易了,下面是比较函数的完整JavaScript代码:

    比较函数

    function SortByProps(item1, item2) {
        "use strict";
        var props = [];
        for (var _i = 2; _i < arguments.length; _i++) {
            props[_i - 2] = arguments[_i];
        }
            
        var cps = []; // 存储排序属性比较结果。
        // 如果未指定排序属性,则按照全属性升序排序。    
        var asc = true;
        if (props.length < 1) {
            for (var p in item1) {
                if (item1[p] > item2[p]) {
                    cps.push(1);
                    break; // 大于时跳出循环。
                } else if (item1[p] === item2[p]) {
                    cps.push(0);
                } else {
                    cps.push(-1);
                    break; // 小于时跳出循环。
                }
            }
        } else {
            for (var i = 0; i < props.length; i++) {
                var prop = props[i];
                for (var o in prop) {
                    asc = prop[o] === "asc";
                    if (item1[o] > item2[o]) {
                        cps.push(asc ? 1 : -1);
                        break; // 大于时跳出循环。
                    } else if (item1[o] === item2[o]) {
                        cps.push(0);
                    } else {
                        cps.push(asc ? -1 : 1);
                        break; // 小于时跳出循环。
                    }
                }
            }
        }        
             
        for (var j = 0; j < cps.length; j++) {
            if (cps[j] === 1 || cps[j] === -1) {
                return cps[j];
            }
        }
        return 0;          
    }

    测试用例

        // -------------测试用例------------------------------
        
        var items = [   { name: 'Edward', value: 21 },
                        { name: 'Sharpe', value: 37 },
                        { name: 'And', value: 45 },
                        { name: 'Edward', value: -12 },
                        { name: 'Magnetic', value: 21 },
                        { name: 'Zeros', value: 37 }
                    ];
                    
        function test(propOrders) {
            items.sort(function (a, b) {
                return SortByProps(a, b, propOrders);
            });
            console.log(items);
        }
        
        function testAsc() {
            test({ "name": "asc", "value": "asc" });
        }
        
        function testDesc() {
            test({ "name": "desc", "value": "desc" });
        }
        
        function testAscDesc() {
            test({ "name": "asc", "value": "desc" });
        }
        
        function testDescAsc() {
            test({ "name": "desc", "value": "asc" });
        }

    TypeScript代码

    /**
    ** 排序方向。
    */
    type Direct = "asc" | "desc";
    
    /**
    ** 排序属性。
    ** 
    ** @interface IPropertyOrder
    */
    interface IPropertyOrder {            
        [name: string] : Direct;
    }
    
    /**
    ** 简单名/值对象。
    ** 
    ** @interface ISimpleObject
    */
    interface ISimpleObject {
        [name: string] : string | number | boolean;
    }
    
    /**
    ** 对简单的名/值对象按照指定属性和排序方向进行排序(根据排序属性及排序方向,
    ** 对两个项依次进行比较,并返回代表排序位置的值)。
    ** 
    ** @template T 简单的名/值对象。
    ** @param {T} item1 排序比较项1。
    ** @param {T} item2 排序比较项2。
    ** @param {...IPropertyOrder[]} props 排序属性。
    ** @returns 若项1大于项2返回1,若项1等于项2返回0,否则返回-1。
    */
    function SortByProps<T extends ISimpleObject>
    (item1: T, item2: T, ...props: IPropertyOrder[]) {
        "use strict";
        var cps: Array<number> = []; // 存储排序属性比较结果。
        // 如果未指定排序属性,则按照全属性升序排序。    
        var asc = true;
        if (props.length < 1) {
            for (var p in item1) {
                if (item1[p] > item2[p]) {
                    cps.push(1);
                    break; // 大于时跳出循环。
                } else if (item1[p] === item2[p]) {
                    cps.push(0);
                } else {
                    cps.push(-1);
                    break; // 小于时跳出循环。
                }
            }
        } else { // 按照指定属性及升降方向进行排序。
            for (var i = 0; i < props.length; i++) {
                var prop = props[i];
                for (var o in prop) {
                    asc = prop[o] === "asc";
                    if (item1[o] > item2[o]) {
                        cps.push(asc ? 1 : -1);
                        break; // 大于时跳出循环。
                    } else if (item1[o] === item2[o]) {
                        cps.push(0);
                    } else {
                        cps.push(asc ? -1 : 1);
                        break; // 小于时跳出循环。
                    }
                }
            }
        }
    
        for (var j = 0; j < cps.length; j++) {
            if (cps[j] === 1 || cps[j] === -1) {
                return cps[j];
            }
        }
        return 0;    
    }

    使用场景及局限性

    在前端使用JavaScript实现多属性排序,减少了对服务器端的请求,减轻服务器端的计算压力,但是也仅适用于只需要对本地数据进行排序的情形。如果需要对整个数据集进行多属性排序,最终还是要在服务器端的数据库层面上进行。

  • 相关阅读:
    【codecombat】 试玩全攻略 第二章 边远地区的森林 一步错
    【codecombat】 试玩全攻略 第十八关 最后的kithman族
    【codecombat】 试玩全攻略 第二章 边远地区的森林 woodlang cubbies
    【codecombat】 试玩全攻略 第二章 边远地区的森林 羊肠小道
    【codecombat】 试玩全攻略 第十七关 混乱的梦境
    【codecombat】 试玩全攻略 第二章 边远地区的森林 林中的死亡回避
    【codecombat】 试玩全攻略 特别关:kithguard斗殴
    【codecombat】 试玩全攻略 第二章 边远地区的森林 森林保卫战
    【codecombat】 试玩全攻略 第二章 边远地区的森林
    实验3 类和对象||
  • 原文地址:https://www.cnblogs.com/miaoqing/p/5586995.html
Copyright © 2011-2022 走看看