Solving JavaScript sorting once and for all

Introduction

Many JavaScript developers have had the experience of sorting data on the client side. Unfortunately, the existing libraries have minor flaws. But these shortcomings add up and limit how programmers think about sorting. To overcome these limitations, let's look at sorting in different languages. Armed with this knowledge, we will be able to choose the most convenient and rigorous interface.





How it all started

One fine summer day, on a project with AngularJS, I was tasked with adding a sort function to a table. In this case, there can be several criteria for sorting at once, and the direction for each criterion can be independent.





List of requirements:





  • use multiple expressions as a key for sorting





  • the ability to specify the direction of sorting independently for each of the keys





  • the ability to sort strings case insensitive and locale sensitive





  • sorting stability





What does AngularJS offer us for sorting? filter: orderBy documentation





{{ orderBy_expression | orderBy : expression : reverse : comparator }}
$filter('orderBy')(collection, expression, reverse, comparator)
Example:
<tr ng-repeat="friend in friends | orderBy:'-age'">...</tr>
      
      



. , -



, , . , . , ? , , JS, . AngularJS, eval



, . AngularJS JS. , TypeScript . expression



, , . , . , .





, β€” reverse



. ! , . , .





β€” comparator



, . , comparator



. localeSensitiveComparator



.





, , TypeScript ? JavaScript , , -.





lodash

lodash



, _.sortBy



, .





var users = [
    { 'user': 'fred',   'age': 48 },
    { 'user': 'barney', 'age': 36 },
    { 'user': 'fred',   'age': 40 },
    { 'user': 'barney', 'age': 34 }
];
 
_.sortBy(users, [(o) => o.user]);
// => objects for [['barney', 36], ['barney', 34], ['fred', 48], ['fred', 40]]
 
_.sortBy(users, ['user', 'age']);
// => objects for [['barney', 34], ['barney', 36], ['fred', 40], ['fred', 48]]
      
      



, , ? - lodash



, _.orderBy



.





This method is like _.sortBy



except that it allows specifying the sort orders of the iteratees to sort by.





, . :





// Sort by `user` in ascending order and by `age` in descending order.
_.orderBy(users, ['user', 'age'], ['asc', 'desc']);
// => objects for [['barney', 36], ['barney', 34], ['fred', 48], ['fred', 40]]
      
      



β€” , . , , .





, _.orderBy



.





Array#sort

JavaScript, Array



sort



. . . , . , , β€” . . , .





items.sort(function(a, b) {
    if (b.salary < a.salary) {
     	 	return -1;
    }
    if (b.salary > a.salary) {
      	return 1;
    }
    if (a.id < b.id) {
      	return -1;
    }
    if (a.id > b.id) {
      	return 1;
    }

    return 0;
});
//  ,     `lodash`
//      :
lodash.orderBy(items, ['salary', 'id'], ['desc', 'asc']);
      
      



, , . , .





, .





SQL / SEQUEL

, , . , , ! , SQL.





, SQL 1976 , . , , ?





SELECT EMPNO,NAME,SAL
FROM EMP
WHERE DNO 50
ORDER BY EMPNO
      
      



SQL , :





SELECT EMPNO,NAME,SAL
FROM EMP
ORDER BY SAL DESC, EMPNO ASC
      
      



. , SQL .





Haskell Rust

Haskell Rust :





Haskell sortOn:





import Data.Ord (Down)
import Data.Sort (sortOn)
sortOn (\employee -> (Down (salary employee), employee_id employee)) employees
      
      



Rust slice::sort_by_key:





use std::cmp::{Reverse};
slice.sort_by_key(|employee| (Reverse(employee.salary), employee.id))
      
      



, β€” (newtype) Down Reverse, . , .





Python

Python list.sort sorted, key



.





cmp, .





sorted(employees, key=lambda employee: (employee.salary, employee.id))
      
      



Python, Haskell Rust, , . , , - . , , .





from ord_reverse import Reverse
sorted(employees, key=lambda employee: (Reverse(employee.salary), employee.id))
      
      



Java C#

Java Arrays.sort



Comparator



( ). Comparator



, , thenComparing



. reversed



.





Comparator<Employee> comparator =
  Comparator.comparing(Employee.getSalary).reversed()
    .thenComparing(Employee.getId);
Arrays.sort(array, comparator);

      
      



β€” . ORDER BY SALARY ASC, ID DESC



:





//  1,   ,   
Comparator<Employee> comparator =
  Comparator.comparing(Employee.getSalary)
    .thenComparing(Comparator.comparing(Employee.getId).reversed());
//  2,   .    
//     .
Comparator<Employee> comparator =
  Comparator.comparing(Employee.getSalary).reversed()
    .thenComparing(Employee.getId).reversed();

      
      



LINQ Query, SQL, C# Enumerable.OrderBy



Enumerable.OrderByDescending



, Enumerable.ThenBy



Enumerable.ThenByDescending



.





IEnumerable<Employee> query =
    employees
    .OrderByDescending(employee => employee.Salary)
    .ThenBy(employee => employee.Id);

      
      



Java . β€” , : IEnumerable



β€” 4 , 1 Haskell/Rust/Python. C# , .





, Java, C# . , .





C C++

C qsort:





#include <stdlib.h>
int cmp_employee(const void *p1, const void *p2)
{
      const employee *a = (employee*)p1;
      const employee *b = (employee*)p2;

      if (b->salary < a->salary) {
        	return -1;
      }
      if (b->salary > a->salary) {
        	return 1;
      }

      if (a->id < b->id) {
        	return -1;
      }
      if (a->id > b->id) {
        	return 1;
      }

    	return 0;
  }

  /* ... */
  qsort(employees, count, sizeof(employee), cmp_employee);
      
      



C++ std::sort:





#include <algorithm>
/* ... */
std::sort(employees.begin(), employees.end(), [](const employee &a, const employee &b) {
  if (b->salary < a->salary) {
    return true;
  }
  if (b->salary > a->salary) {
    return false;
  }
  return a->id < b->id;
});
      
      



C, C++ . C ( , ), C++ β€” . - , . , C++ .





C C++   . Array#sort



, , .





, Haskell Rust. JavaScript?





JS , , JS . , . ?





sortBy(array, (employee) => [{ reverse: employee.salary }, employee.id]);
      
      



JavaScript

, . JavaScript , , Trait



- typeclass



-, , .





:





  1. null



    . Maybe



    Option



    .





  2. , .





  3. NaN



    .





  4. , , BigInt JavaScript.





  5. , .





  6. { reverse: xxx }



    , xxx



    . Down



    / Reverse







  7. { localeCompare: sss, collator: ccc }



    , sss



    ccc



    . .





  8. .





- , . .





, β€” : better-cmp





: X?

  • orderBy: "Inspired by Angular's orderBy filter", . .





  • thenby: , Java , - .





  • multisort: ΰ² _ΰ² 





    if (/[^\(\r\n]*\([^\(\r\n]*\)$/.test(nextKey)) {
        var indexOfOpenParenthesis = nextKey.indexOf("(");
        var args = JSON.parse("[" + nextKey.slice(indexOfOpenParenthesis+1, -1) + "]");
        nextKey = nextKey.slice(0, indexOfOpenParenthesis);
    }
          
          



  • , .





  • .





  • " " JavaScript .





  • The best JavaScript solution I could do is now embodied in the better-cmp library available on npm.








All Articles