2013-03-18 10 views
34

Mam tablicę podobną;Sortuj elementy szyku (ciąg z liczbami), sortowanie naturalne

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

Potrzebuję go posortować tak, aby wyglądał jak;

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

Próbowałem funkcji sortowania;

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

ale to daje rozkaz

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

starałem się myśleć o regex, który będzie działał, ale nie może uzyskać moja głowa wokół niego.
Jeśli to pomaga, format będzie zawsze wynosił 2 litery, x ilość cyfr, a następnie dowolną liczbę znaków.

+0

List pierwszy, następnie numer? –

+0

@BradChristie, tak sortuj według pierwszych dwóch liter, a następnie liczb, pozostałe znaki nie są wymagane (ale byłoby miłe) – Rooneyl

+0

Więc jeśli miałeś "IL10 Hello' &" IL10 Bob', który jest pierwszy? –

Odpowiedz

71

Nazywa się to "naturalny porządek" i mogą być realizowane w JS tak:

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

Aby posortować w odwrotnej kolejności, po prostu zamienić argumenty:

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

lub po prostu

test = test.sort(naturalCompare).reverse(); 
+1

Wydaje się, że preferuję ciągi, które zaczynają się od znaków alfabetycznych nad znakami numerycznymi. Naprawiono, zastępując '$ 1 || 0' z 'typeof ($ 1) == 'undefined'? Infinity: $ 1' – colllin

+2

Czy ktokolwiek może pomóc, w jaki sposób można to zaimplementować zarówno do rosnącego, jak i malejącego sortowania tablic? – anusreemn

+0

@ user3513687: zobacz aktualizację – georg

0

Nie ładne, ale sprawdź pierwsze dwa kody znaków. Jeśli wszyscy równi parse i porównać numery:

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

Example

+0

, który daje mi kolejność ["IL0 Foo", "IL10 Baz", "IL3 Bob mówi" cześć "," PI0 Bar "]. IL3 powinien być drugim elementem – Rooneyl

+0

@Rooneyl, tak, muszę dodać + do wyrażenia regularnego. Zaktualizowałem – Joe

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

Można zrobić regex tak, aby uzyskać non-numeryczne i numerycznych części napisu:

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

zwraca: ["foo", "124" , "bar" , "23"]

Następnie w funkcji porównywania możesz wykonać iterację szorstkie części obu strun porównując je po części. Pierwsza nie pasująca część określa wynik ogólnego porównania. Dla każdej części sprawdź, czy część zaczyna się od cyfry, a jeśli tak, przeanalizuj ją jako liczbę przed wykonaniem porównania.

1

Dodaj jeszcze jedną alternatywę (dlaczego nie):

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

jsfiddle example

3

lubiłem rozwiązanie Georga dużo, ale ja potrzebowałem podkreślenia ("_") do sortowania przed liczbami. Oto jak ja zmodyfikował swój kod:

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

Pad liczb w ciąg z zerem na początku, a następnie rodzaj normalnie.

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

2

Można użyć z options

wrażliwości

Jakie różnice w struny powinno doprowadzić do wartości wynikowych niezerowe. Możliwe wartości to:

  • "base": Tylko łańcuchy różniące się literami podstawowymi są porównywane jako nierówne. Przykłady: a ≠ b, a = á, a = A.
  • "accent": Tylko ciągi, które różnią się literami podstawowymi lub akcentami i innymi znakami diakrytycznymi, są porównywane jako nierównomierne. Przykłady: a ≠ b, a ≠ á, a = A.
  • "case": Tylko ciągi, które różnią się literami podstawowymi lub wielkością liter, są porównywane jako nierównomierne. Przykłady: a ≠ b, a = á, a ≠ A.
  • "variant": Łańcuchy różniące się literami podstawowymi, akcentami i innymi znakami diakrytycznymi lub przypadkiem są porównywane jako nierównomierne. Inne różnice również mogą być brane pod uwagę. Przykłady: a ≠ b, a ≠ á, a ≠ A.

Domyślnym "wariantem" jest użycie "sort"; jego lokalizacja zależy od użycia "szukaj".

numeryczny

czy porównanie numeryczny powinien być używany tak, że "1" < "2" < "10". Możliwe wartości to true i false; wartość domyślna to false. Opcję tę można ustawić za pomocą właściwości options lub klucza rozszerzenia Unicode; jeśli oba są podane, właściwość options ma pierwszeństwo. Implementacje nie są wymagane do obsługi tej właściwości.

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