web-dev-qa-db-fra.com

Trier les éléments du tableau (chaîne avec des nombres), tri naturel

J'ai un tableau comme;

["IL0 Foo", "PI0 Bar", "IL10 Baz", "IL3 Bob says hello"]

Et besoin de le trier pour qu'il apparaisse comme;

["IL0 Foo", "IL3 Bob says hello", "IL10 Baz", "PI0 Bar"]

J'ai essayé une fonction de tri;

function compare(a,b) {
  if (a < b)
     return -1;
  if (a > b)
    return 1;
  return 0;
}

mais cela donne l'ordre

["IL0 Foo", "IL10 Baz", "IL3 Bob says hello", "PI0 Bar"]

J'ai essayé de penser à une expression rationnelle qui fonctionnera, mais je ne peux pas m'en sortir.
Si cela peut aider, le format sera toujours de 2 lettres, x nombre de chiffres, puis n'importe quel nombre de caractères.

48
Rooneyl

Ceci est appelé "tri naturel" et peut être implémenté dans JS comme ceci:

function naturalCompare(a, b) {
    var ax = [], bx = [];

    a.replace(/(\d+)|(\D+)/g, function(_, $1, $2) { ax.Push([$1 || Infinity, $2 || ""]) });
    b.replace(/(\d+)|(\D+)/g, function(_, $1, $2) { bx.Push([$1 || Infinity, $2 || ""]) });
    
    while(ax.length && bx.length) {
        var an = ax.shift();
        var bn = bx.shift();
        var nn = (an[0] - bn[0]) || an[1].localeCompare(bn[1]);
        if(nn) return nn;
    }

    return ax.length - bx.length;
}

/////////////////////////

test = [
    "img12.png",
    "img10.png",
    "img2.png",
    "img1.png",
    "img101.png",
    "img101a.png",
    "abc10.jpg",
    "abc10",
    "abc2.jpg",
    "20.jpg",
    "20",
    "abc",
    "abc2",
    ""
];

test.sort(naturalCompare)
document.write("<pre>" + JSON.stringify(test,0,3));

Pour trier dans l'ordre inverse, échangez simplement les arguments:

test.sort(function(a, b) { return naturalCompare(b, a) })

ou simplement

test = test.sort(naturalCompare).reverse();
90
georg

Vous pouvez utiliser String#localeCompare avec options

sensibilité

Quelles différences dans les chaînes devraient conduire à des valeurs de résultat non nulles. Les valeurs possibles sont:

  • "base": Seules les chaînes dont les lettres de base diffèrent sont considérées comme inégales. Exemples: a ≠ b, a = á, a = A.
  • "accent": Seules les chaînes dont les lettres de base ou les accents diffèrent et les autres signes diacritiques se comparent comme inégales. Exemples: a ≠ b, a ≠ á, a = A.
  • "case": Seules les chaînes qui diffèrent en lettres de base ou en casse se comparent comme inégales. Exemples: a ≠ b, a = á, a ≠ A.
  • "variant": Les chaînes qui diffèrent par les lettres de base, les accents et autres signes diacritiques, ou la casse, se comparent comme inégales. D'autres différences peuvent également être prises en considération. Exemples: a ≠ b, a ≠ á, a ≠ A.

La valeur par défaut est "variant" pour l'utilisation "sort"; cela dépend des paramètres régionaux pour l'utilisation "recherche".

numérique

Indique si le classement numérique doit être utilisé, tel que "1" <"2" <"10". Les valeurs possibles sont true et false; la valeur par défaut est false. Cette option peut être définie via une propriété d'options ou via une clé d'extension Unicode; si les deux sont fournis, la propriété options a priorité. Les implémentations ne sont pas nécessaires pour prendre en charge cette propriété.

var array = ["IL0 Foo", "PI0 Bar", "IL10 Baz", "IL3 Bob says hello"];

array.sort(function (a,b) {
    return a.localeCompare(b, undefined, { numeric: true, sensitivity: 'base' });
});

console.log(array);
8
Nina Scholz
var re = /([a-z]+)(\d+)(.+)/i;
var arr = ["IL0 Foo", "PI0 Bar", "IL10 Baz", "IL3 Bob says hello"];
var order = arr.sort( function(a,b){
    var ma = a.match(re),
        mb = b.match(re),
        a_str = ma[1],
        b_str = mb[1],
        a_num = parseInt(ma[2],10),
        b_num = parseInt(mb[2],10),
        a_rem = ma[3],
        b_rem = mb[3];
    return a_str > b_str ? 1 : a_str < b_str ? -1 : a_num > b_num ? 1 : a_num < b_num ? -1 : a_rem > b_rem;  
});
5
epascarello

J'ai beaucoup aimé la solution de Georg, mais j'avais besoin de traits de soulignement ("_") pour trier avant les nombres. Voici comment j'ai modifié son code:

var chunkRgx = /(_+)|([0-9]+)|([^0-9_]+)/g;
function naturalCompare(a, b) {
    var ax = [], bx = [];
    
    a.replace(chunkRgx, function(_, $1, $2, $3) {
        ax.Push([$1 || "0", $2 || Infinity, $3 || ""])
    });
    b.replace(chunkRgx, function(_, $1, $2, $3) {
        bx.Push([$1 || "0", $2 || Infinity, $3 || ""])
    });
    
    while(ax.length && bx.length) {
        var an = ax.shift();
        var bn = bx.shift();
        var nn = an[0].localeCompare(bn[0]) || 
                 (an[1] - bn[1]) || 
                 an[2].localeCompare(bn[2]);
        if(nn) return nn;
    }
    
    return ax.length - bx.length;
}

/////////////////////////

test = [
    "img12.png",
    "img10.png",
    "img2.png",
    "img1.png",
    "img101.png",
    "img101a.png",
    "abc10.jpg",
    "abc10",
    "abc2.jpg",
    "20.jpg",
    "20",
    "abc",
    "abc2",
    "_abc",
    "_ab_c",
    "_ab__c",
    "_abc_d",
    "ab_",
    "abc_",
    "_ab_cd",
    ""
];

test.sort(naturalCompare)
document.write("<pre>" + JSON.stringify(test,0,3));
3
Chaim Leib Halbert

Remplissez les numéros en chaîne avec des zéros en tête, puis triez normalement.

var naturalSort = function (a, b) {
    a = ('' + a).replace(/(\d+)/g, function (n) { return ('0000' + n).slice(-5) });
    b = ('' + b).replace(/(\d+)/g, function (n) { return ('0000' + n).slice(-5) });
    return a.localeCompare(b);
}

var naturalSortModern = function (a, b) {
    return ('' + a).localeCompare(('' + b), 'en', { numeric: true });
}

console.dir((["IL0 Foo", "PI0 Bar", "IL10 Baz", "IL3 Bob says hello"].sort(naturalSort)));

console.dir((["IL0 Foo", "PI0 Bar", "IL10 Baz", "IL3 Bob says hello"].sort(naturalSortModern)));
3
axfree

Vous pouvez faire une expression régulière comme celle-ci pour obtenir des parties non numériques et numériques de la chaîne:

var s = "foo124bar23";
s.match(/[^\d]+|\d+/g)

résultats: ["foo", "124" , "bar" , "23"]

Ensuite, dans votre fonction de comparaison, vous pouvez parcourir les parties des deux chaînes en les comparant partie par partie. La première partie non correspondante détermine le résultat de la comparaison globale. Pour chaque partie, vérifiez si la partie commence par un chiffre et si c'est le cas, analysez-la comme un nombre avant de faire la comparaison.

2
Tim Goodman

Ajoutez une autre alternative (pourquoi pas):

var ary = ["IL0 Foo", "PI0 Bar", "IL10 Hello", "IL10 Baz", "IL3 Bob says hello"];

// break out the three components in to an array
// "IL10 Bar" => ['IL', 10, 'Bar']
function getParts(i){
    i = i || '';
    var parts = i.match(/^([a-z]+)([0-9]+)(\s.*)$/i);
    if (parts){
        return [
            parts[1],
            parseInt(parts[2], 10),
            parts[3]
        ];
    }
    return []; // erroneous
}
ary.sort(function(a,b){
    // grab the parts
    var _a = getParts(a),
        _b = getParts(b);

    // trouble parsing (both fail = no shift, otherwise
    // move the troubles element to end of the array)
    if(_a.length == 0 && _b.length == 0) return 0;
    if(_a.length == 0) return -1;
    if(_b.length == 0) return 1;

    // Compare letter portion
    if (_a[0] < _b[0]) return -1;
    if (_a[0] > _b[0]) return 1;
    // letters are equal, continue...

    // compare number portion
    if (_a[1] < _b[1]) return -1;
    if (_a[1] > _b[1]) return 1;
    // numbers are equal, continue...

    // compare remaining string
    if (_a[2] < _b[2]) return -1;
    if (_a[2] > _b[2]) return 1;
    // strings are equal, continue...

    // exact match
    return 0;
});

exemple jsfiddle

1
Brad Christie

Pas joli, mais vérifiez les deux premiers codes de caractères. Si tous sont égaux, analysez et comparez les nombres:

var arr = ["IL0 Foo", "IL10 Baz", "IL3 Bob says hello", "PI0 Bar"];
arr.sort(function (a1, b1) {
    var a = parseInt(a1.match(/\d+/g)[0], 10),
        b = parseInt(b1.match(/\d+/g)[0], 10),
        letterA = a1.charCodeAt(0),
        letterB = b1.charCodeAt(0),
        letterA1 = a1.charCodeAt(1),
        letterB1 = b1.charCodeAt(1);
    if (letterA > letterB) {
        return 1;
    } else if (letterB > letterA) {
        return -1;
    } else {
        if (letterA1 > letterB1) {
            return 1;
        } else if (letterB1 > letterA1) {
            return -1;
        }
        if (a < b) return -1;
        if (a > b) return 1;
        return 0;
    }
});

Exemple

0
Joe