【问题标题】:How does one sort a multi dimensional array by multiple columns in JavaScript?如何在 JavaScript 中按多列对多维数组进行排序?
【发布时间】:2011-08-31 09:12:10
【问题描述】:

我整天都在解决这个问题,但没有一个好的解决方案。谷歌也没有什么帮助。我有一个脚本需要接受具有未知行数/列数的二维数组。该脚本还需要接受一个一维数组,其中包含要排序的列列表,另一个包含排序依据的顺序。调用看起来有点像这样:

var orderList = {0,4,3,1};
var orderDir = {asc,desc,desc,asc};
dataArr = do2DArraySort(dataArr, orderList, orderDir);

函数 do2DArraySort 应该返回按第一列(升序)、第五列(降序)、第三列(降序)、第二列(降序)排序的 dataArr 数组)。我可以使用下面的代码将其加深两层,但是一旦我尝试添加第三个排序列,它就崩溃了。我明白为什么,但我想不出一个让它工作的好方法。

有这样做的标准方法吗?有人可以指出我可以在线学习和用作模板的好脚本吗?或者有人可以建议修改我的代码以使其正常工作吗?

谢谢!

//appends an array content to the original array
function addToArray(originalArray, addArray) {
    if (addArray.length != 0) {
        var curLength = 0;
        curLength = originalArray.length;
        var maxLength = 0;
        maxLength = curLength + addArray.length;  
        var itrerateArray = 0;
        for (var r = curLength; r < maxLength; r++) {   
            originalArray[r] = addArray[itrerateArray];
            itrerateArray++;
        }
    }
}

function do2DArraySort(arrayToBeSorted, sortColumnArray, sortDirectionArray) {
    if (arrayToBeSorted == "undefined" || arrayToBeSorted == "null") return arrayToBeSorted;
    if (arrayToBeSorted.length == 0) return arrayToBeSorted;
    if (sortColumnArray.length == 0) return arrayToBeSorted;
    tempArray = arrayToBeSorted; 
    var totalLength = sortColumnArray.length; 
    for(var m = 0; m < totalLength; m++) {
        if (m == 0) {   
            doBubbleSort(tempArray, tempArray.length, sortColumnArray[m], sortDirectionArray[m]);         
        } else {     
            doMultipleSort(tempArray, sortColumnArray[m], sortColumnArray[m-1], sortDirectionArray[m]);
        }
    } 
    return tempArray;
}

//check if a value exists in a single dimensional array
function checkIfExists(arrayToSearch, valueToSearch) {
    if (arrayToSearch == "undefined" || arrayToSearch == "null") return false;
    if (arrayToSearch.length == 0) return false;
    for (var k = 0; k < arrayToSearch.length; k++) {
        if (arrayToSearch[k] == valueToSearch) return true;
    }
    return false;
}

//sorts an 2D array based on the distinct values of the previous column
function doMultipleSort(sortedArray, currentCol, prevCol, sortDirection) {
    var resultArray = new Array(); 
    var newdistinctValuesArray = new Array();
    //finding distinct previous column values 
    for (var n = 0; n < sortedArray.length; n++) {
        if (checkIfExists(newdistinctValuesArray, sortedArray[n][prevCol]) == false) newdistinctValuesArray.push(sortedArray[n][prevCol]);
    }
    var recCursor = 0;
    var newTempArray = new Array(); var toStoreArray = 0; 
    //for each of the distinct values
    for (var x = 0; x < newdistinctValuesArray.length; x++) {
        toStoreArray = 0;
        newTempArray = new Array();  
        //find the rows with the same previous column value
        for (var y = 0; y < sortedArray.length; y++) {
            if (sortedArray[y][prevCol] == newdistinctValuesArray[x]) {
                newTempArray[toStoreArray] = sortedArray[y];
                toStoreArray++;
            }
        }       //sort the row based on the current column
        doBubbleSort(newTempArray, newTempArray.length, currentCol, sortDirection);
        //append it to the result array
        addToArray(resultArray, newTempArray);
    }
    tempArray = resultArray;
}

【问题讨论】:

    标签: javascript arrays sorting multidimensional-array


    【解决方案1】:

    我建议编写一个高阶函数,它将 orderList 和 orderDir 作为参数并返回一个比较器函数,该函数可以直接传递给 Array#sort。通过这种方式,您可以尝试不同的实现(例如,以简单性换取性能)。

    这个未经测试的代码展示了这个想法:

    var getComparator = function(orderList, orderDir) {
      var len = orderList.length; // XXX: assume == orderDir.length
      return function(a, b) {
        var cmp, ax, bx, i;
        for (i=0; i<len; i++) { # For each field and direction...
          ax = a[orderList[i]];
          bx = b[orderList[i]];
          cmp = ax.localeCompare(bx); # compare elements...
          if (cmp != 0) { # if not equal then indicate order...
            return (orderDir[i]=='asc') ? -1 : 1;
          }
        }
        return 0; # otherwise, indicate equality.
      };
    };
    dataArr.sort(getComparator(orderList, orderDir));
    

    请注意,对于字符串和数字使用“localeCompare”和减法时,您需要小心,因此也许该方面也可以参数化为 getComparator 函数。

    【讨论】:

      【解决方案2】:

      数组字面量[] 优于new Array。符号{0,4,3,1} 无效,应为[0,4,3,1]

      是否需要重新发明轮子?可以使用以下方法连接两个数组:

      originalArray = originalArray.concat(addArray);
      

      元素可以使用:

      array.push(element);
      

      数组有一种对数组进行排序的方法。默认情况下,它是按数字排序的:

      // sort elements numerically
      var array = [1, 3, 2];
      array.sort(); // array becomes [1, 2, 3]
      

      数组也可以反转。继续前面的例子:

      array = array.reverse(); //yields [3, 2, 1]
      

      要提供自定义排序,您可以将可选函数参数传递给array.sort()

      array = [];
      array[0] = [1, "first element"];
      array[1] = [3, "second element"];
      array[2] = [2, "third element"];
      array.sort(function (element_a, element_b) {
          return element_a[0] - element_b[0];
      });
      /** array becomes (in order):
       * [1, "first element"]
       * [2, "third element"]
       * [3, "second element"]
       */
      

      如果元素等于另一个元素,元素将保留其位置。使用它,您可以组合多个排序算法。您必须以相反的顺序应用您的排序首选项,因为最后一个排序优先于以前的排序。按第一列(降序)和第二列(升序)对以下数组进行排序:

      array = [];
      array.push([1, 2, 4]);
      array.push([1, 3, 3]);
      array.push([2, 1, 3]);
      array.push([1, 2, 3]);
      // sort on second column
      array.sort(function (element_a, element_b) {
          return element_a[1] - element_b[1];
      });
      // sort on first column, reverse sort
      array.sort(function (element_a, element_b) {
          return element_b[0] - element_a[0];
      });
      /** result (note, 3rd column is not sorted, so the order of row 2+3 is preserved)
       * [2, 1, 3]
       * [1, 2, 4] (row 2)
       * [1, 2, 3] (row 3)
       * [1, 3, 3]
       */
      

      要对拉丁字符串(即英语、德语、荷兰语)进行排序,请使用String.localeCompare

      array.sort(function (element_a, element_b) {
          return element_a.localeCompare(element_b);
      });
      

      要对 Date 对象中的日期进行排序,请使用它们的毫秒表示:

      array.sort(function (element_a, element_b) {
          return element_a.getTime() - element_b.getTime();
      });
      

      您可以将此排序功能应用于所有类型的数据,只需遵循规则:

      x 是比较两个值的结果,这两个值应该由传递给array.sort 的函数返回。

      1. x &lt; 0: element_a 应该在 element_b 之前
      2. x = 0: element_aelement_b 相等,元素不交换
      3. x &gt; 0: element_a 应该在 element_b 之后

      【讨论】:

      • 谢谢;这太棒了,非常有启发性。我现在要做一些进一步的研究。假设所有元素都是数字的,这看起来效果很好,但是由于我的数组可能包含数字、日期、字符等的混合,我需要弄清楚如何修改排序中的函数以确定字段类型并相应地排序.您是否碰巧知道可以为此指向我的好资源?
      • @Nicholas:我提供了一个用于对 Date 对象和字符串的日期进行排序的示例。如果您了解数组的使用并了解一点 Javascript,那么只要您对排序算法有要求,就可以对所有内容进行排序。如果不清楚,请添加可以解释的评论。
      • 莱肯斯坦;再次谢谢你。我想我现在理解得很好(很危险);)。我发现基本的排序算法在日期上似乎可以正常工作,而无需使用 getTime 方法。我错过了跳过它的任何陷阱吗?你是真正的救生员。 :)
      • @Nicholas:如果您的“日期”是字符串,则需要先将其转换为日期:(new Date("Thu, 21 Dec 2000 16:01:07 +0200")).getTime()。对于大型数组,可能更喜欢缓存结果,并使用缓存的结果进行比较。
      • 这是一个很好的答案,但有一个问题:Lekensteyn 指出 Array.prototype.sort() 方法“如果元素等于另一个元素,元素将保留其位置”,但是ECMAScript 规范明确表示实现不必这样做,而某些浏览器则不需要。 Chrome 没有,Opera 没有。因此,您不能期望随后对 sort() 的多次调用会按照您在浏览器中的预期行为。
      【解决方案3】:

      基于 Lekensteyn 的出色响应,我开发了以下解决方案来满足我的需求。我还没有对它进行完整的 QA 测试,也不知道它是否完美(事实上,我很确定它不是),但我希望其他人可以从中得到一些用处,并以此为基础他们的需求。如果需要进行任何重大更改,我会发布更新。

      function do2DArraySort(dataArr, orderList, orderDir) {
          for (x=orderList.length-1; x >= 0; x--) {
              if (orderDir[x] == 'asc') {
                  dataArr.sort(sortMethodFunctionAsc);
              } else {
                  dataArr.sort(sortMethodFunctionDesc);
              }
          }
      
          return dataArr;
      }
      
      function sortMethodFunctionAsc(a, b) {
          if ((IsNumeric(a[orderList[x]]) && IsNumeric(b[orderList[x]])) || (IsDate(a[orderList[x]]) && IsDate(b[orderList[x]]))) {
              return a[orderList[x]] - b[orderList[x]];
          } else {
              if (a[orderList[x]].toString() > b[orderList[x]].toString()) {
                  return 1;
              } else if (a[orderList[x]].toString() < b[orderList[x]].toString()) {
                  return -1;
              } else {
                  return 0;
              }
          }
      }
      
      function sortMethodFunctionDesc(a, b) {
          if ((IsNumeric(a[orderList[x]]) && IsNumeric(b[orderList[x]])) || (IsDate(a[orderList[x]]) && IsDate(b[orderList[x]]))) {
              return b[orderList[x]] - a[orderList[x]];
          } else {
              if (a[orderList[x]].toString() < b[orderList[x]].toString()) {
                  return 1;
              } else if (a[orderList[x]].toString() > b[orderList[x]].toString()) {
                  return -1;
              } else {
                  return 0;
              }
          }
      }
      
      
      function IsNumeric(input) {
          return (input - 0) == input && input.length > 0;
      }
      
      function IsDate(testValue) {
          var returnValue = false;
          var testDate;
          try {
              testDate = new Date(testValue);
              if (!isNaN(testDate)) {
                  returnValue = true;
              } else {
                  returnValue = false;
              }
          }
          catch (e) {
              returnValue = false;
          }
          return returnValue;
      }
      

      【讨论】:

        【解决方案4】:
        var arr = [27, 2, 4, 13]
        arr.sort();
        

        将 arr 设置为 [13, 2, 27, 4],因为在 JavaScript 中数组默认按字符串排序

        arr.sort(function (a, b) {
            return a - b;
        });
        

        将 arr 设置为 [2, 4, 13, 27] 按数字向前排序。

        arr.sort(function (a, b) {
            return b - a;
        });
        

        将 arr 设置为 [27, 13, 4, 2] 以反向数字排序。

        var marr = [[]];
        marr.shift();
        
        marr.push(["frog", 4, 27, 13]);
        marr.push(["frog", 11, 5, 12]);
        marr.push(["cat", 16, 3, 5]);
        marr.push(["dog", 11, 7, 21]);
        marr.push(["cat", 16, 21, 6]);
        marr.push(["dog", 10, 280, 5]);
        marr.push(["dog", 10, 32, 5]);
        
        marr.sort();
        

        按如下方式设置marr,将数组行按列按字符串排序..

        ["cat", 16, 21, 6]
        ["cat", 16, 3, 5]
        ["dog", 10, 280, 5]
        ["dog", 10, 32, 5]
        ["dog", 11, 7, 21]
        ["frog", 11, 5, 12]
        ["frog", 4, 27, 13]
        

        调用按列排序允许按单列排序。 按第 3 列作为数字对行进行排序..

        marr.sort(function (a, b) {
            return a[2] - b[2];
        });
        
        ["cat", 16, 3, 5]
        ["frog", 11, 5, 12]
        ["dog", 11, 7, 21]
        ["cat", 16, 21, 6]
        ["frog", 4, 27, 13]
        ["dog", 10, 32, 5]
        ["dog", 10, 280, 5]
        

        然后将第 4 列反向排序为数字..

        marr.sort(function (a, b) {
            return b[3] - a[3];
        });
        
        ["dog", 11, 7, 21]
        ["frog", 4, 27, 13]
        ["frog", 11, 5, 12]
        ["cat", 16, 21, 6]
        ["cat", 16, 3, 5]
        ["dog", 10, 32, 5]
        ["dog", 10, 280, 5]
        

        然后将第二列按数字升序排序

        marr.sort(function (a, b) {
            return a[1] - b[1];
        });
        
        ["frog", 4, 27, 13]
        ["dog", 10, 32, 5]
        ["dog", 10, 280, 5]
        ["dog", 11, 7, 21]
        ["frog", 11, 5, 12]
        ["cat", 16, 21, 6]
        ["cat", 16, 3, 5]
        

        请注意,每次排序时,如果新列与连续行匹配,则会保持先前的顺序。

        现在您可以滚动第一列的 alpha 排序

        // asc
        marr.sort(function (a, b) {
            return (a[0] < b[0]) ? -1 : 1;
        });
        
        ["cat", 16, 21, 6]
        ["cat", 16, 3, 5]
        ["dog", 10, 32, 5]
        ["dog", 10, 280, 5]
        ["dog", 11, 7, 21]
        ["frog", 4, 27, 13]
        ["frog", 11, 5, 12]
        
        // desc
        marr.sort(function (a, b) {
            return (a[0] > b[0]) ? -1 : 1;
        });
        
        ["frog", 4, 27, 13]
        ["frog", 11, 5, 12]
        ["dog", 10, 32, 5]
        ["dog", 10, 280, 5]
        ["dog", 11, 7, 21]
        ["cat", 16, 21, 6]
        ["cat", 16, 3, 5]
        

        对 desc 循环中的所有数字列进行排序:4、3、2 然后将第一列 asc 排序为字符串

        for (var colid = 3; colid > 0; colid--) {
            marr.sort(function (a, b) {
                return (b[colid] - a[colid]);
            });
        }
        
        // 1st row as string asc
        marr.sort(function (a, b) {
            return (a[0] < b[0]) ? -1 : 1;
        });
        
        ["cat", 16, 21, 6]
        ["cat", 16, 3, 5]
        ["dog", 11, 7, 21]
        ["dog", 10, 280, 5]
        ["dog", 10, 32, 5]
        ["frog", 11, 5, 12]
        ["frog", 4, 27, 13]
        

        以更合乎逻辑的方式组合这些排序,按照您想要排序的列的顺序以及您希望它首先如何排序

        // c1 asc, c2 desc, c3 asc, c4 asc
        marr.sort(function (a, b) {
            return (a[0] < b[0]) ? -1 : (a[0] == b[0]) ?
                (b[1] - a[1]) || (a[2] - b[2]) || (a[3] - b[3]) : 1;
        });
        
        ["cat", 16, 3, 5]
        ["cat", 16, 21, 6]
        ["dog", 11, 7, 21]
        ["dog", 10, 32, 5]
        ["dog", 10, 280, 5]
        ["frog", 11, 5, 12]
        ["frog", 4, 27, 13]
        

        【讨论】:

          【解决方案5】:

          这个问题已经有了很好的答案,想添加一个简短的函数来处理https://stackoverflow.com/users/2279116/shinobi的多个键数组排序启发的解决方案。

          // sort function handle for multiple keys
          const sortCols  = (a, b, attrs) => Object.keys(attrs)
              .reduce((diff, k) =>  diff == 0 ? attrs[k](a[k], b[k]) : diff, 0);
          

          举个例子

          const array = [
              [1, 'hello', 4],
              [1, 'how', 3],
              [2, 'are', 3],
              [1, 'hello', 1],
              [1, 'hello', 3]
          ];
          
          array.sort((a, b) => sortCols(a, b, { 
             0: (a, b) => a - b, 
             1: (a, b) => a.localeCompare(b), 
             2: (a, b) => b - a
          }))
          

          输出如下。

          [ 1, "hello", 4 ]​
          [ 1, "hello", 3 ]​
          [ 1, "hello", 1 ]​
          [ 1, "how", 3 ]
          ​[ 2, "are", 3 ]
          

          【讨论】:

          • 如果您想对第二列然后对第一列进行排序,这将不起作用。它总是从左到右开始对列进行排序,因为 Object.keys() 以升序返回键
          猜你喜欢
          • 2014-05-13
          • 2015-05-12
          • 2011-03-15
          • 2013-12-09
          • 2015-06-28
          • 2014-01-19
          • 1970-01-01
          相关资源
          最近更新 更多