Underscore: sortBy() based on multiple attributes

后端 未结 11 1102
隐瞒了意图╮
隐瞒了意图╮ 2020-11-27 11:09

I am trying to sort an array with objects based on multiple attributes. I.e if the first attribute is the same between two objects a second attribute should be used to comap

相关标签:
11条回答
  • 2020-11-27 11:59

    You could concatenate the properties you want to sort by in the iterator:

    return [patient[0].roomNumber,patient[0].name].join('|');
    

    or something equivalent.

    NOTE: Since you are converting the numeric attribute roomNumber to a string, you would have to do something if you had room numbers > 10. Otherwise 11 will come before 2. You can pad with leading zeroes to solve the problem, i.e. 01 instead of 1.

    0 讨论(0)
  • 2020-11-27 12:01

    I think you'd better use _.orderBy instead of sortBy:

    _.orderBy(patients, ['name', 'roomNumber'], ['asc', 'desc'])
    
    0 讨论(0)
  • 2020-11-27 12:01

    If you happen to be using Angular, you can use its number filter in the html file rather than adding any JS or CSS handlers. For example:

      No fractions: <span>{{val | number:0}}</span><br>
    

    In that example, if val = 1234567, it will be displayed as

      No fractions: 1,234,567
    

    Example and further guidance at: https://docs.angularjs.org/api/ng/filter/number

    0 讨论(0)
  • 2020-11-27 12:04

    None of these answers are ideal as a general purpose method for using multiple fields in a sort. All of the approaches above are inefficient as they either require sorting the array multiple times (which, on a large enough list could slow things down a lot) or they generate huge amounts of garbage objects that the VM will need to cleanup (and ultimately slowing the program down).

    Here's a solution that is fast, efficient, easily allows reverse sorting, and can be used with underscore or lodash, or directly with Array.sort

    The most important part is the compositeComparator method, which takes an array of comparator functions and returns a new composite comparator function.

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

    You'll also need a comparator function for comparing the fields you wish to sort on. The naturalSort function will create a comparator given a particular field. Writing a comparator for reverse sorting is trivial too.

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

    (All the code so far is reusable and could be kept in utility module, for example)

    Next, you need to create the composite comparator. For our example, it would look like this:

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

    This will sort by room number, followed by name. Adding additional sort criteria is trivial and does not affect the performance of the sort.

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

    Returns the following

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

    The reason I prefer this method is that it allows fast sorting on an arbitrary number of fields, does not generate a lot of garbage or perform string concatenation inside the sort and can easily be used so that some columns are reverse sorted while order columns use natural sort.

    0 讨论(0)
  • 2020-11-27 12:07

    I know I'm late to the party, but I wanted to add this for those in need of a clean-er and quick-er solution that those already suggested. You can chain sortBy calls in order of least important property to most important property. In the code below I create a new array of patients sorted by Name within RoomNumber from the original array called patients.

    var sortedPatients = _.chain(patients)
      .sortBy('Name')
      .sortBy('RoomNumber')
      .value();
    
    0 讨论(0)
提交回复
热议问题