Сортировка "расширенных" числовых строк по числовому порядку

Представьте себе этот массив:

var fees = [
    '$0.9 + $0.1',
    '$20 + $2',
    '$0.7 + $0.4',
    '$5 + $0.5',
    '$0 + $0.01',
    '$100 + $9',
    '$1 + $1',
    '$2 + $0.5'
];

Как бы я, используя ванильный JavaScript, сортировал эти строковые значения в порядке возрастания чисел?

Желаемый вывод после сортировки:

['$ 0 + $ 0,01', '$ 0,7 + $0,4', '$0,9 + $0,1', '$1 + $1', '$2 + $0,5', '$5 + $0,5', '$20 + $2', '$100 + $9']

Я попробовал следующее:

function mySort(a, b) {
    return ((a < b) ? -1 : ((a > b) ? 1 : 0));
}

Но это просто выводит:

["$ 0 + $ 0,01", "$ 0,7 + $0,4", "$0,9 + $.1", "$1 + $1", "$100 + $9", "$2 + $0,5", "$20 + $2", "$5 + $0,5"]`

Возможно ли это аккуратно и логично?

Я не хочу получать сумму, так как она даст нежелательные результаты. Рассмотрим пример "$0.9 + $0.1" а также "$0.7 + $0.4", "$0.9 + $0.1" будет меньшее значение, так как сумма 1, но я бы хотел отсортировать так, чтобы "$0.7 + $0.4" является более низким значением вместо. Таким образом, в основном нужно отсортировать по первому числу по возрастанию, и если первое число совпадает между двумя значениями, то сортировать по второму числу

3 ответа

Решение

Я бы использовал этот трехшаговый алгоритм:

  1. добавить к каждому из элементов два значения, которые будут отсортированы по
  2. сортировать по этим значениям
  3. сбросьте эту дополнительную информацию снова

Вот код ES6.

var result = fees.map( s => [s].concat(s.match(/[\d.]+/g).map(Number)) )
                .sort( (a, b) => a[1] - b[1] || a[2] - b[2])
                .map( a => a[0] );

s.match() Вызов будет производить массив с двумя совпадениями (строки). map() вызов превратит их в номера, и concat() добавим эти два числа, чтобы сформировать триплет с исходной строкой.

Сортировка обратного вызова будет сортировать по первому номеру (который имеет индекс 1), и, если он равен (тогда разница равна 0), второй номер будет использоваться для сортировки.

Финал map возьмет исходную строку из триплетов, тем самым отбросив дополнительную информацию, которая использовалась для сортировки.

В приведенном ниже фрагменте я добавил один элемент к вашим образцам данных ($5.1 + $0.1), который покажет разницу с выводом, который вы получите, если отсортировать по суммам (которые вам не нужны, как указано в обновлении вопроса).

var fees = [
    '$0.9 + $.1',
    '$20 + $2',
    '$5 + $0.5',
    '$5.1 + $0.1', // smaller sum than previous, but larger first value
    '$0 + $0.01',
    '$100 + $9',
    '$1 + $1',
    '$2 + $0.5'
];

// 1. add the sum to each of the elements
// 2. sort by the sums
// 3. drop the sums
var result = fees.map( s => [s].concat(s.match(/[\d.]+/g).map(Number)) )
                .sort( (a, b) => a[1] - b[1] || a[2] - b[2])
                .map( a => a[0] );

console.log(result);

Этот способ будет применять регулярное выражение к каждой строке только один раз, что не всегда происходит, когда вы делаете это на лету внутри sort Перезвоните. Алгоритм сортировки, как правило, должен сравнивать одно и то же значение несколько раз, поэтому вы повышаете производительность при удалении логики из sort Обратный звонок, где это возможно.

Сортировка по сумме чисел в строке.

var fees = ['$0.9 + $.1', '$20 + $2', '$5 + $0.5', '$0 + $0.01', '$100 + $9', '$1 + $1', '$2 + $0.5'];

fees.sort(function(a, b) {
  return getSum(a) - getSum(b);
})

function getSum(str) {
  return str
    // remove the $ and space
    .replace(/[^\d+.]/g, '')
    //split by + symbol
    .split('+')
    // get the sum
    .reduce(function(sum, s) {
      // parse and add with sum
      return sum + (Number(s) || 0);
      // set inital value as sum
    }, 0)
}

console.log(fees);


Вы можете ускорить процесс, используя дополнительный объект, который содержит сумму.

var fees = ['$0.9 + $.1', '$20 + $2', '$5 + $0.5', '$0 + $0.01', '$100 + $9', '$1 + $1', '$2 + $0.5'];

var ref = fees.reduce(function(obj, str) {
  // define object property if not defined
  obj[str] = str in obj || str
    // remove the $ and space
    .replace(/[^\d+.]/g, '')
    //split by + symbol
    .split('+')
    // get the sum
    .reduce(function(sum, s) {
      // parse and add with sum
      return sum + (Number(s) || 0);
      // set inital value as sum
    }, 0);
  // return the object reference
  return obj;
  // set initial value as an empty objecct
}, {})

fees.sort(function(a, b) {
  return ref[a] - ref[b];
})



console.log(fees);


ОБНОВЛЕНИЕ: Поскольку вы обновили вопрос, вам нужно сравнить отдельные части.

var fees = ['$0.9 + $.1', '$20 + $2', '$5 + $0.5', '$0 + $0.01', '$100 + $9', '$1 + $1', '$2 + $0.5'];

fees.sort(function(a, b) {
  // get numbers from a
  var arrA = a.replace(/[^\d.+]/g, '').split('+');
  // get numbers from b
  var arrB = b.replace(/[^\d.+]/g, '').split('+');

  // generate sort value
  return arrA[0] - arrB[0] || arrA[1] - arrB[1];
})

console.log(fees);

Вы можете получить части строк, добавить их и взять для сортировки дельту двух элементов.

var fees = ['$0.9 + $.1', '$20 + $2', '$5 + $0.5', '$0 + $0.01', '$100 + $9', '$1 + $1', '$2 + $0.5'];

fees.sort(function (a, b) {
    function getValues(s) {
        return s.match(/([0-9.]+)/g).map(Number);
    }
    
    var aa = getValues(a),
        bb = getValues(b);
    return aa[0] + aa[1] - (bb[0] + bb[1]);
});

console.log(fees);

Другие вопросы по тегам