2011-11-12 3 views
8

Je voudrais trier un tableau de chaînes (en javascript) de sorte que les groupes de chiffres dans les chaînes soient comparés comme des entiers et non comme des chaînes. Je ne suis pas inquiet pour les nombres à virgule flottante ou signée.comment trier les chaînes en javascript numériquement

par exemple, le résultat devrait être ["a1b3","a9b2","a10b2","a10b11"] pas ["a1b3","a10b11","a10b2","a9b2"]

La meilleure façon de faire semble être diviser chaque chaîne sur les limites autour des groupes de chiffres. Y a-t-il un motif que je peux transmettre à String.split pour séparer les limites de caractères sans enlever aucun caractère?

"abc11def22ghi".split(/?/) = ["abc","11","def","22","ghi"];

Ou est-il une autre façon de comparer les chaînes qui ne concernent pas les diviser, peut-être par padding tous les groupes de chiffres avec des zéros de sorte qu'ils sont de la même longueur?

"aa1bb" => "aa00000001bb", "aa10bb" => "aa00000010bb"

Je travaille avec des chaînes arbitraires, et non des chaînes qui ont une disposition particulière des groupes de chiffres.

Edit:

J'aime le /(\d+)/ une doublure de Gaby pour diviser la matrice. Comment rétrocompatible est-ce?

Les solutions qui analysent les chaînes une fois d'une manière qui peut être utilisée pour reconstruire les originaux sont beaucoup plus efficaces que cette fonction de comparaison. Aucune des réponses ne gère certaines chaînes commençant par des chiffres et d'autres non, mais cela serait assez facile à corriger et n'était pas explicite dans la question initiale.

["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(); 

Résultat: "!!,9,9.5,10,a3,a3b,a3b3,a3b20,a3b100,a20,a100,~~"

+0

Les parties non numériques sont-elles toujours les mêmes? Si non, l'algorithme de tri doit-il les trier dans l'ordre ASCII? –

+1

Dans votre exemple, extrayez-vous 13, 92, 102, 1011? Ou est-ce plus comme 1.3, 9.2, 10.2, 10.11? Je veux dire est le premier nombre plus significatif ou les lettres sont-elles simplement ignorées? –

+0

... oh vous voulez toujours trier sur les non-entiers aussi, je l'obtiens maintenant ... –

Répondre

9

Je pense que cela fait ce que vous voulez

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(",") 
); 
+11

Fonctionne avec stacksort. –

5

Si l'on suppose ce que vous voulez faire est suffit de faire une sorte numérique par les chiffres dans chaque entrée du tableau (en ignorant la non-chiffres), vous pouvez utiliser ceci:

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

Il utilise une fonction de tri personnalisée qui supprime les chiffres et les convertit en nombre à chaque fois qu'il est demandé de faire une comparaison. Vous pouvez le voir fonctionner ici: http://jsfiddle.net/jfriend00/t87m2/.

Si ce n'est pas ce que vous voulez, alors veuillez clarifier car votre question n'est pas très claire sur le fait que le tri devrait fonctionner.

+0

Je pense qu'il pourrait y avoir des problèmes si elle rencontre un numéro de plomb zéro, non? c'est à dire. abc03def45 –

+0

@ Dr.Dredel - L'utilisation de parseInt en fait un tri purement numérique. Les zéros en tête seront ignorés lorsqu'ils seront convertis en un vrai nombre comme ils devraient l'être. Je ne vois aucun problème. – jfriend00

+0

ah ... très vrai ... nm –

1

Here's a more complete solution qui trie selon les lettres et les chiffres dans les chaînes

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

Je pensais que l'OP voulait ignorer les non-chiffres et juste trier par les chiffres. – jfriend00

+0

@ jfriend00: Hmmm ... vous pourriez avoir raison. Si c'est le cas, vous pouvez ajouter une clause 'typeof a [i] ===" nombre "' dans la boucle 'while' de la fonction de comparaison – Flambino

0

Le tri se produit de gauche à droite, sauf si vous créez un algorithme personnalisé. Les lettres ou les chiffres sont comparés d'abord aux chiffres puis aux lettres. Cependant, ce que vous voulez accomplir selon votre propre exemple (a1, a9, a10) ne se produira jamais. Cela vous demanderait de connaître les données avant la main et de scinder la chaîne de toutes les manières possibles avant d'appliquer le tri.

Une dernière alternative serait:

a) briser chaque chaîne de gauche à droite à chaque fois un changement de lettre à chiffres et vice versa; B) puis commencez le tri sur ces groupes de DROITE à GAUCHE. Ce sera un algorithme très exigeant. Peut être fait! Enfin, si vous êtes le GENERATEUR du "texte" d'origine, vous devriez envisager de NORMALISER la sortie où a1 a9 a10 pourrait être sortie comme a01 a09 a10. De cette façon, vous pourriez avoir cotnrol complet de la version finale de l'algorithme.

Bonne chance!

4

Utilisez cette fonction pour comparer le tri ..

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

Syntaxe

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

démo àhttp://jsfiddle.net/h9Rqr/7/

0

J'avais besoin d'un moyen de prendre une chaîne mixte et de créer une chaîne qui pourrait être triée ailleurs, de sorte que les nombres soient triés numériquement et les lettres par ordre alphabétique. Basé sur les réponses ci-dessus j'ai créé ce qui suit, qui remplit tous les nombres d'une manière que je peux comprendre, partout où ils apparaissent dans la chaîne.

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