Сортировать массив объектов по значению свойства строки - PullRequest
2336 голосов
/ 15 июля 2009

У меня есть массив объектов JavaScript:

var objs = [ 
    { first_nom: 'Lazslo', last_nom: 'Jamf'     },
    { first_nom: 'Pig',    last_nom: 'Bodine'   },
    { first_nom: 'Pirate', last_nom: 'Prentice' }
];

Как мне отсортировать их по значению last_nom в JavaScript?

Я знаю о sort(a,b), но это работает только со строками и числами. Нужно ли добавлять toString() метод к моим объектам?

Ответы [ 41 ]

22 голосов
/ 30 августа 2017

Lodash.js (расширенный набор Underscore.js )

Хорошо не добавлять фреймворк для каждой простой логики, но полагаться на хорошо протестированные сервисные фреймворки, ускорить разработку и уменьшить количество написанных ошибок - не позор.

Lodash производит очень чистый код и продвигает более стиль функционального программирования , что приводит к меньшему количеству ошибок. Одним взглядом становится ясно, каково намерение, если код.

Проблема ОП может быть просто решена как:

const sortedObjs = _.sortBy(objs, 'last_nom');

Больше информации? Например. у нас есть следующий вложенный объект:

const users = [
  { 'user': {'name':'fred', 'age': 48}},
  { 'user': {'name':'barney', 'age': 36 }},
  { 'user': {'name':'wilma'}},
  { 'user': {'name':'betty', 'age': 32}}
];

Теперь мы можем использовать _. Property сокращение user.age, чтобы указать путь к свойству, которое должно соответствовать. Мы отсортируем пользовательские объекты по вложенному свойству age. Да, он позволяет сопоставлять вложенные свойства!

const sortedObjs = _.sortBy(users, ['user.age']);

Хотите перевернуть? Нет проблем. Используйте _. Reverse .

const sortedObjs = _.reverse(_.sortBy(users, ['user.age']));

Хотите объединить оба, используя Chain вместо?

const sortedObjs = _.chain(users).sortBy('user.age').reverse().value();
20 голосов
/ 05 мая 2016

Здесь есть много хороших ответов, но я хотел бы отметить, что они могут быть расширены очень просто для достижения гораздо более сложной сортировки. Единственное, что вам нужно сделать, это использовать оператор OR для цепочки функций сравнения, таких как:

objs.sort((a,b)=> fn1(a,b) || fn2(a,b) || fn3(a,b) )

Где fn1, fn2, ... - функции сортировки, которые возвращают [-1,0,1]. Это приводит к "сортировке по fn1", "сортировке по fn2", которая почти равна ORDER BY в SQL.

Это решение основано на поведении оператора ||, который оценивает первое вычисленное выражение , которое может быть преобразовано в истинное значение .

Простейшая форма имеет только одну встроенную функцию, подобную этой:

// ORDER BY last_nom
objs.sort((a,b)=> a.last_nom.localeCompare(b.last_nom) )

Наличие двух шагов с порядком сортировки last_nom, first_nom будет выглядеть так:

// ORDER_BY last_nom, first_nom
objs.sort((a,b)=> a.last_nom.localeCompare(b.last_nom) || 
                  a.first_nom.localeCompare(b.first_nom)  )

Общая функция сравнения может выглядеть примерно так:

// ORDER BY <n>
let cmp = (a,b,n)=>a[n].localeCompare(b[n])

Эта функция может быть расширена для поддержки числовых полей, чувствительности к регистру, произвольных типов данных и т. Д.

Вы можете использовать его с цепочкой их по приоритету сортировки:

// ORDER_BY last_nom, first_nom
objs.sort((a,b)=> cmp(a,b, "last_nom") || cmp(a,b, "first_nom") )
// ORDER_BY last_nom, first_nom DESC
objs.sort((a,b)=> cmp(a,b, "last_nom") || -cmp(a,b, "first_nom") )
// ORDER_BY last_nom DESC, first_nom DESC
objs.sort((a,b)=> -cmp(a,b, "last_nom") || -cmp(a,b, "first_nom") )

Дело в том, что чистый JavaScript с функциональным подходом может пройти долгий путь без внешних библиотек или сложного кода. Это также очень эффективно, так как не нужно разбирать строки

19 голосов
/ 30 апреля 2014

Пример использования:

objs.sort(sortBy('last_nom'));

Сценарий:

/**
 * @description
 * Returns a function which will sort an
 * array of objects by the given key.
 *
 * @param  {String}  key
 * @param  {Boolean} reverse
 * @return {Function}
 */
const sortBy = (key, reverse) => {

  // Move smaller items towards the front
  // or back of the array depending on if
  // we want to sort the array in reverse
  // order or not.
  const moveSmaller = reverse ? 1 : -1;

  // Move larger items towards the front
  // or back of the array depending on if
  // we want to sort the array in reverse
  // order or not.
  const moveLarger = reverse ? -1 : 1;

  /**
   * @param  {*} a
   * @param  {*} b
   * @return {Number}
   */
  return (a, b) => {
    if (a[key] < b[key]) {
      return moveSmaller;
    }
    if (a[key] > b[key]) {
      return moveLarger;
    }
    return 0;
  };
};
17 голосов
/ 20 октября 2017

У меня есть кусок кода, который работает для меня:

arr.sort((a, b) => a.name > b.name)

ОБНОВЛЕНИЕ: не всегда работает, поэтому это не правильно: (

16 голосов
/ 18 июля 2018

Я не видел, чтобы этот конкретный подход предлагался, поэтому вот краткий метод сравнения, который мне нравится использовать, который работает как для string, так и для number:

const objs = [ 
  { first_nom: 'Lazslo', last_nom: 'Jamf'     },
  { first_nom: 'Pig',    last_nom: 'Bodine'   },
  { first_nom: 'Pirate', last_nom: 'Prentice' }
];

const sortBy = fn => (a, b) => {
  const fa = fn(a)
  const fb = fn(b)
  return -(fa < fb) || +(fa > fb)
}
const getLastName = o => o.last_nom
const sortByLastName = sortBy(getLastName)

objs.sort(sortByLastName)
console.log(objs.map(getLastName))

Вот объяснение sortBy():

sortBy() принимает fn, который выбирает какое значение из объекта для использования в качестве сравнения, и возвращает функцию, которая может быть передана непосредственно в Array.prototype.sort(). В этом примере мы используем o.last_nom в качестве значения для сравнения, поэтому всякий раз, когда мы получаем два объекта через Array.prototype.sort(), например

{ first_nom: 'Lazslo', last_nom: 'Jamf' }

и

{ first_nom: 'Pig', last_nom: 'Bodine' }

мы используем

(a, b) => {
  const fa = fn(a)
  const fb = fn(b)
  return -(fa < fb) || +(fa > fb)
}

чтобы сравнить их.

Помня, что fn = o => o.last_nom, мы можем расширить функцию сравнения до эквивалентной

(a, b) => {
  const fa = a.last_nom
  const fb = b.last_nom
  return -(fa < fb) || +(fa > fb)
}

Логический оператор ИЛИ || имеет функцию короткого замыкания, которая очень полезна здесь. Из-за того, как это работает, тело функции выше означает

if (fa < fb) return -1
return +(fa > fb)

Так что если fa < fb мы вернем -1, в противном случае fa > fb вернем +1, но если fa == fb, тогда fa < fb и fa > fb равны false, поэтому он возвращает +0.

В качестве дополнительного бонуса, вот эквивалент в ECMAScript 5 без функций стрелок, который, к сожалению, более многословен:

var objs = [ 
  { first_nom: 'Lazslo', last_nom: 'Jamf'     },
  { first_nom: 'Pig',    last_nom: 'Bodine'   },
  { first_nom: 'Pirate', last_nom: 'Prentice' }
];

var sortBy = function (fn) {
  return function (a, b) {
    var fa = fn(a)
    var fb = fn(b)
    return -(fa < fb) || +(fa > fb)
  }
}

var getLastName = function (o) { return o.last_nom }
var sortByLastName = sortBy(getLastName)

objs.sort(sortByLastName)
console.log(objs.map(getLastName))
15 голосов
/ 06 ноября 2016

Я знаю, что этот вопрос слишком старый, но я не видел ни одной реализации, похожей на мою.
Эта версия основана на идиоме преобразования Шварца.

function sortByAttribute(array, ...attrs) {
  // generate an array of predicate-objects contains
  // property getter, and descending indicator
  let predicates = attrs.map(pred => {
    let descending = pred.charAt(0) === '-' ? -1 : 1;
    pred = pred.replace(/^-/, '');
    return {
      getter: o => o[pred],
      descend: descending
    };
  });
  // schwartzian transform idiom implementation. aka: "decorate-sort-undecorate"
  return array.map(item => {
    return {
      src: item,
      compareValues: predicates.map(predicate => predicate.getter(item))
    };
  })
  .sort((o1, o2) => {
    let i = -1, result = 0;
    while (++i < predicates.length) {
      if (o1.compareValues[i] < o2.compareValues[i]) result = -1;
      if (o1.compareValues[i] > o2.compareValues[i]) result = 1;
      if (result *= predicates[i].descend) break;
    }
    return result;
  })
  .map(item => item.src);
}

Вот пример того, как его использовать:

let games = [
  { name: 'Pako',              rating: 4.21 },
  { name: 'Hill Climb Racing', rating: 3.88 },
  { name: 'Angry Birds Space', rating: 3.88 },
  { name: 'Badland',           rating: 4.33 }
];

// sort by one attribute
console.log(sortByAttribute(games, 'name'));
// sort by mupltiple attributes
console.log(sortByAttribute(games, '-rating', 'name'));
14 голосов
/ 10 августа 2015

Сортировка (больше) сложных массивов объектов

Поскольку вы, вероятно, сталкиваетесь с более сложными структурами данных, такими как этот массив, я бы расширил решение.

TL; DR

Более подключаемая версия на основе @ ege-Özcan очень мило ответ .

Задача

Я столкнулся с нижеследующим и не смог его изменить. Я также не хотел временно сплющивать объект. Также я не хотел использовать подчеркивание / lodash, в основном из соображений производительности и для удовольствия реализовать это сам.

var People = [
   {Name: {name: "Name", surname: "Surname"}, Middlename: "JJ"},
   {Name: {name: "AAA", surname: "ZZZ"}, Middlename:"Abrams"},
   {Name: {name: "Name", surname: "AAA"}, Middlename: "Wars"}
];

Цель

Цель состоит в том, чтобы отсортировать его в основном по People.Name.name, а затем по People.Name.surname

Препятствия

Теперь в базовом решении используются скобочные обозначения для вычисления свойств, которые сортируются динамически. Здесь, однако, мы должны были бы также динамически создавать обозначение в скобках, так как можно было бы ожидать, что сработает как People['Name.name'], что не работает.

Простое выполнение People['Name']['name'], с другой стороны, является статичным и позволяет вам только спуститься на n -й уровень.

Решение

Основным дополнением здесь будет прогулка по дереву объектов и определение значения последнего листа, который вы должны указать, а также любого промежуточного листа.

var People = [
   {Name: {name: "Name", surname: "Surname"}, Middlename: "JJ"},
   {Name: {name: "AAA", surname: "ZZZ"}, Middlename:"Abrams"},
   {Name: {name: "Name", surname: "AAA"}, Middlename: "Wars"}
];

People.sort(dynamicMultiSort(['Name','name'], ['Name', '-surname']));
// Results in...
// [ { Name: { name: 'AAA', surname: 'ZZZ' }, Middlename: 'Abrams' },
//   { Name: { name: 'Name', surname: 'Surname' }, Middlename: 'JJ' },
//   { Name: { name: 'Name', surname: 'AAA' }, Middlename: 'Wars' } ]

// same logic as above, but strong deviation for dynamic properties 
function dynamicSort(properties) {
  var sortOrder = 1;
  // determine sort order by checking sign of last element of array
  if(properties[properties.length - 1][0] === "-") {
    sortOrder = -1;
    // Chop off sign
    properties[properties.length - 1] = properties[properties.length - 1].substr(1);
  }
  return function (a,b) {
    propertyOfA = recurseObjProp(a, properties)
    propertyOfB = recurseObjProp(b, properties)
    var result = (propertyOfA < propertyOfB) ? -1 : (propertyOfA > propertyOfB) ? 1 : 0;
    return result * sortOrder;
  };
}

/**
 * Takes an object and recurses down the tree to a target leaf and returns it value
 * @param  {Object} root - Object to be traversed.
 * @param  {Array} leafs - Array of downwards traversal. To access the value: {parent:{ child: 'value'}} -> ['parent','child']
 * @param  {Number} index - Must not be set, since it is implicit.
 * @return {String|Number}       The property, which is to be compared by sort.
 */
function recurseObjProp(root, leafs, index) {
  index ? index : index = 0
  var upper = root
  // walk down one level
  lower = upper[leafs[index]]
  // Check if last leaf has been hit by having gone one step too far.
  // If so, return result from last step.
  if (!lower) {
    return upper
  }
  // Else: recurse!
  index++
  // HINT: Bug was here, for not explicitly returning function
  // https://stackoverflow.com/a/17528613/3580261
  return recurseObjProp(lower, leafs, index)
}

/**
 * Multi-sort your array by a set of properties
 * @param {...Array} Arrays to access values in the form of: {parent:{ child: 'value'}} -> ['parent','child']
 * @return {Number} Number - number for sort algorithm
 */
function dynamicMultiSort() {
  var args = Array.prototype.slice.call(arguments); // slight deviation to base

  return function (a, b) {
    var i = 0, result = 0, numberOfProperties = args.length;
    // REVIEW: slightly verbose; maybe no way around because of `.sort`-'s nature
    // Consider: `.forEach()`
    while(result === 0 && i < numberOfProperties) {
      result = dynamicSort(args[i])(a, b);
      i++;
    }
    return result;
  }
}
* * Пример 1 042

Рабочий пример в JSBin

11 голосов
/ 26 июня 2016

Еще один вариант:

var someArray = [...];

function generateSortFn(prop, reverse) {
    return function (a, b) {
        if (a[prop] < b[prop]) return reverse ? 1 : -1;
        if (a[prop] > b[prop]) return reverse ? -1 : 1;
        return 0;
    };
}

someArray.sort(generateSortFn('name', true));

сортирует по возрастанию по умолчанию.

10 голосов
/ 15 января 2016

Простой способ:

objs.sort(function(a,b) {
  return b.last_nom.toLowerCase() < a.last_nom.toLowerCase();
});

Смотрите, что '.toLowerCase()' необходимо для предотвращения ошибок в сравнении строк.

9 голосов
/ 17 сентября 2012

дополнительные параметры desc для Ege Özcan код

function dynamicSort(property, desc) {
    if (desc) {
        return function (a, b) {
            return (a[property] > b[property]) ? -1 : (a[property] < b[property]) ? 1 : 0;
        }   
    }
    return function (a, b) {
        return (a[property] < b[property]) ? -1 : (a[property] > b[property]) ? 1 : 0;
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...