如何在JavaScript中按数字顺序对字符串进行排序

18
我想对一个字符串数组进行排序(在JavaScript中),使得其中数字的组以整数而不是字符串进行比较。我不担心有符号或浮点数。 例如,结果应该是[“a1b3”,“a9b2”,“a10b2”,“a10b11”]而不是[“a1b3”,“a10b11”,“a10b2”,“a9b2”]。 最简单的方法似乎是将每个字符串在数字组周围的边界上拆分。我能否传递一个模式给String.split,以在不删除任何字符的情况下在字符边界上拆分? “abc11def22ghi”.split(/(\d+)/) = ["abc","11","def","22","ghi"]; 还是是否有另一种不涉及拆分字符串的比较字符串的方法,例如通过在所有数字组中添加前导零来使它们具有相同的长度? "aa1bb" => "aa00000001bb", "aa10bb" => "aa00000010bb" 我正在处理任意字符串,而不是具有特定数字组排列的字符串。
我喜欢Gaby的/(\d+)/一行代码拆分数组。这有多向后兼容? 解析字符串并可以用于重建原始字符串的解决方案比此比较函数更有效。没有答案处理某些字符串以数字开头,而其他字符串不是,但很容易纠正,并且在原始问题中没有明确说明。
["a100", "a20", "a3", "a3b", "a3b100", "a3b20", "a3b3", "!!", "~~", "9", "10", "9.5"].sort(function (inA, inB) {
    var result = 0;

    var a, b, pattern = /(\d+)/;
    var as = inA.split(pattern);
    var bs = inB.split(pattern);
    var index, count = as.length;

    if (('' === as[0]) === ('' === bs[0])) {
        if (count > bs.length)
            count = bs.length;

        for (index = 0; index < count && 0 === result; ++index) {
            a = as[index]; b = bs[index];

            if (index & 1) {
                result = a - b;
            } else {
                result = !(a < b) ? (a > b) ? 1 : 0 : -1;
            }
        }

        if (0 === result)
            result = as.length - bs.length;
    } else {
        result = !(inA < inB) ? (inA > inB) ? 1 : 0 : -1;
    }

    return result;
}).toString();

结果:"!!,9,9.5,10,a3,a3b,a3b3,a3b20,a3b100,a20,a100,~~"


非数字部分总是相同的吗?如果不是,排序算法应该按ASCII顺序对它们进行排序吗? - Mark Byers
1
在你的例子中,是提取13、92、102、1011吗?还是更像1.3、9.2、10.2、10.11?我的意思是第一个数字更重要,还是字母只是被忽略了? - Lee Kowalkowski
哦,你还想对非整数进行排序,我现在明白了。 - Lee Kowalkowski
7个回答

35
另一种变体是使用带有数字选项的 Intl.Collator 实例:

var array = ["a100", "a20", "a3", "a3b", "a3b100", "a3b20", "a3b3", "!!", "~~", "9", "10", "9.5"];
var collator = new Intl.Collator([], {numeric: true});
array.sort((a, b) => collator.compare(a, b));
console.log(array);


1
这对我很有效。谢谢! - Kirk Liemohn

19

我认为这是你想要的

function sortArray(arr) {
    var tempArr = [], n;
    for (var i in arr) {
        tempArr[i] = arr[i].match(/([^0-9]+)|([0-9]+)/g);
        for (var j in tempArr[i]) {
            if( ! isNaN(n = parseInt(tempArr[i][j])) ){
                tempArr[i][j] = n;
            }
        }
    }
    tempArr.sort(function (x, y) {
        for (var i in x) {
            if (y.length < i || x[i] < y[i]) {
                return -1; // x is longer
            }
            if (x[i] > y[i]) {
                return 1;
            }
        }
        return 0;
    });
    for (var i in tempArr) {
        arr[i] = tempArr[i].join('');
    }
    return arr;
}
alert(
    sortArray(["a1b3", "a10b11", "a10b2", "a9b2"]).join(",")
);

25
适用于stacksort。 - Eugene Bujak
1
如果一些字符串以数字开头,而另一些字符串以字母开头,则无法正常工作。请编辑已提交内容。 - Daniel Griscom

12

假设你只想根据每个数组条目中的数字(忽略非数字字符)进行数字排序,你可以使用以下代码:

function sortByDigits(array) {
    var re = /\D/g;
    
    array.sort(function(a, b) {
        return(parseInt(a.replace(re, ""), 10) - parseInt(b.replace(re, ""), 10));
    });
    return(array);
}

它使用自定义排序函数,在每次进行比较时都会删除数字并将其转换为数字。 您可以在此处查看它的工作方式:http://jsfiddle.net/jfriend00/t87m2/


如果遇到前导零为零的数字,会出现问题,不是吗?例如:abc03def45。 - Yevgeny Simkin
@Dr.Dredel - 使用parseInt使其成为纯数字排序。当转换为真实数字时,前导零将被忽略,因为它们应该被忽略。我没有看到任何问题。 - jfriend00
我认为 OP 仍然想按非数字排序。 - Lee Kowalkowski
@LeeKowalkowski - 这是一个相当不清楚的问题,而且原帖作者也没有澄清。如果我的回答不是他们正在寻找的内容,我已经要求原帖作者回复并澄清,但他们没有回复。 - jfriend00

7
使用此比较函数进行排序...
function compareLists(a, b) {
    var alist = a.split(/(\d+)/), // Split text on change from anything
                                  // to digit and digit to anything
        blist = b.split(/(\d+)/); // Split text on change from anything
                                  // to digit and digit to anything

    alist.slice(-1) == '' ? alist.pop() : null; // Remove the last element if empty

    blist.slice(-1) == '' ? blist.pop() : null; // Remove the last element if empty

    for (var i = 0, len = alist.length; i < len; i++) {
        if (alist[i] != blist[i]){ // Find the first non-equal part
           if (alist[i].match(/\d/)) // If numeric
           {
              return +alist[i] - +blist[i]; // Compare as number
           } else {
              return alist[i].localeCompare(blist[i]); // Compare as string
           }
        }
    }

    return true;
}

语法

var data = ["a1b3", "a10b11", "b10b2", "a9b2", "a1b20", "a1c4"];
data.sort(compareLists);
alert(data);

这里有一个演示:http://jsfiddle.net/h9Rqr/7/


1

这里是一个更完整的解决方案,可以根据字符串中的字母和数字进行排序。

function sort(list) {
    var i, l, mi, ml, x;
    // copy the original array
    list = list.slice(0);

    // split the strings, converting numeric (integer) parts to integers
    // and leaving letters as strings
    for( i = 0, l = list.length; i < l; i++ ) {
        list[i] = list[i].match(/(\d+|[a-z]+)/g);
        for( mi = 0, ml = list[i].length; mi < ml ; mi++ ) {
            x = parseInt(list[i][mi], 10);
            list[i][mi] = !!x || x === 0 ? x : list[i][mi];
        }
    }

    // sort deeply, without comparing integers as strings
    list = list.sort(function(a, b) {
        var i = 0, l = a.length, res = 0;
        while( res === 0 && i < l) {
            if( a[i] !== b[i] ) {
                res = a[i] < b[i] ? -1 : 1;
                break;
            }

            // If you want to ignore the letters, and only sort by numbers
            // use this instead:
            // 
            // if( typeof a[i] === "number" && a[i] !== b[i] ) {
            //     res = a[i] < b[i] ? -1 : 1;
            //     break;
            // }

            i++;
        }
        return res;
    });

    // glue it together again
    for( i = 0, l = list.length; i < l; i++ ) {
        list[i] = list[i].join("");
    }
    return list;
}

我认为OP想要忽略非数字,只按数字排序。 - jfriend00
@jfriend00:嗯... 你可能是对的。如果真是这样,你可以在比较函数的 while 循环中添加一个 typeof a[i] === "number" 条件子句。 - Flambino

1
我需要一种方法来将混合字符串转换为可在其他地方进行排序的字符串,以便数字按数字顺序排序,字母按字母表顺序排序。基于上面的答案,我创建了以下内容,它以我可以理解的方式填充所有数字,无论它们在字符串中出现的位置。
function padAllNumbers(strIn) {
    // Used to create mixed strings that sort numerically as well as non-numerically
    var patternDigits = /(\d+)/g; // This recognises digit/non-digit boundaries
    var astrIn = strIn.split( patternDigits ); // we create an array of alternating digit/non-digit groups

    var result = "";

    for (var i=0;i<astrIn.length;  i++) {
        if (astrIn[i] != "") { // first and last elements can be "" and we don't want these padded out
            if (isNaN(astrIn[i])) {
                result += astrIn[i];
            } else {
                result += padOneNumberString("000000000",astrIn[i]);
            }
        }
    }
    return result;
}

function padOneNumberString(pad,strNum,left) {
    // Pad out a string at left (or right)
    if (typeof strNum === "undefined") return pad;
    if (typeof left === "undefined") left = true;
    var padLen =  pad.length - (""+ strNum).length;
    var padding = pad.substr(0,padLen);
    return left?  padding + strNum : strNum + padding;
}

0

排序通常从左到右进行,除非您创建自定义算法。字母或数字按数字优先,然后按字母比较。

然而,根据您自己的示例(a1、a9、a10),永远不会发生。这需要您事先了解数据并在应用排序之前以每种可能的方式拆分字符串。

最后一个选择是:

a)每当从字母转换为数字或数字转换为字母时,从左到右中断每个字符串;和 b)然后从右到左开始对这些组进行排序。那将是一个非常要求高的算法。可以做到!

最后,如果您是原始“文本”的生成器,则应考虑标准化输出,其中a1 a9 a10可以输出为a01 a09 a10。这样,您可以完全控制算法的最终版本。


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