114

I need to sort an array of strings, but I need it so that null is always last. For example, the array:

var arr = [a, b, null, d, null]

When sorted ascending I need it to be sorted like [a, b, d, null, null] and when sorted descending I need it to be sorted like [d, b, a, null, null].

Is this possible? I tried the solution found below but it's not quite what I need.

How can one compare string and numeric values (respecting negative values, with null always last)?

Community
  • 1
  • 1
Sirk
  • 1,547
  • 2
  • 12
  • 18
  • Use your own custom sort function a la http://stackoverflow.com/q/8537602/438992 that always returns greater-than for nulls. – Dave Newton Apr 23 '15 at 16:24

7 Answers7

231

Check out .sort() and do it with custom sorting. Example

function alphabetically(ascending) {
  return function (a, b) {
    // equal items sort equally
    if (a === b) {
        return 0;
    }

    // nulls sort after anything else
    if (a === null) {
        return 1;
    }
    if (b === null) {
        return -1;
    }

    // otherwise, if we're ascending, lowest sorts first
    if (ascending) {
        return a < b ? -1 : 1;
    }

    // if descending, highest sorts first
    return a < b ? 1 : -1;
  };
}



var arr = [null, "a", "z", null, "b"];

console.log(arr.sort(alphabetically(true)));
console.log(arr.sort(alphabetically(false)));
AntouanK
  • 4,880
  • 1
  • 21
  • 26
  • 10
    Can anyone explain why do we send 1 for a and -1 for b? if(a === null) { return 1; } else if(b === null){ return -1; } – prgmrDev Jan 15 '18 at 05:04
  • 2
    @prgmrDev see the sort documentation: "If compareFunction(a, b) is less than 0, sort a to an index lower than b (i.e. a comes first)." – Bart Römgens Jan 24 '19 at 07:57
  • @BartRömgens or anyone else, I'm still having trouble understanding that line in the documentation. Would you be able to elaborate a little more to help me out? – Jeffrey Wen Jul 21 '20 at 18:04
  • 1
    @JeffreyWen Basically you give a function to "sort()" and that function runs for every pair of elements. For example run this `[1,2,3,4,5,6,7].sort(function(a,b){ console.log('comparing pair:', a,b); return -1; })` and then `[1,2,3,4,5,6,7].sort(function(a,b){ console.log('comparing pair:', a,b); return 1; })` For every pair, you can return a negative or positive number, to change or not change the order of the pair. I hope that makes sense. Experiment with it and use console.log to see what the function does. – AntouanK Jul 22 '20 at 19:31
  • @AntouanK how do you do it if you want to place null first?? – Abhishek Ekaanth Sep 25 '20 at 07:07
  • @AbhishekEkaanth you would reverse those null comparisons to return -1 and 1 respectively. So that they sort the values earlier if they are null. See an example here https://repl.it/repls/LightgreenRoyalblueDesigners#index.js – AntouanK Sep 25 '20 at 09:06
  • 1
    no need for first 3 `else if`-s, `if` is enough – Alex Szücs May 20 '22 at 08:32
  • @AlexSzücs Please explain so we know what you mean. – close Jun 05 '22 at 12:00
  • @close Return leaves the function. Everything else happens if the condition is not true. `if(x){return}else if(y){}` -> `if(x){return} if(y){}` – Alex Szücs Jun 07 '22 at 08:47
  • @AlexSzücs I see, so replacing each `else if` by `if` will do (but keep the `else` at the end). – close Jun 28 '22 at 09:12
105

Use a custom compare function that discriminates against null values:

arr.sort(function(a, b) {
    return (a===null)-(b===null) || +(a>b)||-(a<b);
});

For descending order of the non-null values, just swap a and b in the direct comparison:

arr.sort(function(a, b) {
    return (a===null)-(b===null) || -(a>b)||+(a<b);
});
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • 3
    Used this technique to send empty dates to the bottom of the list when sorting dates in ascending order. Brilliant solution, thank you kindly! – mhodges May 20 '16 at 18:16
  • mind explaining how this works in cases where 1) one of them is null 2) both are not null? – cegprakash Nov 07 '19 at 12:31
  • 2
    @cegprakash If exactly one of them is `null`, the `(a===null)-(b===null)` expression will evaluate to `1` or `-1` (subtraction of booleans gets coerced to a number), when both are not `null` it will evaluate to `0` and the second part of the `||` expression will take effect. – Bergi Nov 07 '19 at 13:27
  • @Bergi how do you do it if you want to place null first?? – Abhishek Ekaanth Sep 25 '20 at 07:08
  • 2
    @AbhishekEkaanth You can swap `a` and `b` in the null comparisons: `return (b===null)-(a===null) || …` – Bergi Sep 25 '20 at 12:22
  • @Bergi - answer needs correction for descending order sort. As you have correctly expressed in comments to AbhishekEkaanth, a and b need to be swapped in the null comparisons - this is not reflected in the answer (it still shows (a===null) - (b===null)). I tried to edit, but edits need min. 6 characters, and I didn't feel more changes than necessary were appropriate. (Sorry for referencing post from long ago, but it's still handy today!) – richter Jul 08 '21 at 02:50
  • @richter The OP question asks for the nulls to be sorted last, it would be wrong to change the answer. I can however clarify what "descending" refers to in the second snippet. – Bergi Jul 08 '21 at 12:54
  • 1
    @Bergi - Sorry, you are of course correct. I was confused by my own use case! I needed to put null values first, but on reading your comment, I realised that you were discussing the descending order being for non-null values. I achieved non-null first with 'b===null - a === null...'. Apologies for my being thick-headed, and thanks for a handy answer! – richter Jul 12 '21 at 12:13
  • 1
    what does +(a>b)||-(a – cegprakash Jan 10 '22 at 11:50
  • 1
    @cegprakash It's similar, but `a-b` only works for numbers and booleans. – Bergi Jan 10 '22 at 11:52
22

Ascending

arr.sort((a, b) => (a != null ? a : Infinity) - (b != null ? b : Infinity))

Descending

arr.sort((a, b) => (b != null ? b : -Infinity) - (a != null ? a : -Infinity))

(For descending order if you don't have negative values in the array, I recommend to use 0 instead of -Infinity)

cegprakash
  • 2,937
  • 33
  • 60
15

The simplest approach is to handle null first, then deal with non-null cases based on the desired order:

function sortnull(arr, ascending) {
  // default to ascending
  if (typeof(ascending) === "undefined")
    ascending = true;

  const multiplier = ascending ? 1 : -1;

  const sorter = function(a, b) {
    if (a === b)          // identical? return 0
      return 0;
    else if (a === null)  // a is null? last 
      return 1;
    else if (b === null)  // b is null? last
      return -1;
    else                  // compare, negate if descending
      return a.localeCompare(b) * multiplier;
  }

  return arr.sort(sorter);
}

const arr = ["a", "b", null, "d", null];

console.log(sortnull(arr));        // ascending   ["a", "b", "d", null, null]
console.log(sortnull(arr, true));  // ascending   ["a", "b", "d", null, null]
console.log(sortnull(arr, false)); // descending  ["d", "b", "a", null, null]
Paul Roub
  • 36,322
  • 27
  • 84
  • 93
3

If you need natural sorting for numbers, or any of the options provided by Collator (including speed enhancements and respecting locale), try this approach, based off of Paul Roub's solution, cleaned up a bit. We almost always use numeric sorting, hence the defaults...

If you are not a Typescript fan, just strip off the :type specs or copy from the snippet.

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Collator

const naturalCollator = new Intl.Collator(undefined, {numeric: true, sensitivity: 'base'});
const alphabeticCollator = new Intl.Collator(undefined, {});

function nullSort(descending: boolean = false, alphabetic: boolean = false) {
  return function (a: any, b: any): number {
    if (a === b) {
      return 0;
    }
    if (a === null) {
      return 1;
    }
    if (b === null) {
      return -1;
    }

    let ret
    if (alphabetic) {
      ret = alphabeticCollator.compare(a, b)
    } else {
      ret = naturalCollator.compare(a, b)
    }
    if (descending) {
      ret = -ret
    }
    return ret
  };
}

Use it like this.

// numeric, ascending (default)
myList.sort(nullSort());

// alphabetic, descending
myList.sort(nullSort(true, true));

You can modify the factory method to take a collator instead, for greater flexibility.

function nullSort(descending: boolean = false, collator: Collator = naturalCollator)

Working Snippet

const naturalCollator = new Intl.Collator(undefined, {
  numeric: true,
  sensitivity: 'base'
});
const alphabeticCollator = new Intl.Collator(undefined, {});

function nullSort(descending = false, alphabetic = false) {
  return function(a, b) {
    if (a === b) {
      return 0;
    }
    if (a === null) {
      return 1;
    }
    if (b === null) {
      return -1;
    }

    let ret
    if (alphabetic) {
      ret = alphabeticCollator.compare(a, b)
    } else {
      ret = naturalCollator.compare(a, b)
    }
    if (descending) {
      ret = -ret
    }
    return ret
  };
}

const items = [null, 10, 1, 100, null, 'hello', .1, null]

console.log(items.sort(nullSort()));
Steven Spungin
  • 27,002
  • 5
  • 88
  • 78
0

I am sorting objects with a custom index and this works for me. I am not wanting to change the original array and it is important to keep the null indexes where they are.

let sorted = [...array].sort((a, b) => {
               if (!a || !b) return 0;
               else return a.CustomIndex - b.CustomIndex;
             });
-1

Do it like:

        var arr = [a, b, null, d, null]

        foreach ($arr as $key => $value) {
           if($value == null)
           unset($arr[$key]);
           $arr[] = $value;

        }
        // rebuild array index
        $arr = array_values($arr);

        echo '<pre>';print_r($arr);die;
Shubham
  • 37
  • 4