JavaScript排序混合字符串和空值的数组

11

当对一个由字符串、空值和零混合而成的数组进行排序时,我得到的结果与预期不符,空值似乎被排序为类似于“null”字符串的形式。

我在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 + "; ")});

结果是:

排序后的数组: 0; 0; b; c; d; e; g; h; i; l; m; n; ne; nimbus; null; null; null; nur; o; p;

你有想过如何在对数组进行排序时将null值视为空字符串,使它们与零一起出现在排序后的数组中吗?

谢谢!


您需要在字符串之前对数字进行排序吗? 字典序并不等同于数字顺序。 考虑将{100,15}和{"100","15"}排序作为一个例子。 - Andras Vass
@andras:不,只有零。 - Marco Demaio
9个回答

15

通过将所有内容转换为字符串(特别是将 null 转换为空字符串),并允许 JavaScript 内置的字符串比较来完成您想要的操作:

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 );
} );

如果字符串中包含数字,这种排序方法将失败。你需要使用更多的三元运算符。 - Andras Vass
1
如果您所指的“正确地排序”是指20在5之前,那么默认排序也有同样的问题。 - Alex Jasmin
1
@Marco:这有两个问题:首先,零和null都会被转换为空字符串,这意味着排序将无法区分空值和零(除非您不介意)。其次,非零数字不会被转换为字符串,并且数字与无法转换为数字的字符串(例如“d”)进行比较始终返回false,因此排序结果可能不符合您的预期。 - Tim Down
@Tim Down:谢谢并点赞,你说得非常正确,尤其是第二点。 - Marco Demaio
@MarcoDemaio:我之前没有看到你的编辑。非常有趣,谢谢你添加它。 - Tim Down
显示剩余2条评论

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);
});

啊,是的,我忘记了 localeCompare。+1。 - Tim Down
请注意,此实现未处理 abnull 的情况,这应该返回 0。通常不会有影响,但根据回调函数的使用方式,可能会有影响。 - christo8989

4

我看到这个主题,正在寻找类似的快速粗糙答案,但它没有触及我实际需要的内容。 "如何处理空值",将它们浮动到顶部或底部等等。 这就是我想出来的:

    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

我还不能在@robert的评论下添加评论,但是这里是对@robert的扩展,以支持布尔值:

[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;
});

此外,根据JS规范,undefined值总是被移到数组的末尾...

2
使用一个自定义排序函数,以这种方式处理空值。
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;
});

第一行当然应该写成: if (a===null) a=''; - HBP

2

我们可以用最简单的方式完成它

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

0

由于我不断遇到不同的用例来对空值和未定义的内容进行排序,因此我创建了以下函数来使用一组选项创建比较器。在这里添加它,以防对他人有用。

请注意,此函数目前不处理NaN和其他混合类型等情况,但可以轻松地将其作为选项添加。

使用示例:

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,
});

代码如下:

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;
}

0

我提出了一种对包含混合值(数字、字符串、null、未定义的值)的数组进行升序排序的方案。

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];
};

结果:

[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', 'ААА', 'ааэ', 'жа', '  Жу', 'Эркер', 'Яромир Ягр'];

-1

浏览器正在执行 null.toString();由于 null 是一个对象,这基本上就是 Object.toString()... 这将返回 "null"。

传递一个参数来排序,作为您的比较函数 [如果函数返回大于 0 的值,则 b 排在 a 之前]

函数基本上应该是:

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);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接