JavaScript: naturalny rodzaj ciągów alfanumerycznych

173

Szukam najłatwiejszego sposobu posortowania tablicy składającej się z liczb i tekstu oraz ich kombinacji.

Na przykład

'123asd'
'19asd'
'12345asd'
'asd123'
'asd12'

zamienia się w

'19asd'
'123asd'
'12345asd'
'asd12'
'asd123'

Będzie to używane w połączeniu z rozwiązaniem innego pytania, które tutaj zadałem .

Funkcja sortowania sama w sobie działa, potrzebuję funkcji, która może powiedzieć, że „19asd” jest mniejsze niż „123asd”.

Piszę to w JavaScript.

Edycja: jak wskazał adormitu , szukam funkcji naturalnego sortowania

ptrn
źródło
zobacz także How do you do string comparison in JavaScript?na stackoverflow.com/questions/51165/…
Adrien Be
1
Oryginalne pytanie zostało zadane w 2010 roku, więc nie byłoby zaskoczeniem :)
ptrn

Odpowiedzi:

316

Jest to teraz możliwe w nowoczesnych przeglądarkach korzystających z localeCompare. Pomijając numeric: trueopcję, inteligentnie rozpozna liczby. Możesz rozróżniać wielkość liter przy użyciusensitivity: 'base' . Przetestowano w Chrome, Firefox i IE11.

Oto przykład. Wraca 1, co oznacza, że ​​10 idzie po 2:

'10'.localeCompare('2', undefined, {numeric: true, sensitivity: 'base'})

Aby uzyskać wydajność podczas sortowania dużej liczby ciągów, artykuł mówi:

Porównując dużą liczbę ciągów, na przykład przy sortowaniu dużych tablic, lepiej jest utworzyć obiekt Intl.Collator i użyć funkcji dostarczonej przez jego właściwość compare. Link do dokumentów

var collator = new Intl.Collator(undefined, {numeric: true, sensitivity: 'base'});
var myArray = ['1_Document', '11_Document', '2_Document'];
console.log(myArray.sort(collator.compare));

frodo2975
źródło
12
Jeśli chcesz posortować tablicę obiektów, możesz również użyć Collator: codepen.io/TimPietrusky/pen/rKzoGN
TimPietrusky
2
Aby wyjaśnić powyższy komentarz: „Jeśli argument loces nie został podany lub jest niezdefiniowany, używane są domyślne ustawienia regionalne środowiska wykonawczego”.
gkiely
46

Więc potrzebujesz naturalnego gatunku ?

Jeśli tak, to może ten scenariusz Briana Huismana oparty na pracy Davida Koelle'a byłby tym, czego potrzebujesz.

Wygląda na to, że rozwiązanie Briana Huismana jest teraz bezpośrednio hostowane na blogu Davida Koelle'a:

mhitza
źródło
Właściwy, naturalny rodzaj jest tym, czego szukam. Sprawdzę link, który wysłałeś, dzięki
ptrn
To bardzo nienaturalny rodzaj. Nie tworzy sortowania alfabetycznego.
tchrist
@tchrist: co masz na myśli, mówiąc „nie tworzy sortowania alfabetycznego?”
Adrien Be
Działa dobrze, ale nie obsługuje poprawnie liczb ujemnych. To znaczy: dałoby [„-1”. „-2”, „0”, „1”, „2”].
adrianboimvaser
2
@mhitza ten kod wydaje się działać dobrze github.com/litejs/natural-compare-lite zobacz szybki test jsbin.com/bevututodavi/1/edit?js,console
Adrien Be
23

Aby porównać wartości, możesz użyć metody porównawczej -

function naturalSorter(as, bs){
    var a, b, a1, b1, i= 0, n, L,
    rx=/(\.\d+)|(\d+(\.\d+)?)|([^\d.]+)|(\.\D+)|(\.$)/g;
    if(as=== bs) return 0;
    a= as.toLowerCase().match(rx);
    b= bs.toLowerCase().match(rx);
    L= a.length;
    while(i<L){
        if(!b[i]) return 1;
        a1= a[i],
        b1= b[i++];
        if(a1!== b1){
            n= a1-b1;
            if(!isNaN(n)) return n;
            return a1>b1? 1:-1;
        }
    }
    return b[i]? -1:0;
}

Ale aby przyspieszyć sortowanie tablicy, ustaw tablicę przed sortowaniem, tak aby konwersje na małe litery i wyrażenie regularne były wykonywane tylko raz, a nie w każdym kroku sortowania.

function naturalSort(ar, index){
    var L= ar.length, i, who, next, 
    isi= typeof index== 'number', 
    rx=  /(\.\d+)|(\d+(\.\d+)?)|([^\d.]+)|(\.(\D+|$))/g;
    function nSort(aa, bb){
        var a= aa[0], b= bb[0], a1, b1, i= 0, n, L= a.length;
        while(i<L){
            if(!b[i]) return 1;
            a1= a[i];
            b1= b[i++];
            if(a1!== b1){
                n= a1-b1;
                if(!isNaN(n)) return n;
                return a1>b1? 1: -1;
            }
        }
        return b[i]!= undefined? -1: 0;
    }
    for(i= 0; i<L; i++){
        who= ar[i];
        next= isi? ar[i][index] || '': who;
        ar[i]= [String(next).toLowerCase().match(rx), who];
    }
    ar.sort(nSort);
    for(i= 0; i<L; i++){
        ar[i]= ar[i][1];
    }
}
kennebec
źródło
czy to zadziała w moim przypadku, gdy wewnętrzna tablica będzie decydować o kolejności zewnętrznej?
ptrn
Co String.prototype.tlc()? Czy to twój własny kod, czy skądś go masz? Jeśli to drugie, podaj link do strony.
Andy E
przepraszam za błąd - poprawiony, dziękuję. Jeśli chcesz, aby a [1] i b [1] kontrolowały sortowanie, użyj a = String (a [1]). ToLowerCase (); b = String (b [1]). toLowerCase ();
kennebec
Właśnie miałem listę danych, które chciałem posortować, pomyślałem, że powinno to być łatwe do zrobienia w konsoli Chrome Dev Tools - dzięki za funkcję!
ajh158
9

Jeśli masz tablicę obiektów, możesz zrobić to w następujący sposób:

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

D0rm1nd0
źródło
1
Doskonała odpowiedź! Dziękuję Ci.
hubert17
5

Najbardziej w pełni funkcjonalna biblioteka do obsługi tego od 2019 wydaje się być naturalnie uporządkowana .

const { orderBy } = require('natural-orderby')

const unordered = [
  '123asd',
  '19asd',
  '12345asd',
  'asd123',
  'asd12'
]

const ordered = orderBy(unordered)

// [ '19asd',
//   '123asd',
//   '12345asd',
//   'asd12',
//   'asd123' ]

Nie tylko pobiera tablice ciągów, ale także może sortować według wartości określonego klucza w tablicy obiektów. Może również automatycznie identyfikować i sortować ciągi: walut, dat, waluty i wielu innych rzeczy.

Zaskakujące jest to, że po skompresowaniu gzip ma tylko 1,6 kB.

Julien
źródło
2

Wyobraź sobie 8-cyfrową funkcję wypełniania, która przekształca:

  • „123asd” -> „00000123asd”
  • „19asd” -> „00000019asd”

Możemy użyć wyściełanych ciągów, aby pomóc nam posortować „19asd” tak, aby pojawił się przed „123asd”.

Użyj wyrażenia regularnego, /\d+/gaby znaleźć wszystkie liczby, które należy uzupełnić:

str.replace(/\d+/g, pad)

Poniżej przedstawiono sortowanie przy użyciu tej techniki:

var list = [
    '123asd',
    '19asd',
    '12345asd',
    'asd123',
    'asd12'
];

function pad(n) { return ("00000000" + n).substr(-8); }
function natural_expand(a) { return a.replace(/\d+/g, pad) };
function natural_compare(a, b) {
    return natural_expand(a).localeCompare(natural_expand(b));
}

console.log(list.map(natural_expand).sort()); // intermediate values
console.log(list.sort(natural_compare)); // result

Wyniki pośrednie pokazują, co robi procedura natural_expand () i pozwalają zrozumieć, jak będzie działać następna procedura natural_compare:

[
  "00000019asd",
  "00000123asd",
  "00012345asd",
  "asd00000012",
  "asd00000123"
]

Wyjścia:

[
  "19asd",
  "123asd",
  "12345asd",
  "asd12",
  "asd123"
]
Stephen Quan
źródło
1

Opierając się na odpowiedzi @Adrien Be powyżej i używając kodu utworzonego przez Briana Huismana i Davida Koelle'a , oto zmodyfikowane sortowanie prototypów dla tablicy obiektów:

//Usage: unsortedArrayOfObjects.alphaNumObjectSort("name");
//Test Case: var unsortedArrayOfObjects = [{name: "a1"}, {name: "a2"}, {name: "a3"}, {name: "a10"}, {name: "a5"}, {name: "a13"}, {name: "a20"}, {name: "a8"}, {name: "8b7uaf5q11"}];
//Sorted: [{name: "8b7uaf5q11"}, {name: "a1"}, {name: "a2"}, {name: "a3"}, {name: "a5"}, {name: "a8"}, {name: "a10"}, {name: "a13"}, {name: "a20"}]

// **Sorts in place**
Array.prototype.alphaNumObjectSort = function(attribute, caseInsensitive) {
  for (var z = 0, t; t = this[z]; z++) {
    this[z].sortArray = new Array();
    var x = 0, y = -1, n = 0, i, j;

    while (i = (j = t[attribute].charAt(x++)).charCodeAt(0)) {
      var m = (i == 46 || (i >=48 && i <= 57));
      if (m !== n) {
        this[z].sortArray[++y] = "";
        n = m;
      }
      this[z].sortArray[y] += j;
    }
  }

  this.sort(function(a, b) {
    for (var x = 0, aa, bb; (aa = a.sortArray[x]) && (bb = b.sortArray[x]); x++) {
      if (caseInsensitive) {
        aa = aa.toLowerCase();
        bb = bb.toLowerCase();
      }
      if (aa !== bb) {
        var c = Number(aa), d = Number(bb);
        if (c == aa && d == bb) {
          return c - d;
        } else {
          return (aa > bb) ? 1 : -1;
        }
      }
    }

    return a.sortArray.length - b.sortArray.length;
  });

  for (var z = 0; z < this.length; z++) {
    // Here we're deleting the unused "sortArray" instead of joining the string parts
    delete this[z]["sortArray"];
  }
}
Eric Norcross
źródło