Home > Blog > Secondary Sort For Array

Secondary sort for array

Sorting has always been one of the most commonly performed task for many of us especially those deal with large sets of data. In JavaScript, this is no exception even for web developers. Luckily enough, they do not have to implement their own sorting algorithm if the native sort method that provides out of the box is already good enough to get the job done.

Dealing with sorting with large sets of data such as an list of user records or shopping items, users usually want to do different kind of sorting to see which data tops the list. It is pretty easy to do sorting on a list of data with each containing its unique set of values in each field. In reality, there is a high chance that more than 1 item could have field which requires for sorting contains the exact same value. As such, it is difficult to do a proper sorting on those kind of data as we do not have enough information to perform sorting when the field values are the same.

What if we could actually do a secondary sorting based on another field such as the name or ID?

A piece of code is worth a million words

const productList = [
  {
    "price": 242,
    "productId": 973,
    "createdAt": "2017-02-20T13:08:55.799Z",
    "stock": 501
  },
  {
    "price": 331,
    "productId": 514,
    "createdAt": "1985-10-30T10:03:55.658Z",
    "stock": 875
  },
  {
    "price": 958,
    "productId": 953,
    "createdAt": "1992-12-15T10:07:58.691Z",
    "stock": 335
  },
  {
    "price": 547,
    "productId": 585,
    "createdAt": "1996-01-20T16:54:16.601Z",
    "stock": 591
  },
  {
    "price": 820,
    "productId": 420,
    "createdAt": "2005-02-01T15:31:01.768Z",
    "stock": 494
  },
  {
    "price": 945,
    "productId": 524,
    "createdAt": "2018-06-05T13:46:51.857Z",
    "stock": 76
  },
  {
    "price": 242,
    "productId": 144,
    "createdAt": "1993-11-20T13:14:22.462Z",
    "stock": 697
  },
  {
    "price": 331,
    "productId": 408,
    "createdAt": "1982-01-20T02:27:53.115Z",
    "stock": 18
  }
];

productList is a list of un-sorted products that contains 4 fields - price, productId, createdAt, and stock. Some of the products have the exact same price such as 242 and 331. Let see what the output will look like if we perform a sorting based on the field price.

function sortingFunction(a, b) {
  return a.price - b.price; /** Sorting ascendingly */
}

/** Sorting mutates the original array, doing a clone here to ensure original one stays untouched */
const clonedProductList = productList.slice();
clonedProductList.sort(sortingFunction);
console.log(clonedProductList);
// Sorted list:
// [
//   {
//     "price": 242,
//     "productId": 973,
//     "createdAt": "2017-02-20T13:08:55.799Z",
//     "stock": 501
//   },
//   {
//     "price": 242,
//     "productId": 144,
//     "createdAt": "1993-11-20T13:14:22.462Z",
//     "stock": 697
//   },
//   {
//     "price": 331,
//     "productId": 514,
//     "createdAt": "1985-10-30T10:03:55.658Z",
//     "stock": 875
//   },
//   {
//     "price": 331,
//     "productId": 408,
//     "createdAt": "1982-01-20T02:27:53.115Z",
//     "stock": 18
//   },
//   {
//     "price": 547,
//     "productId": 585,
//     "createdAt": "1996-01-20T16:54:16.601Z",
//     "stock": 591
//   },
//   {
//     "price": 820,
//     "productId": 420,
//     "createdAt": "2005-02-01T15:31:01.768Z",
//     "stock": 494
//   },
//   {
//     "price": 945,
//     "productId": 524,
//     "createdAt": "2018-06-05T13:46:51.857Z",
//     "stock": 76
//   },
//   {
//     "price": 958,
//     "productId": 953,
//     "createdAt": "1992-12-15T10:07:58.691Z",
//     "stock": 335
//   }
// ]

We can see that it is a stable sort as the list items with the same field value appear in the same order in sorted output. That looks good to me but this is not the purpose of today's topic. What we wanted to see is a secondary sort on list items that have the same field values.

function sortWithSecondarySort(a, b) {
  const primarySort = a.price - b.price; /** Sort by `price`, ascendingly */
  
  return primarySort || (a.productId - b.productId); /** Secondary sort by `productId`, ascendingly */
}

/** Sorting mutates the original array, doing a clone here to ensure original one stays untouched */
const clonedProductList = productList.slice();
clonedProductList.sort(sortingFunction);
console.log(clonedProductList);
// Sorted output with secondary sort:
// [
//   {
//     "price": 242,
//     "productId": 144,
//     "createdAt": "1993-11-20T13:14:22.462Z",
//     "stock": 697
//   },
//   {
//     "price": 242,
//     "productId": 973,
//     "createdAt": "2017-02-20T13:08:55.799Z",
//     "stock": 501
//   },
//   {
//     "price": 331,
//     "productId": 408,
//     "createdAt": "1982-01-20T02:27:53.115Z",
//     "stock": 18
//   },
//   {
//     "price": 331,
//     "productId": 514,
//     "createdAt": "1985-10-30T10:03:55.658Z",
//     "stock": 875
//   },
//   {
//     "price": 547,
//     "productId": 585,
//     "createdAt": "1996-01-20T16:54:16.601Z",
//     "stock": 591
//   },
//   {
//     "price": 820,
//     "productId": 420,
//     "createdAt": "2005-02-01T15:31:01.768Z",
//     "stock": 494
//   },
//   {
//     "price": 945,
//     "productId": 524,
//     "createdAt": "2018-06-05T13:46:51.857Z",
//     "stock": 76
//   },
//   {
//     "price": 958,
//     "productId": 953,
//     "createdAt": "1992-12-15T10:07:58.691Z",
//     "stock": 335
//   }
// ]

Great. You can now see the difference is that the first 2 items are no longer in the same order after performing a secondary sort on its productId field. A basic compare function basically needs to know which value is greater than or lesser than the other. Sometimes, it could return 0 which indicates no differences between the two items during comparison. 0 is one of the falsy values in JavaScript so we can make use of the fact to add a fallback using the || operator to add a secondary sort. With such technique, you can technically add even more comparison if the secondary sort returns an 0 as well.

Wrap up

Sorting array or any other large sets of sortable data is a common task for software engineers regardless of what kind of task you are dealing with.

It is important to note that sometimes a primary sort key is not always enough to fulfill certain use cases. You might need to ensure that you always have a secondary sort (and more, probably) that might come in handy under some circumstances, for instance, a stable sort algorithm might not always used even in a native sorting method a language or a system provides.

In JavaScript, we can make use of the fact that a falsy value, 0 will be returned for two items being exactly the same during comparison. The || operator allows fallback to the next expression as shown in the example above.

That is it. Have a wonderful day ahead and I'll see you in the upcoming blog post. Peace! ✌️