Javascript custom array sorting functions


Difficulty

One of the most useful features that many languages offer is the possibility of sorting arrays and lists of elements in the most disparate ways. Let’s create our array sorting functions.
Unlike more typed languages, in the case of Javascript arrays can have profoundly different types of elements, including primitives and objects.

Let’s start from the simple case, the one in which we have to sort a list of primitive numbers or strings. In this case we just need to run the method sort() on that array and we will have the expected result.

const simpleDatas = [5, 2, 7, 1];
simpleDatas.sort(); // [1, 2, 5, 7]

const simpleStringDatas = ['alpha', 'gamma', 'theta', 'beta'];
simpleStringDatas.sort(); // ["alpha", "beta", "gamma", "theta"]


For reverse sorting we can simply reverse the array with the reverse() method.

simpleStringDatas.sort().reverse(); // ["theta", "gamma", "beta", "alpha"]


Array sorting functions with objects

Now let’s consider slightly more complicated cases, those in which we have an array of complex objects. For example let’s consider objects with properties that can be either numbers or strings or even dates.
To do this we must establish the comparator that the sort method should have. We can do this including an arrow function as a parameter within the method itself.

let datas = [
  { id: 5, city: 'Rome', zip: '75201', when: new Date('1995-01-17T01:24:00') },
  { id: 4, city: 'New York', zip: 11, when: new Date('1991-12-17T03:51:00') },
  { id: 3, city: 'Dubai', zip: 'v010', when: new Date('1998-05-19T07:03:00') },
  { id: 18, city: 'San Diego', zip: 324, when: new Date('1998-12-17T08:24:00') }
];


The function we will include will have 2 parameters as input (usually defined a and b) and will return a number that can be positive or negative. The number will be positive if a is greater than b, negative if b is greater than a.

(a, b) => { return a - b }

In the case of comparisons between numbers or strings, the argument is quite easy because it is enough to compare a to b.

// Number sort:
datas.sort((a, b) => a.id - b.id);
// [{3}, {4}, {5}, {18}]

// String sort:
datas.sort((a, b) => a.city - b.city);
// [{'Dubai'}, {'New York'}, {'Rome'}, {'San Diego'}]


Sort with Date properties

In the case of dates it must be considered that they are objects, which can therefore be null. In our case we arbitrarily decide that null dates will be pushed to the bottom. Other than that, we can compare dates in Javascript in the same way as strings and numbers.

datas.sort((a, b) => a.when && b.when // if all valued...
    ? a.when - b.when // ...sort...
    : !a.when ? 1 : -1 // ...else, without date, got to the bottom.
    );

With Typescript you have to add getTime() method to no have this error:

TS2362: The left-hand side of an arithmetic operation must be of type 'any', 'number', 'bigint' or an enum type
datas.sort((a, b) => a.when && b.when // if all valued...
    ? a.when.getTime() - b.when.getTime() // ...sort...
    : !a.when ? 1 : -1 // ...else, without date, got to the bottom.
    );


Double sort!

Let us now think of the most complicated case that could ever happen to us.
A sort based not on the alphanumeric order of the string properties contained in objects. Instead we decide to sort according to their arbitrary weight, that we decide according to unspecified canons.
As a further difficulty, we insert a further sort that acts secondarily to the first. It will orders further based on dates. In this case that is, if the date of A is later than the date of B, then it will be placed after. In the event that one of the two dates is null, it will be inserted later.
Here is an example of what our array might look like to sort. In our example we have to sort by task status and by date.

let complexDatas = [
  { id: 5, task: 'Task G',  status: 'DI', when: new Date('1995-01-17T01:24:00') },
  { id: 4, task: 'Task U',  status: 4, when: new Date('1991-12-17T03:51:00') },
  { id: 3, task: 'Task L',  status: 'DE', when: new Date('1998-05-19T07:03:00') },
  { id: 18, task: 'Task P', status: 'A', when: new Date('1998-12-17T08:24:00') },
  { id: 18, task: 'Task 1', status: 'E', when: new Date('1998-12-17T08:24:00') },
  { id: 12, task: 'Task P', status: 'A', when: new Date('1987-11-07T08:24:00') },
  { id: 11, task: 'Task P', status: 'A', when: null },
];


To obtain the desired result, we first need to establish which objects to order lower and which ones to order higher. We can do this through a “weight” system. To explain it in other words, the objects that weigh more will be pushed down, while the objects that weigh less will be higher.
To do this, we create a dictionary that maps the various status with respect to the weights they must have.

const Status = {
  ACTIVE: { label: 'Active', color: 'success', weight: 1 },
  EXPIRED: { label: 'Expired', color: 'error', weight: 2 },
  DELETED: { label: 'Deleted', color: 'error', weight: 3 },
  DISABLED: { label: 'Disabled', color: 'error', weight: 4 },
  UNKNOWN: { label: 'Unknown', color: 'disabled', weight: 5 },
};


Once we have established what weight each status should have, let’s create a method that transforms a generic string into our status property, with its weight. In this way we can also attribute further properties to the status, such as color or a specific label.

const getStatus = (label = 'a') => {
  label = typeof label === 'string' ? label.toUpperCase() : label;
  switch (label) {
    case 'A': return Status.ACTIVE;
    case 'E': return Status.EXPIRED;
    case 'DE': return Status.DELETED;
    case 'DI': return Status.DISABLED;
    default: return Status.UNKNOWN;
  }
};


Our sort method must therefore expect to use status in the object format and not in the initial string form for sorting.
In our case it will decide first if one object goes on top of the other based on the weight of the status and, if two objects have the same state, it will decide based on the date.

  complexDatas.sort((a, b) => {
    const as = getStatus(a.status);
    const bs = getStatus(b.status);
    if (as.weight > bs.weight) {
      return 1;
    } else if (as.weight < bs.weight) {
      return -1;
    }
    return a.when && b.when // if all valued...
      ? a.when.getTime() - b.when.getTime() // ...sort...
      : !a.when
      ? 1
      : -1; // ...else, without date, got to the bottom.
  });


We can do the same thing using the typescript (and with Angular / React). In this case, instead of scattered methods we can use an Enum. We rely on it for the transformation of generic strings into status.
Enums in Typescript are not powerful enough. To make up for it we will use the singleton pattern on a generic class. It will allow us to create the same effect as an Enum (as it would be in Java).

export class Status {
  static readonly ACTIVE = new Status('Active', 'success', 1);
  static readonly EXPIRED = new Status('Expired', 'error', 2);
  static readonly DELETED = new Status('Deleted', 'error', 3);
  static readonly DISABLED = new Status('Disabled', 'error', 4);
  static readonly UNKNOWN = new Status('Unknown', 'disabled', 5);

  // private to disallow creating other instances of this type
  private constructor(
    public readonly label: string,
    public readonly color: string,
    public readonly weight: number
  ) {}

  /**s
   * Return the state type
   * @param label | default: active
   */
  public static get(label: string | number = 'active') {
    label = typeof label === 'string' ? label.toUpperCase() : label;
    switch (label) {
      case 'A':
        return Status.ACTIVE;
      case 'E':
        return Status.EXPIRED;
      case 'DE':
        return Status.DELETED;
      case 'DI':
        return Status.DISABLED;
      default:
        return Status.UNKNOWN;
    }
  }

  toString() {
    return this.label;
  }
}


We will use the same function used for javascript. The one exception is that the mapping from string to object status will take place with the Enum method.

complexDatas.sort((a, b) => {
    const as = Status.get(a.status);
    const bs = Status.get(b.status);
    if (as.weight > bs.weight) {
      return 1;
    } else if (as.weight < bs.weight) {
      return -1;
    }
    return (a.when && b.when) // if all valued...
      ? a.when.getTime() - b.when.getTime() // ...sort...
      : (!a.when ? 1 : -1 ); // ...else, without date, got to the bottom.
  }
);


Here are the complete and working examples, one for javascript and one for typescript, with custom array sorting functions:


That’s all.
Try it at home!

0
Be the first one to like this.
Please wait...

One thought on...
Leave a Reply

Thanks for choosing to leave a comment.
Please keep in mind that all comments are moderated according to our comment policy, and your email address will NOT be published.
Please do NOT use keywords in the name field. Let's have a personal and meaningful conversation.