11

When soritng an array made of a mix of strings, null values and zeros, i get the result not properly as exptected, null values seem to get sorted as if they were 'null' strings. I did this (tested on FireFox):

var arr1 = arr2 = [null, "b", "c", "d", null, "e", 0, "g", null, 0, "h", "i", "l", "m", "n", "o", "p", "ne", "nur", "nimbus"];

document.write("SORTED ARRAY:<br>");
arr1.sort();
arr1.forEach(function(val){document.write(val + "; ")});

And the result is:

SORTED ARRAY: 0; 0; b; c; d; e; g; h; i; l; m; n; ne; nimbus; null; null; null; nur; o; p;

Do you have an idea of how to make the null value be considered like empty string during the sorting of the array, so that they appear 1st in the sorted arry along with the zeros.

Thanks!

2
  • Do you need numbers sorted as well before strings? Lexicographic order is not the same as numeric. Consider ordering { 100, 15 } and { "100", "15" } as an example. Commented Feb 24, 2010 at 20:09
  • @andras: No, just zeros. Commented Feb 25, 2010 at 15:54

9 Answers 9

15

This will do what you want by converting everything to strings (in particular converting null to an empty string) and allowing JavaScript's built-in string comparison do the work:

arr2.sort( function(a, b) 
{
    /* 
       We avoid reuse of arguments variables in a sort
       comparison function because of a bug in IE <= 8.
       See http://www.zachleat.com/web/array-sort/
    */
    var va = (a === null) ? "" : "" + a,
        vb = (b === null) ? "" : "" + b;

    return va > vb ? 1 : ( va === vb ? 0 : -1 );
} );
7
  • This will fail to sort properly if he has numbers. You need some more ternaries. Commented Feb 24, 2010 at 19:56
  • 1
    @andras If by properly you mean that 20 comes before 5 the default sort has the same issue Commented Feb 24, 2010 at 20:06
  • @andras, @Alexandre: The original question does say the array has zeros, strings and nulls so I'm not sure correctly handling numbers is required, but I take your point. Commented Feb 24, 2010 at 20:10
  • @Tim: sounds great thanks! What about doing: a = a || ""; b = b || ""; Commented Feb 25, 2010 at 15:54
  • 1
    @Marco: Two problems with that: first, zeroes and null will both be converted to an empty string, meaning the sort will not distinguish between nulls and zeroes (unless that isn't a problem for you). Second, non-zero numbers will not be converted to strings, and the comparison of a number with a string that cannot be converted into a number (such as "d") always returns false, so will not order as you expect. Commented Feb 25, 2010 at 16:10
9
[null, "b", "c", "d", null, "e", 0, "g", null, 0, "h", "i", "l", "m", "n", "o", "p", "ne", "nur", "nimbus"].sort(function (a,b) { 
   return a === null ? -1 : b === null ? 1 : a.toString().localeCompare(b);
});
2
  • Ah yes, I forgot about localeCompare. +1. Commented Feb 25, 2010 at 16:20
  • Be aware that this implementation does not handle if a and b are null, which should return 0. Typically it won't matter but depending on how the callback is used, it could matter. Commented Dec 2, 2020 at 0:48
4

I came across this thread looking for a similar quick-and-dirty answer, but it didn't touch on what I actually needed. "How to treat nulls", float them to the top or bottom, etc. This is what I came up with:

    var list = [0, -1, 1, -1, 0, null, 1];

var sorter = function(direction){

    // returns a sort function which treats `null` as a special case, either 'always higher' (1)
    // or 'always lower' (-1)

    direction = direction || 1;
    var up = direction > 0;

    return function(a, b){

        var r = -1,
            aa = a == null ? undefined : a,
            bb = b == null ? undefined : b,
            careabout = up ? aa : bb
        ;

        if(aa == bb){
            r = 0;
        }else if(aa > bb || careabout == undefined){
            r = 1
        }
        return r;

    }

}

var higher = [].concat(list.sort(sorter(1)));    
var lower = [].concat(list.sort(sorter(-1)));

console.log(lower[0] === null, lower);
console.log(higher[higher.length - 1] === null, higher);

// then, something that sorts something in a direction can use that direction to
// determine where the nulls end up. `list` above ranged from negative-one to one, 
// with mixed zero and null values in between. If we want to view that list 
// from highest value to descending, we'd want the nulls to be treated as 
// 'always lower' so they appear at the end of the list.
// If we wanted to view the list from lowest value to highest value we'd want the
// nulls to be treated as `higher-than-anything` so they would appear at the bottom
// list.

var sortThisArray = function(arr, direction){
    var s = sorter(direction);
    return arr.sort(function(a,b){
       return direction * s(a,b) 
    });
}

console.log(sortThisArray(list, 1));
console.log(sortThisArray(list, -1));
3

I can't add a comment yet to @robert's comment, but here's an expansion on @robert's to add support for boolean:

[null, "b", "c", "d", null, "e", 0, undefined, "g", null, 0, "h", "i", true, "l", "m", undefined, "n", "o", "p", false, "ne", "nur", "nimbus"].sort(function (a,b) { 
    if (a === b) { return 0; }
    if (a === null) {
        return -1;
    } else if (b === null) {
        return 1;
    } else if (typeof a === 'string') {
        return a.localeCompare(b);
    } else if (typeof a === 'number' || typeof a === 'boolean') {
        if (a < b) return -1;
        if (a > b) return 1;
    }
    return 0;
});

also, per JS specification, undefineds always get booted to the end of the array...

2

Use a custom ordering function that handles null values this way.

arr1.sort(function(a, b) {
    if (a===null) a='';
    if (b===null) b='';

    if (''+a < ''+b) return -1;
    if (''+a > ''+b) return  1;

    return 0;
});
1
  • That first line should of course read: if (a===null) a=''; Commented Feb 24, 2010 at 18:57
2

we can do it simplest way

sort: (a, b) => {
        a = a.name || '';
        b = b.name || '';
        return a.localeCompare(b);
    }
0

My proposal to ascending sorting of an array with mixed values (numbers, strings, nulls, undefined values).

const arr = [null, 'b46', '+', 'Яромир Ягр', '76region', 2, 9999999, 'Эркер', '', 0, 3, 33, 765, '366', '77rus', 'ааэ', null, null, '200', undefined, '�?�?�?', '1', '40', 88, 'cat', undefined, 'apple', 4, '55555', 777, 12, 6, 0, '55', 8, null, undefined, '  Ж�?', 'жа', 'bbbb', '    Xz', '  Z', 'aa', undefined];

const sortAsc = (arr) => {
  const undefinedAndNulls = arr.filter(val => val === null || val === undefined);
  const numbers = arr.filter(val => !isNaN(val) && val !== null);
  const sortedNumbers = numbers.sort((a, b) => a - b);
  const rest = arr.filter(val => val && isNaN(val));
  const sortedRest = rest.sort((a, b) => {
    const val1 = a || '';
    const val2 = b || '';
    const valueA = val1.toString().trimLeft();
    const valueB = val2.toString().trimLeft();
    return valueA.localeCompare(valueB);
  });
  return [...undefinedAndNulls, ...sortedNumbers, ...sortedRest];
};

result:

[null, null, null, undefined, undefined, null, undefined, undefined, '', 0, 0, '1', 2, 3, 4, 6, 8, 12, 33, '40', '55', 88, '200', '366', 765, 777, '55555', 9999999, '+', '76region', '77rus', 'aa', 'apple', 'b46', 'bbbb', 'cat', '    Xz', '  Z', '�?�?�?', 'ааэ', 'жа', '  Ж�?', 'Эркер', 'Яромир Ягр'];
0

Since I keep having different use cases for where to sort things like nulls and undefined, I created the following function to create a comparator using a set of options. Adding it here in case it's useful for others.

Note that this function doesn't currently handle NaN and other situations like mixed types, but these could easily be added as options.

Usage examples:

array.sort(createComparator({
   property: 'path.to.property',
   direction: 'desc',
   sortNulls: 'top',
   caseSensitive: true,
});

array.sort(createComparator({
   accessor: value => value.date.valueOf(),
   direction: 'desc',
   sortNulls: 'top',
   caseSensitive: true,
});

And the code is:

import get from 'lodash/get';

/**
 * Creates a comparator function for sorting given a set of options.
 *
 * @param {String}   options.property       
 *                   The path to the property to sort by
 *                   
 * @param {Function} options.accessor       
 *                   The function used to calculate the property to sort by. Takes the 
 *                   item being sorted and returns the value to use for the sorting 
 *                   comparison
 *                   
 * @param {String}   options.direction      
 *                   The direction of sort: `asc` or `desc`. Defaults to `asc`
 *                   
 * @param {String}   options.sortNulls      
 *                   Where null values should be sorted: `top` or `bottom`. Defaults 
 *                   to `top`
 *                   
 * @param {String}   options.sortUndefineds 
 *                   Where undefined values should be sorted: `top` or `bottom`. 
 *                   Defaults to the value of `sortNulls`
 *                   
 * @param {boolean}  options.caseSensitive  
 *                   Whether to compare strings with the case of letters affecting 
 *                   the sort. Defaults to `false`
 *
 * @return {Function} A comparator function that can be used with `Array.sort` to 
 *                    sort an array
 */
function createComparator({
  property,
  accessor,
  direction = 'asc',
  sortNulls = 'top',
  sortUndefineds,
  caseSensitive = false,
}) {
  const topNulls = sortNulls === 'top';

  // Convert binary parameters to boolean to avoid doing it for each comparison
  return advancedComparator.bind(null, {
    accessor: property ? value => get(value, property) : accessor,
    desc: direction === 'desc' ? -1 : 1,
    topNulls,
    topUndefineds: sortUndefineds != null ? sortUndefineds === 'top' : topNulls,
    caseSensitive,
  });
}

function advancedComparator(options, a, b) {
  const { accessor, desc, topNulls, topUndefineds, caseSensitive } = options;

  a = accessor ? accessor(a) : a;
  b = accessor ? accessor(b) : b;

  if (a === null) {
    return b === null ? 0 : (topNulls ? -1 : 1);
  } else if (b === null) {
    return (topNulls ? 1 : -1);
  }

  if (typeof a === 'undefined') {
    return typeof b === 'undefined' ? 0 : (topUndefineds ? -1 : 1);
  } else if (typeof b === 'undefined') {
    return (topUndefineds ? 1 : -1);
  }

  if (!caseSensitive) {
    a = typeof a === 'string' ? a.toLowerCase() : a;
    b = typeof b === 'string' ? b.toLowerCase() : b;
  }

  if (typeof a === 'string' && typeof b === 'string') {
    return a.localeCompare(b);
  }

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

  return 0;
}
-1

The browser is doing null.toString(); since null is an Object, this is pretty much Object.toString()... which would return "null"

Pass in a parameter to sort, as your comparison function [if the function returns something greater than 0, b is sorted lower than a]

function would basically be:

comparisonFunc = function(a, b)
{
 if((a === null) && (b === null)) return 0; //they're both null and equal
 else if((a === null) && (b != null)) return -1; //move a downwards
 else if((a != null) && (b === null)) return 1; //move b downwards
 else{
  //Lexicographical sorting goes here
 }
}
set.sort(comparisonFunc);

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.