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

13

我一整天都在解决这个问题,但是没有好的解决方案。 Google也没有提供太多帮助。 我有一个脚本需要接收一个未知行列数的二维数组,并且还需要接受一个包含要排序的列的一维数组,以及另一个包含按什么顺序排序的数组。 调用将类似于这样:

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;
}
5个回答

26

使用数组字面量[]比使用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 < 0: element_a 应该在element_b之前
  2. x = 0: element_aelement_b相等,元素不会交换
  3. x > 0: element_a 应该在element_b之后

谢谢;这太棒了,收获颇丰。我现在要做进一步的研究。看起来这很好用,只要所有元素都是数字,但由于我的数组可能包含混合的数字、日期、字符等,我需要想办法修改该排序函数,以确定字段类型并相应地进行排序。您是否知道可以指导我这方面的好资源? - Nicholas
1
@Nicholas:我提供了一个关于对Date对象和字符串进行排序的示例。如果你理解数组的使用并且了解一些Javascript,你可以对所有内容进行排序,只要你有排序算法的要求。如果不清楚,请添加评论以便解释。 - Lekensteyn
Lekensteyn;再次感谢您。我认为我现在已经足够理解(可能会有危险);)。我发现基本的排序算法似乎可以在日期上正常工作,而无需使用getTime方法。我是否忽略了任何问题?您是真正的救星。 :) - Nicholas
@Nicholas:如果你的“date”是一个字符串,你需要先将它转换成日期格式:(new Date("Thu, 21 Dec 2000 16:01:07 +0200")).getTime()。对于大型数组,最好缓存结果,并使用缓存结果进行比较。 - Lekensteyn
2
这是一个非常不错的回答,但有一个问题:Lekensteyn提到了Array.prototype.sort()方法,“如果元素等于另一个元素,则元素将保留其位置”,然而ECMAScript规范明确表示实现并不一定要这样做,一些浏览器也不会这么做。Chrome和Opera都没有这么做。因此,你不能期望多次调用sort()在不同浏览器中的行为方式是相同的。 - hallvors

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

通过调用按列排序函数,可以按单个列进行排序。 按第三列数字升序排列行。

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]

然后按数字反向排序第四列..

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]

请注意,每次排序时,先前的顺序会保持不变,其中新列与连续行匹配。

现在您可以对第一列进行字母排序

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

将所有数字列按降序循环排序:4、3、2,然后将第一列作为字符串按升序排序

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]

3

这个问题已经有了很好的答案,我想增加一个处理多关键字数组排序的短函数,灵感来自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()总是按升序返回键,所以它将始终从左到右开始对列进行排序。 - user639467

0
我建议编写一个高阶函数,该函数将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函数中。

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接