Javascript: натуральний вид буквено-цифрових рядків


173

Я шукаю найпростіший спосіб сортування масиву, який складається з чисел та тексту, та їх комбінації.

Напр

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

перетворюється на

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

Це буде використано в поєднанні з вирішенням іншого питання, яке я тут задав .

Функція сортування сама по собі працює, що мені потрібно, це функція, яка може сказати, що "19asd" менше, ніж "123asd".

Я пишу це на JavaScript.

Редагувати: як зазначав adormitu , те, що я шукаю, - це функція природного сортування


дивіться також How do you do string comparison in JavaScript?на stackoverflow.com/questions/51165/…
Adrien Be

1
Оригінальне запитання було задано у 2010 році, так що це не буде дивно :)
ptrn

Можливий дублікат способу сортування рядків у JavaScript
feeela

Відповіді:


316

Зараз це можливо в сучасних браузерах за допомогою localeCompare. Передавши numeric: trueопцію, вона розумно розпізнає числа. Ви можете зробити нечутливі до регістру, використовуючи sensitivity: 'base'. Тестовано в Chrome, Firefox та IE11.

Ось приклад. Він повертається 1, тобто 10 йде після 2:

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

Для продуктивності при сортуванні великої кількості рядків у статті йдеться про:

Порівнюючи велику кількість рядків, наприклад, при сортуванні великих масивів, краще створити об’єкт Intl.Collator і використовувати функцію, надану його властивістю порівняння. Документ посилання

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


12
Якщо ви хочете сортувати масив об’єктів, ви також можете скористатися Collator: codepen.io/TimPietrusky/pen/rKzoGN
TimPietrusky

2
Для уточнення вище зауваження: "Якщо аргумент локалів не надано або не визначено, використовується локаль за умовами виконання за умовчанням."
gkiely

46

Тож вам потрібен натуральний сорт ?

Якщо так, то, можливо, цей сценарій Брайана Хайсмана, заснований на роботі Девіда Коелла, був би тим, що вам потрібно.

Схоже, рішення Брайана Хайсмана зараз безпосередньо розміщено в блозі Девіда Коелла:


Правильний, природний сорт - це те, що я шукаю. Я перегляну посилання, яке ви надіслали, спасибі
ptrn

Це дуже неприродний сорт. Це не дає альфбетичного сорту.
tchrist

@tchrist: що ви маєте на увазі під "алфавітним сортом?"
Адрієн Бе

Це прекрасно, але він не обробляє негативні числа правильно. Тобто: це дало б ['-1'. '-2', '0', '1', '2'].
adrianboimvaser

2
@mhitza цей код, здається, добре справляється github.com/litejs/natural-compare-lite дивіться швидкий тест jsbin.com/bevututodavi/1/edit?js,console
Adrien Be

23

Для порівняння значень можна використовувати метод порівняння-

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

Але для того, щоб швидко сортувати масив, обробляйте масив перед сортуванням, тому вам потрібно робити лише малі перетворення та регулярний вираз один раз замість кожного кроку через сортування.

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

чи працювало б це в моєму випадку, коли внутрішній масив визначав порядок зовнішнього?
ptrn

Що String.prototype.tlc()? Це ваш власний код чи ви його звідкись отримали? Якщо останні, будь ласка, посилання на сторінку.
Енді Е

вибачте за помилку - виправлено, дякую. Якщо ви хочете, щоб [1] і b [1] контролювали сортування, використовуйте a = String (a [1]). ToLowerCase (); b = рядок (b [1]). toLowerCase ();
kennebec

Щойно у мене був список даних, які я хотів сортувати, я вважав, що це легко зробити на консолі Chrome Dev Tools - дякую за функцію!
ajh158

9

Якщо у вас є масив об'єктів, ви можете зробити так:

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


1
Ідеальна відповідь! Дякую.
hubert17

5

Найповніша бібліотека для вирішення цього питання на 2019 рік, здається, є впорядкованому порядку .

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

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

const ordered = orderBy(unordered)

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

Він не тільки приймає масиви рядків, але також може сортувати за значенням певного ключа в масиві об'єктів. Він також може автоматично визначати і сортувати рядки: валюти, дати, валюту та купу інших речей.

Дивно, але це також лише 1,6 кБ, коли gzipped.


2

Уявіть 8-значну функцію прокладки, яка перетворює:

  • '123asd' -> '00000123asd'
  • '19asd' -> '00000019asd'

Ми можемо використовувати м'які рядки, щоб допомогти нам сортувати "19asd", щоб відображатися перед "123asd".

Використовуйте регулярний вираз, /\d+/gщоб допомогти знайти всі цифри, які потрібно замістити:

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

Далі показано сортування за цією методикою:

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

Проміжні результати показують, що виконує звичайна процедура_на_копичення () і дають зрозуміти, як буде працювати наступний звичайний звичайний порядок:

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

Виходи:

[
  "19asd",
  "123asd",
  "12345asd",
  "asd12",
  "asd123"
]

1

Спираючись на відповідь @Adrien Be вище та використовуючи код, який створили Брайан Хуйсман та Девід Колле , ось модифікований прототип сортування для масиву об’єктів:

//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"];
  }
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.