下划线:sortBy() 基于多个属性

IT技术 javascript sorting underscore.js
2021-02-20 04:58:01

我正在尝试使用基于多个属性的对象对数组进行排序。即,如果两个对象之间的第一个属性相同,则应使用第二个属性来比较这两个对象。例如,考虑以下数组:

var patients = [
             [{name: 'John', roomNumber: 1, bedNumber: 1}],
             [{name: 'Lisa', roomNumber: 1, bedNumber: 2}],
             [{name: 'Chris', roomNumber: 2, bedNumber: 1}],
             [{name: 'Omar', roomNumber: 3, bedNumber: 1}]
               ];

roomNumber属性对这些进行排序,我将使用以下代码:

var sortedArray = _.sortBy(patients, function(patient) {
    return patient[0].roomNumber;
});

这工作正常,但我该如何进行才能正确排序“约翰”和“丽莎”?

6个回答

sortBy 说它是一种稳定的排序算法,因此您应该能够先按第二个属性排序,然后再按第一个属性排序,如下所示:

var sortedArray = _(patients).chain().sortBy(function(patient) {
    return patient[0].name;
}).sortBy(function(patient) {
    return patient[0].roomNumber;
}).value();

当第二个sortBy发现 John 和 Lisa 具有相同的房间号时,它将按照找到它们的顺序保留他们,第一个sortBy设置为“Lisa,John”。

可以在此处找到更简单的链式排序解决方案公平地说,看起来这篇博文是在给出这些答案之后写的,但是在尝试使用上面答案中的代码并失败后,它帮助我弄清楚了这一点。
2021-04-18 04:58:01
@ac_fire 这是该死链接的存档:archive.is/tiatQ
2021-04-22 04:58:01
您确定患者 [0].name 和患者 [1].roomNumber 应该有索引吗?患者不是数组...
2021-04-24 04:58:01
[0]分度器是必需的,因为在原来的实例中,patients是一个数组的数组。这也是为什么另一条评论中提到的博客文章中的“更简单的解决方案”在这里不起作用的原因。
2021-05-03 04:58:01
有一篇博客文章对此进行了扩展,其中包含有关对升序和降序属性进行排序的良好信息。
2021-05-08 04:58:01

这是我有时在这些情况下使用的一个技巧:以结果可排序的方式组合属性:

var sortedArray = _.sortBy(patients, function(patient) {
  return [patient[0].roomNumber, patient[0].name].join("_");
});

然而,正如我所说,这很hacky。要正确执行此操作,您可能希望实际使用核心 JavaScriptsort方法

patients.sort(function(x, y) {
  var roomX = x[0].roomNumber;
  var roomY = y[0].roomNumber;
  if (roomX !== roomY) {
    return compare(roomX, roomY);
  }
  return compare(x[0].name, y[0].name);
});

// General comparison function for convenience
function compare(x, y) {
  if (x === y) {
    return 0;
  }
  return x > y ? 1 : -1;
}

当然,会将您的数组排序到位。如果你想要一个排序的副本(就像_.sortBy会给你的),首先克隆数组:

function sortOutOfPlace(sequence, sorter) {
  var copy = _.clone(sequence);
  copy.sort(sorter);
  return copy;
}

出于无聊,我也为此编写了一个通用解决方案(按任意数量的键排序):看看.

此外,如何做compare的句柄值不属于原始值- undefinednull或素色的物体?
2021-04-24 04:58:01
仅供参考,只有当您确保数组中所有项目的每个值的 str 长度都相同时,此 hack 才有效。
2021-04-25 04:58:01
非常感谢这个解决方案最终使用了第二个,因为我的属性可以是字符串和数字。所以似乎没有一种简单的原生方式来对数组进行排序?
2021-04-28 04:58:01
指向您的通用解决方案的链接似乎已损坏(或者我可能无法通过我们的代理服务器访问它)。你能把它贴在这里吗?
2021-04-30 04:58:01
为什么没有 就return [patient[0].roomNumber, patient[0].name];足够了join
2021-05-08 04:58:01

我知道我迟到了,但我想为那些需要更清洁、更快速的解决方案的人添加这个,这些解决方案已经建议了。您可以按照最不重要的属性到最重要的属性的顺序链接 sortBy 调用。在下面的代码创建的排序患者一个新的数组名称RoomNumber从原来的阵列称为患者

var sortedPatients = _.chain(patients)
  .sortBy('Name')
  .sortBy('RoomNumber')
  .value();
第二种不会覆盖第一种吗?
2021-04-18 04:58:01
即使你迟到了,你仍然是正确的:) 谢谢!
2021-04-27 04:58:01
不错,很干净。
2021-04-29 04:58:01
不,这是一个“稳定”排序,因此它知道将第二个排序保留在第一个排序中,这意味着它不会干扰第一个排序中不同项目的顺序,同时在其中重新排序以显示第二个排序的结果。
2021-05-14 04:58:01

顺便说一句,您的患者初始化程序有点奇怪,不是吗?为什么不将这个变量初始化为这个 - 作为一个真正的对象数组- 你可以使用_.flatten()而不是作为单个对象的数组数组来完成,也许这是错字问题):

var patients = [
        {name: 'Omar', roomNumber: 3, bedNumber: 1},
        {name: 'John', roomNumber: 1, bedNumber: 1},
        {name: 'Chris', roomNumber: 2, bedNumber: 1},
        {name: 'Lisa', roomNumber: 1, bedNumber: 2},
        {name: 'Kiko', roomNumber: 1, bedNumber: 2}
        ];

我对清单进行了不同的排序,并将 Kiko 添加到 Lisa 的床上;只是为了好玩,看看会发生什么变化......

var sorted = _(patients).sortBy( 
                    function(patient){
                       return [patient.roomNumber, patient.bedNumber, patient.name];
                    });

检查排序,你会看到这个

[
{bedNumber: 1, name: "John", roomNumber: 1}, 
{bedNumber: 2, name: "Kiko", roomNumber: 1}, 
{bedNumber: 2, name: "Lisa", roomNumber: 1}, 
{bedNumber: 1, name: "Chris", roomNumber: 2}, 
{bedNumber: 1, name: "Omar", roomNumber: 3}
]

所以我的答案是:在你的回调函数中使用一个数组, 这与Dan Tao的答案非常相似,我只是忘记了 join(可能是因为我删除了唯一项的数组数组 :))
使用你的数据结构,然后它将是 :

var sorted = _(patients).chain()
                        .flatten()
                        .sortBy( function(patient){
                              return [patient.roomNumber, 
                                     patient.bedNumber, 
                                     patient.name];
                        })
                        .value();

测试负载会很有趣......

认真的,这就是答案
2021-04-28 04:58:01

这些答案都不是在排序中使用多个字段的通用方法的理想选择。上述所有方法都是低效的,因为它们要么需要对数组进行多次排序(这在足够大的列表上可能会减慢很多速度),要么会生成大量的垃圾对象,VM 需要清理这些对象(并最终减慢速度)程序关闭)。

这是一个快速、高效、轻松允许反向排序的解决方案,可以与underscore一起使用lodash,或直接与Array.sort

最重要的部分是compositeComparator方法,它接受一组比较器函数并返回一个新的复合比较器函数。

/**
 * Chains a comparator function to another comparator
 * and returns the result of the first comparator, unless
 * the first comparator returns 0, in which case the
 * result of the second comparator is used.
 */
function makeChainedComparator(first, next) {
  return function(a, b) {
    var result = first(a, b);
    if (result !== 0) return result;
    return next(a, b);
  }
}

/**
 * Given an array of comparators, returns a new comparator with
 * descending priority such that
 * the next comparator will only be used if the precending on returned
 * 0 (ie, found the two objects to be equal)
 *
 * Allows multiple sorts to be used simply. For example,
 * sort by column a, then sort by column b, then sort by column c
 */
function compositeComparator(comparators) {
  return comparators.reduceRight(function(memo, comparator) {
    return makeChainedComparator(comparator, memo);
  });
}

您还需要一个比较器函数来比较您希望排序的字段。naturalSort函数将创建一个给定特定字段的比较器。为反向排序编写一个比较器也很简单。

function naturalSort(field) {
  return function(a, b) {
    var c1 = a[field];
    var c2 = b[field];
    if (c1 > c2) return 1;
    if (c1 < c2) return -1;
    return 0;
  }
}

(到目前为止,所有代码都是可重用的,例如可以保存在实用程序module中)

接下来,您需要创建复合比较器。对于我们的示例,它看起来像这样:

var cmp = compositeComparator([naturalSort('roomNumber'), naturalSort('name')]);

这将按房间号排序,然后是名称。添加额外的排序条件是微不足道的,不会影响排序的性能。

var patients = [
 {name: 'John', roomNumber: 3, bedNumber: 1},
 {name: 'Omar', roomNumber: 2, bedNumber: 1},
 {name: 'Lisa', roomNumber: 2, bedNumber: 2},
 {name: 'Chris', roomNumber: 1, bedNumber: 1},
];

// Sort using the composite
patients.sort(cmp);

console.log(patients);

返回以下内容

[ { name: 'Chris', roomNumber: 1, bedNumber: 1 },
  { name: 'Lisa', roomNumber: 2, bedNumber: 2 },
  { name: 'Omar', roomNumber: 2, bedNumber: 1 },
  { name: 'John', roomNumber: 3, bedNumber: 1 } ]

我更喜欢这种方法的原因是它允许对任意数量的字段进行快速排序,不会产生大量垃圾或在排序中执行字符串连接,并且可以轻松使用,以便某些列反向排序,而顺序列使用自然种类。