Javascript: натуральный вид буквенно-цифровых строк
Я ищу самый простой способ сортировки массива, состоящего из чисел и текста, а также их комбинации.
Например
'123asd'
'19asd'
'12345asd'
'asd123'
'asd12'
превращается в
'19asd'
'123asd'
'12345asd'
'asd12'
'asd123'
Это будет использоваться в сочетании с решением другого вопроса, который я здесь задавал.
Функция сортировки сама по себе работает, мне нужна функция, которая может сказать, что "19asd" меньше, чем "123asd".
Я пишу это в JavaScript.
Изменить: как Adormitu указал, что я ищу, это функция для естественной сортировки
7 ответов
Теперь это возможно в современных браузерах, использующих 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));
Если у вас есть массив объектов, вы можете сделать это так:
var myArrayObjects = [{
"id": 1,
"name": "1 example"
},
{
"id": 2,
"name": "100 example"
},
{
"id": 3,
"name": "12 example"
},
{
"id": 4,
"name": "5 example"
},
]
myArrayObjects = myArrayObjects.sort(function(a, b) {
return a.name.localeCompare(b.name, undefined, {
numeric: true,
sensitivity: 'base'
});
});
console.log(myArrayObjects);
Так вам нужен натуральный сорт?
Если так, то, возможно, вам понадобится сценарий Брайана Хьюсмана, основанный на творчестве Дэвида Коелла.
Похоже, что решение Брайана Хуисмана теперь прямо размещено в блоге Дэвида Коуля:
Для сравнения значений вы можете использовать метод сравнения
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];
}
}
Представьте себе 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
Промежуточные результаты показывают, что делает подпрограмма natural_expand(), и дают вам понимание того, как будет работать последующая подпрограмма natural_compare:
[
"00000019asd",
"00000123asd",
"00012345asd",
"asd00000012",
"asd00000123"
]
Выходы:
[
"19asd",
"123asd",
"12345asd",
"asd12",
"asd123"
]
Наиболее полнофункциональная библиотека для обработки данных по состоянию на 2019 г. выглядит вполне естественной.
const { orderBy } = require('natural-orderby')
const unordered = [
'123asd',
'19asd',
'12345asd',
'asd123',
'asd12'
]
const ordered = orderBy(unordered)
// [ '19asd',
// '123asd',
// '12345asd',
// 'asd12',
// 'asd123' ]
Он не только принимает массивы строк, но и может сортировать по значению определенного ключа в массиве объектов. Он также может автоматически идентифицировать и сортировать строки: валюты, даты, валюта и множество других вещей.
Удивительно, но это также только 1,6 КБ в сжатом виде.
Основываясь на ответе @Adrien Be выше и используя код, созданный Brian Huisman & David koelle, вот модифицированная сортировка прототипа для массива объектов:
//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"];
}
}