skip to Main Content

I have an array that contains negative numbers, empty string '', Zero 0 and positive numbers. I want to sort the array so that empty string '' always shows at the end.

[-1, 2, '', -2, 0, 4, '', 1,5,0, -5, 7, 11, '',3]

and expected output

[-5, -2, -1, 0, 0, 1, 2, 3, 4, 5, 7, 11, '', '', '']

4

Answers


  1. you can write a custom sort function so that empty strings are moved to the back of the array
    [...arr] if you don’t want to mutate your original array since sort mutates original array.

    const arr = [-1, 2, '', -2, 0, 4, '', 1,5,0, -5, 7, 11, '',3];
    
    const sorted = [...arr].sort((a, b) => {
      if (a === '') return 1;   //a after b
      if (b === '') return -1;  //b after a
      return a - b;             //number sorting
    });
    
    //or shorten using ternary operator
    //const sorted = [...arr].sort((a, b) => a === '' ? 1 : b === '' ? -1 : a - b);
    
    console.log(sorted);
    Login or Signup to reply.
  2. You can prioritize the comparison with the empty string in the sorting condition.

    let arr = [-1, 2, '', -2, 0, 4, '', 1,5,0, -5, 7, 11, '',3];
    arr.sort((a, b) => (a === '') - (b === '') || a - b);
    console.log(arr);
    Login or Signup to reply.
  3. If b is empty, place it in front; but if a is empty, place it at the end.

    Where both a and b are non-empty, perform a standard a - b return.

    const arr = [-1, 2, '', -2, 0, 4, '', 1, 5, 0, -5, 7, 11, '', 3];
    
    arr.sort((a, b) => {
      if (b === '') return -1;
      if (a === '') return 1;
      return a - b;
    });
    
    console.log(JSON.stringify(arr));

    If you want to always keep the empty values at the end, you need to track sorting direction:

    const
      DIRECTION_ASC = 1,
      DIRECTION_DESC = -1,
      customSort = (direction = DIRECTION_ASC) => {
        const dir = direction === DIRECTION_DESC ? -1 : 1;
        return (a, b) => {
          if (b === '') return -1;
          if (a === '') return 1;
          return (a - b) * dir;
        };
      };
    
    const
      custSortAsc = customSort(),
      custSortDesc = customSort(DIRECTION_DESC),
      sortTextJsonArr = (textarea, sorter) => {
        textarea.value = JSON.stringify(
            JSON.parse(textarea.value.replace(/'/g, '"'))
              .sort(sorter))
          .replace(/"/g, "'")
          .replace(/,/g, ', ');
      },
      sortAsc = (selector) =>
        sortTextJsonArr(document.querySelector(selector), custSortAsc),
      sortDesc = (selector) =>
        sortTextJsonArr(document.querySelector(selector), custSortDesc);
    body, textarea { text-align: center; }
    <textarea id="data" rows="2" cols="60">
    [-1, 2, '', -2, 0, 4, '', 1, 5, 0, -5, 7, 11, '', 3]
    </textarea>
    <br />
    <button onclick="sortAsc('#data')">Sort Ascending</button>
    <button onclick="sortDesc('#data')">Sort Descending</button>
    Login or Signup to reply.
  4. I would personally define some general purpose sort helpers that are useful in all sorts of sort() scenarios.

    Let’s first look at the answer without knowing what the helpers do:

    import { fallThrough, by, asc, desc, front, back } from './lib/sort-helpers';
    
    const items = [-1, 2, '', -2, 0, 4, '', 1,5,0, -5, 7, 11, '',3];
    items.sort(fallThrough(
      back(item => typeof item === "string"),
      asc,
    ));
    console.log(items);
    
    function solution({ fallThrough, by, asc, desc, front, back, itself }) {
      const items = [-1, 2, '', -2, 0, 4, '', 1,5,0, -5, 7, 11, '',3];
      items.sort(fallThrough(
        back(item => typeof item === "string"),
        asc,
      ));
      console.log(items);
    }
    
    const SortHelpers = (function () {
      const fallThrough = (...comparators) => (a, b) => (
        comparators.reduce((diff, comparator) => diff || comparator(a, b), 0)
      );
      const by = (fn, comparator = asc) => (a, b) => comparator(fn(a), fn(b));
      const asc = (a, b) => -(a < b) || +(a > b);
      const desc = (a, b) => asc(b, a);
      const bool = (fn) => (item) => !!fn(item);
      const front = (fn) => by(bool(fn), desc);
      const back = (fn) => by(bool(fn), asc);
      
      return { fallThrough, by, asc, desc, front, back };
    })();
    
    solution(SortHelpers);

    As you can see this solution looks relatively simple. Note that the second sorting criteria asc will apply to both numbers and strings. So if the strings are non-empty, they will also be sorted in ascending order.

    Helpers

    Now, let’s explain the general purpose helpers (see collapsed snippet).

    I’ll use the term comparator quite a bit. A comparator is a function that accepts 2 arguments, then returns a number to tell sort() how the 2 arguments should be sorted.

    A negative value indicates that the first argument should be placed before the second. A positive value indicates that the first argument should be placed after the second argument. Zero indicates that both arguments can be considered equal.

    A basic example of an comparator can be seen in the explanation for asc.

    fallThrough

    fallThrough allows us to pass multiple comparator functions. If the first comparator says that the values are equal, then the next comparator is invoked. If the second comparator also says the values are equal, we’ll move on the the third.

    This goes on until one of the comparators says which value comes first. If there are no more comparators available, the values are considered equal.

    Here is an example without the use of the other helpers:

    const people = [
      { firstName: "John", lastName: "Doe" },
      { firstName: "Jane", lastName: "Doe" },
      { firstName: "Dave", lastName: "Dex" },
    ];
    
    people.sort(fallThrough(
      (personA, personB) => personA.lastName.localeCompare(personA.lastName),
      (personA, personB) => personB.firstName.localeCompare(personB.firstName),
    ));
    

    The above will first try to sort based on last name. If those are equal we sort by first name.

    by

    by is a simple helper that accepts a mapping function and a comparator. It applies the mapping function to both comparator arguments. The purpose is mainly to reduce code repetition.

    When looking in the example above we can see that we access personA.lastName and personB.lastName, which are the same mappings for both compared items. By using by we can simplify this example:

    const localeCompare = (a, b) => a.localeCompare(b);
    
    people.sort(fallThrough(
      by(person => person.LastName, localeCompare),
      by(person => person.firstName, localeCompare),
    ));
    

    Note that you don’t have to use fallThrough. If have just a single sorting criteria then the following will suffice:

    people.sort(by(person => person.LastName, localeCompare));
    

    asc & desc

    asc and desc are comparators based on < (less than) and > (greater then).

    The definition of asc is:

    const asc = (a, b) => -(a < b) || +(a > b);
    

    Which might be hard to read if you’re still new to JavaScript. It could also be written as:

    function asc(a, b) {
      if (a < b) return -1; // a before b
      if (a > b) return  1; // a after b
      return 0; // equal
    }
    

    The more cryptic version uses -(a < b), which evaluates to -(true), which in turn evaluates to -1. Since -1 is a truthy value || short circuits and -1 is returned. If this first expression is not true, then it evaluates to -(false), which will produce -0. Since -0 is a falsy value we’ll move on to the second operand of ||. Here we’ll do the same if a > b is true then +(true) will result in 1 for the return value. If a > b is false (aka the values are equal) then +(false) will return 0.

    Note that when comparing strings with < and > they are compared based on code point value of the characters.

    desc is the same as asc, but produces the reverse order by swapping the comparator arguments.

    front & back

    front and back are there to simplify boolean logic in sorting.

    by(item => typeof item === "string", asc)
    

    Is kind of hard to read. Are the strings placed in the front or in the back? Let’s think for a second. typeof item === "string" produces a true or false value. true will coerce into 1, false into 0. We are sorting in ascending order, so we are sorting from small to high. 1 is greater than 0, meaning that strings are placed in the back.

    You don’t want to do this kind of thinking when reading code. Therefore I’ve provided the front and back helpers. These expect you to pass a test and make the above a lot more readable.

    back(item => typeof item === "string")
    

    In this version you can instantly see that strings are placed at the back of the array.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search