Сортировка элементов массива (строка с числами), естественная сортировка
У меня есть массив вроде:
["IL0 Foo", "PI0 Bar", "IL10 Baz", "IL3 Bob says hello"]
И нужно отсортировать его так, чтобы он выглядел следующим образом:
["IL0 Foo", "IL3 Bob says hello", "IL10 Baz", "PI0 Bar"]
Я попробовал функцию сортировки;
function compare(a,b) {
if (a < b)
return -1;
if (a > b)
return 1;
return 0;
}
но это дает порядок
["IL0 Foo", "IL10 Baz", "IL3 Bob says hello", "PI0 Bar"]
Я пытался подумать о регулярном выражении, которое будет работать, но не может окунуться в него.
Если это помогает, формат всегда будет 2 буквы, x количество чисел, затем любое количество символов.
Ответы
Ответ 1
Это называется "естественной сортировкой" и может быть реализовано в JS следующим образом:
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));
Ответ 2
Вы можете использовать String#localeCompare
с options
чувствительность
Какие различия в строках должны приводить к ненулевым значениям результата. Возможные значения:
-
"base"
: только строки, отличающиеся базовыми буквами, сравниваются как неравные. Примеры: a ≠ b
, a = á
, a = A
-
"accent"
: только строки, которые отличаются базовыми буквами или акцентами и другими диакритическими знаками, сравниваются как неравные. Примеры: a ≠ b
, a ≠ á
, a = A
-
"case"
: только строки, которые отличаются базовыми буквами или случаем, сравниваются как неравные. Примеры: a ≠ b
, a = á
, a ≠ A
-
"variant"
: строки, которые отличаются буквенными буквами, акцентами и другими диакритическими знаками, или случайные сравнения как неравные. Другие различия также могут быть приняты во внимание. Примеры: a ≠ b
, a ≠ á
, a ≠ A
По умолчанию используется "вариант" для использования "sort"; это зависит от языка для поиска "поиск".
числовой
Следует ли использовать числовую сортировку, такую, что "1" <"2" <"10". Возможные значения: true
и false
; значение по умолчанию - false
. Эта опция может быть задана с помощью свойства параметров или с помощью расширительного ключа Unicode; если оба они предоставлены, свойство options
имеет приоритет. Реализации не требуются для поддержки этого свойства.
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);
Ответ 3
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;
});
Ответ 4
Мне очень понравилось решение georg, но мне нужны были подчеркивания ( "_" ) для сортировки перед номерами. Здесь, как я изменил его код:
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));
Ответ 5
Номера пэдов в строке с ведущими нулями, затем сортировка обычно.
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)));
Ответ 6
Вы можете сделать такое регулярное выражение, чтобы получить нечисловые и числовые части строки:
var s = "foo124bar23";
s.match(/[^\d]+|\d+/g)
возвращает: ["foo", "124" , "bar" , "23"]
Затем в вашей функции сравнения вы можете перебирать части двух строк, сравнивая их по частям. Первая несогласованная часть определяет результат общего сравнения. Для каждой части проверьте, начинается ли эта часть с цифры, и если это нужно проанализировать как число перед выполнением сравнения.
Ответ 7
Добавьте еще одну альтернативу (почему бы и нет):
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
Ответ 8
Не очень, но проверьте первые два кода char. Если все равно разобрать и сравнить числа:
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;
}
});
Пример
Ответ 9
Это хорошо! Это то, что я искал!