如何在多列上对数组进行排序?

145
我有一个多维数组。主要的数组是一组包含其他数组的元素,每个子数组都有相同数量的元素。我需要按照子数组中特定元素的值对主数组进行排序。有什么好的方法可以做到这一点吗?
[publicationID][publication_name][ownderID][owner_name] 

我想做的是按照 owner_name 然后按照 publication_name 对数组进行排序。我知道在JavaScript中,有一个名为 Array.sort() 的函数,你可以传入一个自定义的函数。在我的情况下,我的函数如下:

function mysortfunction(a, b) {
    var x = a[3].toLowerCase();
    var y = b[3].toLowerCase();

    return ((x < y) ? -1 : ((x > y) ? 1 : 0));
}

这个方法可以用来按照一个列(owner_name)排序,但是我该如何修改它来按照owner_name然后是publication_name的顺序排序呢?

21个回答

201
如果所有者名称不同,则按照它们进行排序。否则,使用出版物名称作为决定胜负的依据。
function mysortfunction(a, b) {

  var o1 = a[3].toLowerCase();
  var o2 = b[3].toLowerCase();

  var p1 = a[1].toLowerCase();
  var p2 = b[1].toLowerCase();

  if (o1 < o2) return -1;
  if (o1 > o2) return 1;
  if (p1 < p2) return -1;
  if (p1 > p2) return 1;
  return 0;
}

@dcp 我不明白它如何也能对第二个属性进行排序。除非你循环它与所选列的数量一样多。我是对的吗?例如:[[A, 10], [J, 15], [A, 5], [J, 5]] => [[A, 10], [A, 5], [J, 15], [J, 5]] - Bla...
2
@user26409021 - 不,那不对。它最终会变成[[A, 5], [A, 10], [J, 5], [J, 15]]。它首先按第一个属性排序,如果它们相同,则按第二个属性排序。因此,在您的示例中,A将排在J之前。如果两个元素的A相同,则使用第二个属性。因此,对于[A,10],[A,5],5在10之前,因此它将以[A,5],[A,10]的顺序结束。您可能错过的是,当您使用Array.sort时,mysortfunction会被多次调用,直到排序完成。 - dcp
3
mysortfunction函数中不需要使用循环,因为Array.sort会根据需要调用该函数,直到数组正确排序。mysortfunction的唯一职责是确定参数a和b是否相等,a是否小于b或a是否大于b。我们不需要循环来做出这个决定。希望这能帮助你。 - dcp
@dcp 这个能扩展到3列以上吗? - John Odom
1
@John Odom - 是的,您只需根据需要添加额外的排序条件即可。例如,如果您想按姓名、年龄和薪水(按顺序)进行排序,则应先添加姓名条件,然后是年龄条件,最后是薪水条件(例如if语句)。条件的顺序很重要,因为放置条件的顺序控制了排序逻辑应用的顺序。 - dcp

66

我认为你需要的是 thenBy.js:https://github.com/Teun/thenBy.js

它允许你使用标准的 Array.sort,但是采用 firstBy().thenBy().thenBy() 的格式来排序。

这里 有一个示例。


1
在处理大型数据集时要注意性能。每次调用 thenBy 方法时,所有数组项都会再次循环遍历。 - Ray Shan
12
那绝对不是这种情况。当你调用 thenBy() 时,它会构建一个封装前一个函数的新函数。在排序时,JavaScript 不会严格“循环遍历”项目,但它会多次调用您传递给它的函数。使用 thenBy 不会改变调用次数。有关性能考虑,请阅读:https://github.com/Teun/thenBy.js#a-word-on-performance - Teun D
4
我明白了,我的错误,感谢您考虑性能。或许可以加上一条关于使用新函数创建闭包时内存考虑的注释? - Ray Shan
如何在多个动态中使用它?或者在一个for循环中使用? - Hemil Patel
@Harry 如果你无法让它正常工作,请发布一个问题,说明你无法对其进行排序的示例,以便其他人也可以学习。很高兴能帮助你。https://github.com/Teun/thenBy.js/issues - Teun D

47

在许多字符串字段上进行排序的好方法是使用 localeCompare 方法和布尔运算符 ||

例如:

// Sorting record releases by name and then by title.
releases.sort((oldRelease, newRelease) => {
  const compareName = oldRelease.name.localeCompare(newRelease.name);
  const compareTitle = oldRelease.title.localeCompare(newRelease.title);

  return compareName || compareTitle;
})

如果您想要按更多的字段进行排序,您只需通过更多的布尔运算符将它们链接到返回语句即可。


事实上,你可以使用.reduce()来整理它。 - ekkis
然而,.localCompare() 返回 -1、0、1,所以我认为你的解决方案不适用于 || 运算符。 - ekkis
14
@ekkis,1和-1都是“真值”,因此这是一个非常优雅的解决方案。我只是做了这个:sortItems = (a,b) =>(a.distance - b.distance)||(a.name - b.name);对于我的不太挑剔的需求,它工作得很好。 - bstst
1
@bstst 你的方法更好,因为它只在必要时才评估 (a.name - b.name)。即使不需要,先创建变量也会做额外的工作。 - andi
4
没错,这段代码做了比必要更多的工作,但我只会在关键区域进行更改。对于对少量数据进行排序的代码来说,代码清晰度比性能更为重要。 - tbranyen

28

我发现需要按键以SQL样式混合升序和降序排序对象数组。

kennebec的解决方案帮助我实现了这一点:

Array.prototype.keySort = function(keys) {

keys = keys || {};

// via
// https://dev59.com/QXVD5IYBdhLWcg3wWaVh
var obLen = function(obj) {
    var size = 0, key;
    for (key in obj) {
        if (obj.hasOwnProperty(key))
            size++;
    }
    return size;
};

// avoiding using Object.keys because I guess did it have IE8 issues?
// else var obIx = function(obj, ix){ return Object.keys(obj)[ix]; } or
// whatever
var obIx = function(obj, ix) {
    var size = 0, key;
    for (key in obj) {
        if (obj.hasOwnProperty(key)) {
            if (size == ix)
                return key;
            size++;
        }
    }
    return false;
};

var keySort = function(a, b, d) {
    d = d !== null ? d : 1;
    // a = a.toLowerCase(); // this breaks numbers
    // b = b.toLowerCase();
    if (a == b)
        return 0;
    return a > b ? 1 * d : -1 * d;
};

var KL = obLen(keys);

if (!KL)
    return this.sort(keySort);

for ( var k in keys) {
    // asc unless desc or skip
    keys[k] = 
            keys[k] == 'desc' || keys[k] == -1  ? -1 
          : (keys[k] == 'skip' || keys[k] === 0 ? 0 
          : 1);
}

this.sort(function(a, b) {
    var sorted = 0, ix = 0;

    while (sorted === 0 && ix < KL) {
        var k = obIx(keys, ix);
        if (k) {
            var dir = keys[k];
            sorted = keySort(a[k], b[k], dir);
            ix++;
        }
    }
    return sorted;
});
return this;
};

示例用法:

var obja = [
  {USER:"bob",  SCORE:2000, TIME:32,    AGE:16, COUNTRY:"US"},
  {USER:"jane", SCORE:4000, TIME:35,    AGE:16, COUNTRY:"DE"},
  {USER:"tim",  SCORE:1000, TIME:30,    AGE:17, COUNTRY:"UK"},
  {USER:"mary", SCORE:1500, TIME:31,    AGE:19, COUNTRY:"PL"},
  {USER:"joe",  SCORE:2500, TIME:33,    AGE:18, COUNTRY:"US"},
  {USER:"sally",    SCORE:2000, TIME:30,    AGE:16, COUNTRY:"CA"},
  {USER:"yuri", SCORE:3000, TIME:34,    AGE:19, COUNTRY:"RU"},
  {USER:"anita",    SCORE:2500, TIME:32,    AGE:17, COUNTRY:"LV"},
  {USER:"mark", SCORE:2000, TIME:30,    AGE:18, COUNTRY:"DE"},
  {USER:"amy",  SCORE:1500, TIME:29,    AGE:19, COUNTRY:"UK"}
];

var sorto = {
  SCORE:"desc",TIME:"asc", AGE:"asc"
};

obja.keySort(sorto);
产生以下结果:
 0: {     USER: jane;     SCORE: 4000;    TIME: 35;       AGE: 16;    COUNTRY: DE;   }
 1: {     USER: yuri;     SCORE: 3000;    TIME: 34;       AGE: 19;    COUNTRY: RU;   }
 2: {     USER: anita;    SCORE: 2500;    TIME: 32;       AGE: 17;    COUNTRY: LV;   }
 3: {     USER: joe;      SCORE: 2500;    TIME: 33;       AGE: 18;    COUNTRY: US;   }
 4: {     USER: sally;    SCORE: 2000;    TIME: 30;       AGE: 16;    COUNTRY: CA;   }
 5: {     USER: mark;     SCORE: 2000;    TIME: 30;       AGE: 18;    COUNTRY: DE;   }
 6: {     USER: bob;      SCORE: 2000;    TIME: 32;       AGE: 16;    COUNTRY: US;   }
 7: {     USER: amy;      SCORE: 1500;    TIME: 29;       AGE: 19;    COUNTRY: UK;   }
 8: {     USER: mary;     SCORE: 1500;    TIME: 31;       AGE: 19;    COUNTRY: PL;   }
 9: {     USER: tim;      SCORE: 1000;    TIME: 30;       AGE: 17;    COUNTRY: UK;   }
 keySort: {  }

(使用来自这里的打印函数)

这里是一个jsbin示例

编辑:已经清理并发布在GitHub上作为mksort.js


17

这对各种大小的字母排序都很方便。 将您要按顺序排序的索引作为参数传递给它。

Array.prototype.deepSortAlpha= function(){
    var itm, L=arguments.length, order=arguments;

    var alphaSort= function(a, b){
        a= a.toLowerCase();
        b= b.toLowerCase();
        if(a== b) return 0;
        return a> b? 1:-1;
    }
    if(!L) return this.sort(alphaSort);

    this.sort(function(a, b){
        var tem= 0,  indx=0;
        while(tem==0 && indx<L){
            itm=order[indx];
            tem= alphaSort(a[itm], b[itm]); 
            indx+=1;        
        }
        return tem;
    });
    return this;
}

var arr= [[ "Nilesh","Karmshil"], ["Pranjal","Deka"], ["Susants","Ghosh"],
["Shiv","Shankar"], ["Javid","Ghosh"], ["Shaher","Banu"], ["Javid","Rashid"]];

arr.deepSortAlpha(1,0);

请问您从哪里收集到这些数据的? [[ "Nilesh","Karmshil"], ["Pranjal","Deka"], ["Susants","Ghosh"], ["Shiv","Shankar"], ["Javid","Ghosh"], ["Shaher","Banu"], ["Javid","Rashid"]]; - defau1t

15

我建议使用内置比较器并使用逻辑或||连接所需的排序顺序。

function customSort(a, b) {
    return a[3].localeCompare(b[3]) || a[1].localeCompare(b[1]);
}

工作示例:

var array = [
    [0, 'Aluminium', 0, 'Francis'],
    [1, 'Argon', 1, 'Ada'],
    [2, 'Brom', 2, 'John'],
    [3, 'Cadmium', 3, 'Marie'],
    [4, 'Fluor', 3, 'Marie'],
    [5, 'Gold', 1, 'Ada'],
    [6, 'Kupfer', 4, 'Ines'],
    [7, 'Krypton', 4, 'Joe'],
    [8, 'Sauerstoff', 3, 'Marie'],
    [9, 'Zink', 5, 'Max']
];

array.sort(function (a, b) {
    return a[3].localeCompare(b[3]) || a[1].localeCompare(b[1]);
});

document.write('<pre>');
array.forEach(function (a) {
    document.write(JSON.stringify(a) + '<br>');
});


这对我非常有帮助!也很容易理解。谢谢! - DanCue

8
你可以将这两个变量拼接成一个排序键,然后用它进行比较。
list.sort(function(a,b){
   var aCat = a.var1 + a.var2;
   var bCat = b.var1 + b.var2;
   return (aCat > bCat ? 1 : aCat < bCat ? -1 : 0);
});

@GustavoRodrigues 可能因为它太脆弱了。由于没有分隔符或其他区别,它会在某些输入键上无法按预期排序。考虑如果对于项目X,var1和var2是"foo"和"baz",而对于项目Y,var1是"foobar"。在排序时,X应该排在第一位,但在这种情况下,它会排在第二位。这个答案可以改进,但就目前而言,它并不安全。 - Peter Hansen

6
我发现了一个名为 multisort 的库。这是一个简单、强大且小巧的多重排序库。我需要根据动态排序条件对对象数组进行排序:

const criteria = ['name', 'speciality']
const data = [
  { name: 'Mike', speciality: 'JS', age: 22 },
  { name: 'Tom', speciality: 'Java', age: 30 },
  { name: 'Mike', speciality: 'PHP', age: 40 },
  { name: 'Abby', speciality: 'Design', age: 20 },
]

const sorted = multisort(data, criteria)

console.log(sorted)
<script src="https://cdn.rawgit.com/peterkhayes/multisort/master/multisort.js"></script>

这个库更加强大,这正是我的情况。试试看吧。


4

字符串拼接方法

您可以通过将数值附加到字符串中并比较字符串来简单地按多个值进行排序。为防止一个键超出另一个键,最好添加一个分割关键字。

示例

const arr = [ 
    { a: 1, b: 'a', c: 3 },
    { a: 2, b: 'a', c: 5 },
    { a: 1, b: 'b', c: 4 },
    { a: 2, b: 'a', c: 4 }
]


function sortBy (arr, keys, splitKeyChar='~') {
    return arr.sort((i1,i2) => {
        const sortStr1 = keys.reduce((str, key) => str + splitKeyChar+i1[key], '')
        const sortStr2 = keys.reduce((str, key) => str + splitKeyChar+i2[key], '')
        return sortStr1.localeCompare(sortStr2)
    })
}

console.log(sortBy(arr, ['a', 'b', 'c']))

递归方法

你也可以使用递归方法来实现这个目的。它比字符串追加方法更复杂,但它允许你在键级别上进行ASC和DESC操作。我会在每个部分评论,因为它有点更复杂。

有一些注释掉的测试,以展示并验证混合顺序和默认顺序的排序是否有效。

例子

const arr = [ 
    { a: 1, b: 'a', c: 3 },
    { a: 2, b: 'a', c: 5 },
    { a: 1, b: 'b', c: 4 },
    { a: 2, b: 'a', c: 4 }
]


function sortBy (arr, keys) {
    return arr.sort(function sort (i1,i2, sKeys=keys) {
        // Get order and key based on structure
        const compareKey = (sKeys[0].key) ? sKeys[0].key : sKeys[0];
        const order = sKeys[0].order || 'ASC'; // ASC || DESC
        // Calculate compare value and modify based on order
        let compareValue = i1[compareKey].toString().localeCompare(i2[compareKey].toString())
        compareValue = (order.toUpperCase() === 'DESC') ? compareValue * -1 : compareValue
        // See if the next key needs to be considered 
        const checkNextKey = compareValue === 0 && sKeys.length !== 1
        // Return compare value
        return (checkNextKey) ? sort(i1, i2, sKeys.slice(1)): compareValue;
    })
}

// console.log(sortBy(arr, ['a', 'b', 'c']))
console.log(sortBy(arr, [{key:'a',order:'desc'}, 'b', 'c']))
// console.log(sortBy(arr, ['a', 'b', {key:'c',order:'desc'}]))
// console.log(sortBy(arr, ['a', {key:'b',order:'desc'}, 'c']))
// console.log(sortBy(arr, [{key:'a',order:'asc'}, {key:'b',order:'desc'}, {key:'c',order:'desc'}]))

1
在我看来,正确的选项是。我会进行改进,使其能够使用键的升序|降序选项。此外,人们希望将其作为Array.prototype函数。 - Guilherme Ferreira
字符串方法是关于简单性的,我想不出一种简单的方法来包括升序和降序。因此,我添加了一个递归方法,允许您这样做。它有点更复杂,但有效。 - Michael Warner
将函数添加到数组原型中是一个好主意,但这是一个实现细节,与他人分享时会降低解决问题的重点。 - Michael Warner
到目前为止,我的方法是将每个属性的字符串进行本地比较并转换为00000000或1111111111,按相同顺序连接它们,然后再进行本地比较。在本地比较时,根据升序/降序和比较结果,我会反转000>111。最终,您将得到一个001101101001001字符串以进行本地比较。 - Guilherme Ferreira
这是一个很酷的方法!唯一需要说的是,有一些不必要的计算,因为你需要比较所有的值,无论你是否需要,但在大多数情况下,这并不重要。太棒了! - Michael Warner

3

试试这个:

t.sort( (a,b)=> a[3].localeCompare(b[3]) || a[1].localeCompare(b[1]) );

let t = [
    //[publicationID, publication_name, ownderID, owner_name ]
    [1, 'ZBC', 3, 'John Smith'],
    [2, 'FBC', 5, 'Mike Tyson'],
    [3, 'ABC', 7, 'Donald Duck'],
    [4, 'DBC', 1, 'Michael Jackson'],
    [5, 'XYZ', 2, 'Michael Jackson'],
    [6, 'BBC', 4, 'Michael Jackson'],
  ]; 
  
  // owner_name subarrray index = 3
  // publication_name subarrray index = 1

t.sort( (a,b)=> a[3].localeCompare(b[3]) || a[1].localeCompare(b[1]) );

console.log(t.join('\n'));

我假设你的数据存储在数组 let t = [ [publicationID, publication_name, ownderID, owner_name ], ... ] 中,其中 owner_name 的索引为 3,publication_name 的索引为 1。


太棒了!我必须滚动很长一段时间才能找到一个非常简单的问题的最简单解决方案。 - PauAI

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