如何在 JavaScript 中按多列对多维数组进行排序?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/6101475/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-25 19:27:11  来源:igfitidea点击:

How does one sort a multi dimensional array by multiple columns in JavaScript?

javascriptarrayssortingmultidimensional-array

提问by Nicholas

I've been working on this problem all day without a good solution. Google has been little help as well. I have a script that needs to accept a two dimensional array with an unknown number number of rows/columns. The script also needs to accept a one dimensional array containing a list of columns to sort by, and another containing the order to sort by. The call will look a little like this:

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

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

The function do2DArraySort should return the dataArr array sorted by the first column (in ascending order), then by the fifth (in descending order), then by the third (in descending order), then by the second (in descending order). I was able to make it two levels deep using the code below, but it fell apart once I tried adding a third sort column. I understand why, but I can't figure out a good way to make it work.

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

Is there a standard way of doing this? Could someone point me to a good script online I can study and use as a template? Or can someone suggest a modification to my code to make it work?

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

Thanks!

谢谢!

//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;
}

回答by Lekensteyn

The array literal []is preferred over new Array. The notation {0,4,3,1}is not valid and should be [0,4,3,1].

数组文字[]优于new Array. 符号{0,4,3,1}无效,应该是[0,4,3,1]

Is there a need for reinventing the wheel? Two arrays can be joined using:

有必要重新发明轮子吗?可以使用以下方法连接两个数组:

originalArray = originalArray.concat(addArray);

Elements can be appended to the end using:

可以使用以下方法将元素附加到末尾:

array.push(element);

Arrays have a method for sorting the array. By default, it's sorted numerically:

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

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

Arrays can be reversed as well. Continuing the previous example:

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

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

To provide custom sorting, you can pass the optional function argument to array.sort():

要提供自定义排序,您可以将可选函数参数传递给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"]
 */

Elements will retain their position if the element equals an other element. Using this, you can combine multiple sorting algoritms. You must apply your sorting preferences in reverse order since the last sort has priority over previous ones. To sort the below array by the first column (descending order) and then the second column (ascending order):

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

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]
 */

To sort latin strings (i.e. English, German, Dutch), use String.localeCompare:

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

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

To sort date's from the Dateobject, use their milliseconds representation:

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

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

You could apply this sort function to all kind of data, just follow the rules:

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

xis the result from comparing two values which should be returned by a function passed to array.sort.

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

  1. x < 0: element_ashould come before element_b
  2. x = 0: element_aand element_bare equal, the elements are not swapped
  3. x > 0: element_ashould come after element_b
  1. x < 0:element_a应该先来element_b
  2. x = 0:element_a并且element_b相等,元素不交换
  3. x > 0:element_a应该在后面element_b

回答by rab

There are already good answers to this question, would like to add a short functions to handle multiple key array sort inspired solution of https://stackoverflow.com/users/2279116/shinobi.

这个问题已经有很好的答案,想添加一个简短的函数来处理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);

Let's take an following example

让我们举一个下面的例子

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
}))

The output would be following.

输出将如下。

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

回答by maerics

I would suggest writing a higher-order function which takes the orderList and orderDir as arguments and returns a comparator function which can be passed directly to Array#sort. This way you can try different implementations (which trade off simplicity for performance, for example).

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

This untested code demonstrates the idea:

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

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));

Note that you'll want to be careful about using "localeCompare" vs subtraction for strings vs numbers, so perhaps that aspect could be parameterized to the getComparator function as well.

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

回答by Nicholas

Based on Lekensteyn's excellent response, I've developed the following solution to my needs. I haven't done full QA testing on it yet and don't know if it is perfect (in fact, I'm quite sure it's not), but I hope that others can get some use out of this and build upon it for their needs. I'll post an update if any major changes needed made.

基于 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;
}

回答by Joseph Ptheitroadier

var arr = [27, 2, 4, 13]
arr.sort();

sets arr as [13, 2, 27, 4], because arrays are sorted as strings by default in JavaScript

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

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

sets arr as [2, 4, 13, 27] sorting numerically forward.

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

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

sets arr as [27, 13, 4, 2] sorting numerically in reverse.

将 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();

sets marr as follows, sorting array rows, by column in order as strings..

设置 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]

calling sort by column allows sorting by a single column. sort row by 3rd column as number..

调用 sort by column 允许按单列排序。按第 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]

then sort 4th column in reverse as number..

然后将第 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]

then sort 2nd column ascending as number

然后按数字升序对第二列进行排序

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]

notice each time you sort, prior order is maintained where new column is a match to a consecutive row.

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

Now you can roll in alpha sorting of the first column

现在您可以滚动第一列的 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]

sort all the numeric columns in a desc loop : 4, 3, 2 then sort the 1st column asc as string

对 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]

combine these sorts.. in a more logical way, in order of what column you want to sort & how you want it sorted first

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

// 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]