how to sort array by nested properties

孤街醉人 提交于 2019-12-02 12:54:14

In general, sorting by multiple criteria is done like this:

theArray.sort(function(left, right) {
    var result = /*...compare first criterion*/;
    if (result == 0) {
        result = /*...compare second criterion*/;
        if (result == 0) {
            result = /*...compare third criterion*/;
            // ...and so on...
        }
    }
    return result;
});

...where "compare X criterion" results in a negative number if left should come before right, 0 if they're the same for that criterion, and a positive number if right should come before left.

That works because we only need to evaluate our second criterion if the entries are "the same" according to the first, only need to look at the third if the first and second are "the same," etc.

That's for a hardcoded series. It can easily be adapted for a series defined by an array of comparisons to make (using a loop that breaks when result is not 0).

Simple example of the hardcoded version:

var array = [
  {a: 4, b: 3, c: 5},
  {a: 1, b: 2, c: 7},
  {a: 4, b: 4, c: 4},
  {a: 4, b: 4, c: 2},
  {a: 4, b: 4, c: 6},
  {a: 2, b: 8, c: 9},
  {a: 2, b: 9, c: 8}
];
show("Unsorted", array);
array.sort(function(left, right) {
  var result = left.a - right.a;
  if (result == 0) {
    result = left.b - right.b;
    if (result == 0) {
      result = left.c - right.c;
    }
  }
  return result;
});
show("Sorted", array);

function show(label, a) {
  console.log(
    label,
    a.map(function(entry) {
      return "a:" + entry.a +
             ",b:" + entry.b +
             ",c:" + entry.c;
    })
  );
}
.as-console-wrapper {
  max-height: 100% !important;
}

You could define your compare functions in a separate object, and reference the one your need when you call sort:

Here is a snippet that does that:

const array = [{"id":248439,"name":"Cross Creek Ranch/Creek Cove","surveyStatus":{"territoryName":"Fulshear","subdivisionName":"Cross Creek Ranch/Creek Cove","subdivisionId":248439,"dateTimeAdded":null,"surveyStatus":"0"}},{"id":248545,"name":"Lakes of Bella Terra/Via Verdone","surveyStatus":{"territoryName":"Fulshear","subdivisionName":"Lakes of Bella Terra/Via Verdone","subdivisionId":248545,"dateTimeAdded":null,"surveyStatus":"0"}},{"id":248546,"name":"Lakes of Bella Terra/Via Moderna","surveyStatus":{"territoryName":"Fulshear","dateTimeAdded":"2017-03-13 14:24:24.312","lng":-95.78459389953542,"userId":"6e77831f-9be5-41a4-8135-d961a94ef917","subdivisionId":"248546","surveyStatus":"2","territoryId":"4921","userName":"Michelle Artis","marketId":"13","subdivisionName":"Lakes of Bella Terra/Via Moderna","dateTimeUploaded":"2017-03-13 14:24:24.316","lat":29.68844027643332}},{"id":248547,"name":"Lakes of Bella Terra/Via Privato","surveyStatus":{"territoryName":"Fulshear","subdivisionName":"Lakes of Bella Terra/Via Privato","subdivisionId":248547,"dateTimeAdded":null,"surveyStatus":"0"}},{"id":248548,"name":"Lakes of Bella Terra/Mirandola","surveyStatus":{"territoryName":"Fulshear","subdivisionName":"Lakes of Bella Terra/Mirandola","subdivisionId":248548,"dateTimeAdded":null,"surveyStatus":"0"}},{"id":248549,"name":"Lakes of Bella Terra/La Bella Cortile","surveyStatus":{"territoryName":"Fulshear","dateTimeAdded":"2017-03-13 14:38:22.958","lng":-95.78834879221002,"userId":"6e77831f-9be5-41a4-8135-d961a94ef917","subdivisionId":"248549","surveyStatus":"2","territoryId":"4921","userName":"Michelle Artis","marketId":"13","subdivisionName":"Lakes of Bella Terra/La Bella Cortile","dateTimeUploaded":"2017-03-13 14:38:22.964","lat":29.69532227612072}},{"id":248838,"name":"Cross Creek Ranch/Pond","surveyStatus":{"territoryName":"Fulshear","dateTimeAdded":"2017-03-14 12:12:20.408","lng":-95.8761960827707,"userId":"6e77831f-9be5-41a4-8135-d961a94ef917","subdivisionId":"248838","surveyStatus":"1","territoryId":"4921","userName":"Michelle Artis","marketId":"13","subdivisionName":"Cross Creek Ranch/Pond","dateTimeUploaded":"2017-03-14 12:12:20.416","lat":29.70182981810505}},{"id":249626,"name":"Cross Creek Ranch/Legacy-Herons Lake","surveyStatus":{"territoryName":"Fulshear","dateTimeAdded":"2017-03-14 13:11:24.276","lng":-95.8625899069904,"userId":"6e77831f-9be5-41a4-8135-d961a94ef917","subdivisionId":"249626","surveyStatus":"2","territoryId":"4921","userName":"Michelle Artis","marketId":"13","subdivisionName":"Cross Creek Ranch/Legacy-Herons Lake","dateTimeUploaded":"2017-03-14 13:11:24.282","lat":29.70904039221789}},{"id":249727,"name":"Fulshear Run","surveyStatus":{"territoryName":"Fulshear","dateTimeAdded":"2017-03-14 11:49:22.765","lng":-95.8850889467596,"userId":"6e77831f-9be5-41a4-8135-d961a94ef917","subdivisionId":"249727","surveyStatus":"2","territoryId":"4921","userName":"Michelle Artis","marketId":"13","subdivisionName":"Fulshear Run","dateTimeUploaded":"2017-03-14 11:49:22.772","lat":29.6824066434332}},{"id":249739,"name":"Lakes of Bella Terra/Cittanova","surveyStatus":{"territoryName":"Fulshear","dateTimeAdded":"2017-03-13 15:56:25.460","lng":-95.78473585666696,"userId":"6e77831f-9be5-41a4-8135-d961a94ef917","subdivisionId":"249739","surveyStatus":"2","territoryId":"4921","userName":"Michelle Artis","marketId":"13","subdivisionName":"Lakes of Bella Terra/Cittanova","dateTimeUploaded":"2017-03-13 15:56:25.467","lat":29.69387132677221}},{"id":249883,"name":"Lakes of Bella Terra/Vita Bella","surveyStatus":{"territoryName":"Fulshear","dateTimeAdded":"2017-03-13 15:54:04.856","lng":-95.78164947228113,"userId":"6e77831f-9be5-41a4-8135-d961a94ef917","subdivisionId":"249883","surveyStatus":"1","territoryId":"4921","userName":"Michelle Artis","marketId":"13","subdivisionName":"Lakes of Bella Terra/Vita Bella","dateTimeUploaded":"2017-03-13 15:54:04.864","lat":29.69463965392643}},{"id":249884,"name":"Lakes of Bella Terra/Valencia","surveyStatus":{"territoryName":"Fulshear","dateTimeAdded":"2017-03-13 14:32:35.471","lng":-95.78839095318297,"userId":"6e77831f-9be5-41a4-8135-d961a94ef917","subdivisionId":"249884","surveyStatus":"2","territoryId":"4921","userName":"Michelle Artis","marketId":"13","subdivisionName":"Lakes of Bella Terra/Valencia","dateTimeUploaded":"2017-03-13 14:32:35.477","lat":29.69075137286418}},{"id":249885,"name":"Lakes of Bella Terra/Porte Toscana","surveyStatus":{"territoryName":"Fulshear","dateTimeAdded":"2017-03-13 14:10:33.875","lng":-95.79300477178376,"userId":"6e77831f-9be5-41a4-8135-d961a94ef917","subdivisionId":"249885","surveyStatus":"1","territoryId":"4921","userName":"Michelle Artis","marketId":"13","subdivisionName":"Lakes of Bella Terra/Porte Toscana","dateTimeUploaded":"2017-03-13 14:10:33.882","lat":29.68849873638683}},{"id":249920,"name":"Cross Creek Ranch/The Falls","surveyStatus":{"territoryName":"Fulshear","dateTimeAdded":"2017-03-14 15:07:57.054","lng":-95.86257892669724,"userId":"6e77831f-9be5-41a4-8135-d961a94ef917","subdivisionId":"249920","surveyStatus":"2","territoryId":"4921","userName":"Michelle Artis","marketId":"13","subdivisionName":"Cross Creek Ranch/The Falls","dateTimeUploaded":"2017-03-14 15:07:57.060","lat":29.73065241708395}},{"id":249941,"name":"Cross Creek Ranch/Heights","surveyStatus":{"territoryName":"Fulshear","dateTimeAdded":"2017-03-14 13:38:52.380","lng":-95.85420054392503,"userId":"6e77831f-9be5-41a4-8135-d961a94ef917","subdivisionId":"249941","surveyStatus":"2","territoryId":"4921","userName":"Michelle Artis","marketId":"13","subdivisionName":"Cross Creek Ranch/Heights","dateTimeUploaded":"2017-03-14 13:38:52.385","lat":29.71784273165252}}];

const populate = (array) => {
    const rows = [];
    for (let i = 0; i < array.length; i++) {
        rows.push("<tr>",
                  "<td>",array[i].surveyStatus.subdivisionName,"</td>",
                  "<td>",array[i].surveyStatus.dateTimeAdded,"</td>",
                  "<td>",array[i].surveyStatus.surveyStatus,"</td>",
                  "</tr>");
    }
    document.getElementById("box").innerHTML = "<table border='1|1'>" +
        rows.join("") + "</table>";
}

const compare = {
    dateTimeAdded: (a, b) => // keep null at the end
        (Date.parse(a.surveyStatus.dateTimeAdded) || Infinity) - 
        (Date.parse(b.surveyStatus.dateTimeAdded) || Infinity),
    "-dateTimeAdded": (a, b) => // keep null at the end
        (Date.parse(b.surveyStatus.dateTimeAdded) || 0) - 
        (Date.parse(a.surveyStatus.dateTimeAdded) || 0),
    inProgress: (a, b) => b.surveyStatus.surveyStatus - a.surveyStatus.surveyStatus,
    notStarted: (a, b) => a.surveyStatus.surveyStatus - b.surveyStatus.surveyStatus,
    complete: (a, b) => // Give precedence to status 1
        (b.surveyStatus.surveyStatus + 1) % 3 - (a.surveyStatus.surveyStatus + 1) % 3,
};

const sortTable = (option, array) => populate(array.sort(compare[option]));

const sort = document.getElementById('sort');
sort.addEventListener('change', e => sortTable(sort.value, array) );
sortTable(sort.value, array); // sort table at page load
<select id="sort">
    <option value="complete">Sort Completed First</option>
    <option value="inProgress">Sort In-Progress First</option>
    <option value="notStarted">Sort Not-Started First</option>
    <option value="dateTimeAdded">Sort Added Asc</option>
    <option value="-dateTimeAdded">Sort Added Desc</option>
</select>
<div id= "box"></div>

Note that sort provides a stable sort in most browsers, meaning that if you first sort by date, and then by a status, the records will still be sorted by date within the same status group.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!