Сортировка массива объектов по значению свойства string

у меня есть массив объектов 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() метод для моих объектов?

30 ответов


достаточно просто написать свою собственную функцию сравнения:

function compare(a,b) {
  if (a.last_nom < b.last_nom)
    return -1;
  if (a.last_nom > b.last_nom)
    return 1;
  return 0;
}

objs.sort(compare);

или inline (c/o Marco Demaio):

objs.sort((a,b) => (a.last_nom > b.last_nom) ? 1 : ((b.last_nom > a.last_nom) ? -1 : 0)); 

вы также можете создать динамическую функцию сортировки, которая сортирует объекты по их значению, которое вы передаете:

function dynamicSort(property) {
    var sortOrder = 1;
    if(property[0] === "-") {
        sortOrder = -1;
        property = property.substr(1);
    }
    return function (a,b) {
        var result = (a[property] < b[property]) ? -1 : (a[property] > b[property]) ? 1 : 0;
        return result * sortOrder;
    }
}

таким образом, вы можете иметь массив таких объектов:

var People = [
    {Name: "Name", Surname: "Surname"},
    {Name:"AAA", Surname:"ZZZ"},
    {Name: "Name", Surname: "AAA"}
];

...и это сработает, когда вы сделаете:

People.sort(dynamicSort("Name"));
People.sort(dynamicSort("Surname"));
People.sort(dynamicSort("-Surname"));

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

несколько Параметры

вы можете использовать функцию ниже для создания функций сортировки с несколькими параметрами сортировки.

function dynamicSortMultiple() {
    /*
     * save the arguments object as it will be overwritten
     * note that arguments object is an array-like object
     * consisting of the names of the properties to sort by
     */
    var props = arguments;
    return function (obj1, obj2) {
        var i = 0, result = 0, numberOfProperties = props.length;
        /* try getting a different result from 0 (equal)
         * as long as we have extra properties to compare
         */
        while(result === 0 && i < numberOfProperties) {
            result = dynamicSort(props[i])(obj1, obj2);
            i++;
        }
        return result;
    }
}

что позволило бы вам сделать что-то вроде этого:

People.sort(dynamicSortMultiple("Name", "-Surname"));

Добавление Его В Прототип

(реализация, которая находится чуть ниже вдохновлен Майк Р ' s ответ)

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


подчеркивания.js

используйте подчеркивание, его маленький и удивительный...

sortBy_.sortBy (list, iterator, [context]) возвращает отсортированную копию список, ранжированный в порядке возрастания по результатам выполнения каждого значения через итератор. Iterator также может быть строковым именем свойства Сортировать по (например. длина.)

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

var sortedObjs = _.sortBy( objs, 'first_nom' );

в ES6 / ES2015 или более поздней версии вы можете сделать так:

objs.sort((a, b) => a.last_nom.localeCompare(b.last_nom));

не понимаю, почему люди все усложняют:

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

для более строгих двигателей:

objs.sort(function(a, b){
  return a.last_nom == b.last_nom ? 0 : +(a.last_nom > b.last_nom) || -1;
});

поменяйте оператор, чтобы он был отсортирован по обратному алфавитному порядку.


если у вас есть дубликаты фамилий, вы можете сортировать их по имени -

obj.sort(function(a,b){
  if(a.last_nom< b.last_nom) return -1;
  if(a.last_nom >b.last_nom) return 1;
  if(a.first_nom< b.first_nom) return -1;
  if(a.first_nom >b.first_nom) return 1;
  return 0;
});

простое и быстрое решение этой проблемы с использованием наследования прототипов:

Array.prototype.sortBy = function(p) {
  return this.slice(0).sort(function(a,b) {
    return (a[p] > b[p]) ? 1 : (a[p] < b[p]) ? -1 : 0;
  });
}

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

objs = [{age:44,name:'vinay'},{age:24,name:'deepak'},{age:74,name:'suresh'}];

objs.sortBy('age');
// Returns
// [{"age":24,"name":"deepak"},{"age":44,"name":"vinay"},{"age":74,"name":"suresh"}]

objs.sortBy('name');
// Returns
// [{"age":24,"name":"deepak"},{"age":74,"name":"suresh"},{"age":44,"name":"vinay"}]

обновление: больше не изменяет исходный массив.


вместо использования пользовательской функции сравнения вы также можете создать тип объекта с помощью custom toString() метод (который вызывается функция сравнения по умолчанию):

function Person(firstName, lastName) {
    this.firtName = firstName;
    this.lastName = lastName;
}

Person.prototype.toString = function() {
    return this.lastName + ', ' + this.firstName;
}

var persons = [ new Person('Lazslo', 'Jamf'), ...]
persons.sort();

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

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

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

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

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

// 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])

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

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

// 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 с функциональным подходом может пройти долгий путь без внешних библиотек или сложного кода. Это также очень эффективно, так как никакой синтаксический анализ строк не должен выполняться


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

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}     
 */
function 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.
  var 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.
  var moveLarger = reverse ? -1 : 1;

  /**
   * @param  {*} a
   * @param  {*} b
   * @return {Number}
   */
  return function(a, b) {
    if (a[key] < b[key]) {
      return moveSmaller;
    }
    if (a[key] > b[key]) {
      return moveLarger;
    }
    return 0;
  };

}

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

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'));

Лодашь.js (надмножество подчеркивания.js)

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

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

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

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

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

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

хотите поменять местами? Не проблема. Использовать _.обратный.

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

хотите объединить оба с помощью сцепление?

const sortedObjs = _.chain(users).sortBy('user.age').reverse().value();

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

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

TL; DR

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


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

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

UPDATE: не работает всегда, поэтому это неправильно : (


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

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

видно, что '.toLowerCase()' необходимо предотвратить erros при сравнении строк.


по состоянию на 2018 год существует гораздо более короткое и элегантное решение. Просто использовать. массив.прототип.sort ().

пример:

var items = [
  { name: 'Edward', value: 21 },
  { name: 'Sharpe', value: 37 },
  { name: 'And', value: 45 },
  { name: 'The', value: -12 },
  { name: 'Magnetic', value: 13 },
  { name: 'Zeros', value: 37 }
];

// sort by value
items.sort(function (a, b) {
  return a.value - b.value;
});

комбинируя динамическое решение Ege с идеей Vinay, вы получаете хорошее надежное решение:

Array.prototype.sortBy = function() {
    function _sortByAttr(attr) {
        var sortOrder = 1;
        if (attr[0] == "-") {
            sortOrder = -1;
            attr = attr.substr(1);
        }
        return function(a, b) {
            var result = (a[attr] < b[attr]) ? -1 : (a[attr] > b[attr]) ? 1 : 0;
            return result * sortOrder;
        }
    }
    function _getSortFunc() {
        if (arguments.length == 0) {
            throw "Zero length arguments not allowed for Array.sortBy()";
        }
        var args = arguments;
        return function(a, b) {
            for (var result = 0, i = 0; result == 0 && i < args.length; i++) {
                result = _sortByAttr(args[i])(a, b);
            }
            return result;
        }
    }
    return this.sort(_getSortFunc.apply(null, arguments));
}

использование:

// Utility for printing objects
Array.prototype.print = function(title) {
    console.log("************************************************************************");
    console.log("**** "+title);
    console.log("************************************************************************");
    for (var i = 0; i < this.length; i++) {
        console.log("Name: "+this[i].FirstName, this[i].LastName, "Age: "+this[i].Age);
    }
}

// Setup sample data
var arrObj = [
    {FirstName: "Zach", LastName: "Emergency", Age: 35},
    {FirstName: "Nancy", LastName: "Nurse", Age: 27},
    {FirstName: "Ethel", LastName: "Emergency", Age: 42},
    {FirstName: "Nina", LastName: "Nurse", Age: 48},
    {FirstName: "Anthony", LastName: "Emergency", Age: 44},
    {FirstName: "Nina", LastName: "Nurse", Age: 32},
    {FirstName: "Ed", LastName: "Emergency", Age: 28},
    {FirstName: "Peter", LastName: "Physician", Age: 58},
    {FirstName: "Al", LastName: "Emergency", Age: 51},
    {FirstName: "Ruth", LastName: "Registration", Age: 62},
    {FirstName: "Ed", LastName: "Emergency", Age: 38},
    {FirstName: "Tammy", LastName: "Triage", Age: 29},
    {FirstName: "Alan", LastName: "Emergency", Age: 60},
    {FirstName: "Nina", LastName: "Nurse", Age: 54}
];

//Unit Tests
arrObj.sortBy("LastName").print("LastName Ascending");
arrObj.sortBy("-LastName").print("LastName Descending");
arrObj.sortBy("LastName", "FirstName", "-Age").print("LastName Ascending, FirstName Ascending, Age Descending");
arrObj.sortBy("-FirstName", "Age").print("FirstName Descending, Age Ascending");
arrObj.sortBy("-Age").print("Age Descending");

дополнительные параметры 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;
    }
}

в вашем примере вам нужно отсортировать по двум полям (фамилия, имя), а не по одному. Вы можете использовать Alasql библиотека, чтобы сделать этот вид в одной строке:

var res = alasql('SELECT * FROM ? ORDER BY last_nom, first_nom',[objs]);

попробуйте этот пример в jsFiddle.


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

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

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


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

objs.sort(function (a,b) {

var nameA=a.last_nom.toLowerCase(), nameB=b.last_nom.toLowerCase()

if (nameA < nameB)
  return -1;
if (nameA > nameB)
  return 1;
return 0;  //no sorting

})

function compare(propName) {
    return function(a,b) {
        if (a[propName] < b[propName])
            return -1;
        if (a[propName] > b[propName])
            return 1;
        return 0;
    };
}

objs.sort(compare("last_nom"));

objs.sort(function(a,b){return b.last_nom>a.last_nom})

я не видел этого конкретного подхода, поэтому вот краткий метод сравнения, который мне нравится использовать, который работает для обоих 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) => -(fn(a) < fn(b)) || +(fn(a) > fn(b))
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) => -(fn(a) < fn(b)) || +(fn(a) > fn(b))

сравнивать их.

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

(a, b) => -(a.last_nom < b.last_nom) || +(a.last_nom > b.last_nom)

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

if (a.last_nom < b.last_nom) return -1
return +(a.last_nom > b.last_nom)

так что если a < b вернуться -1, иначе если a > b затем мы возвращаемся +1, а если a == b, потом a < b и a > b являются ложными, поэтому он возвращает +0.

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

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) {
    return -(fn(a) < fn(b)) || +(fn(a) > fn(b))
  }
}

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

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

Это простая проблема, не знаю, почему у людей такое сложное решение.
Простая функция сортировки (на основе быстрая сортировка):

function sortObjectsArray(objectsArray, sortKey)
        {
            // Quick Sort:
            var retVal;

            if (1 < objectsArray.length)
            {
                var pivotIndex = Math.floor((objectsArray.length - 1) / 2);  // middle index
                var pivotItem = objectsArray[pivotIndex];                    // value in the middle index
                var less = [], more = [];

                objectsArray.splice(pivotIndex, 1);                          // remove the item in the pivot position
                objectsArray.forEach(function(value, index, array)
                {
                    value[sortKey] <= pivotItem[sortKey] ?                   // compare the 'sortKey' proiperty
                        less.push(value) :
                        more.push(value) ;
                });

                retVal = sortObjectsArray(less, sortKey).concat([pivotItem], sortObjectsArray(more, sortKey));
            }
            else
            {
                retVal = objectsArray;
            }

            return retVal;
        }

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

var myArr = 
        [
            { val: 'x', idx: 3 },
            { val: 'y', idx: 2 },
            { val: 'z', idx: 5 },
        ];
myArr = sortObjectsArray(myArr, 'idx');

Использование Ramda,

npm установить ramda

import R from 'ramda'
var objs = [ 
    { first_nom: 'Lazslo', last_nom: 'Jamf'     },
    { first_nom: 'Pig',    last_nom: 'Bodine'   },
    { first_nom: 'Pirate', last_nom: 'Prentice' }
];
var ascendingSortedObjs = R.sortBy(R.prop('last_nom'), objs)
var descendingSortedObjs = R.reverse(ascendingSortedObjs)

Я только что улучшил Ege Özcanдинамическая сортировка для погружения глубоко внутри объектов. Если данные выглядят следующим образом:

obj = [
    {
        a: { a: 1, b: 2, c: 3 },
        b: { a: 4, b: 5, c: 6 }
    },
    { 
        a: { a: 3, b: 2, c: 1 },
        b: { a: 6, b: 5, c: 4 }
}];

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

Object.defineProperty(Object.prototype, 'deepVal', {
    enumerable: false,
    writable: true,
    value: function (propertyChain) {
        var levels = propertyChain.split('.');
        parent = this;
        for (var i = 0; i < levels.length; i++) {
            if (!parent[levels[i]])
                return undefined;
            parent = parent[levels[i]];
        }
        return parent;
    }
});

и изменен _dynamicSort ' s возвращение функция:

return function (a,b) {
        var result = ((a.deepVal(property) > b.deepVal(property)) - (a.deepVal(property) < b.deepVal(property)));
        return result * sortOrder;
    }

и теперь вы можете отсортировать а.есть. этот путь:

obj.sortBy('a.a');

см. сценарий Commplete в JSFiddle


учитывая оригинальный пример:

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

сортировка по нескольким полям:

objs.sort(function(left, right) {
    var last_nom_order = left.last_nom.localeCompare(right.last_nom);
    var first_nom_order = left.first_nom.localeCompare(right.first_nom);
    return last_nom_order || first_nom_order;
});

Примечания

  • a.localeCompare(b) и универсальный и возвращает -1,0,1 если a<b,a==b,a>b соответственно.
  • || в последней строке last_nom приоритет first_nom.
  • вычитание работает на числовых полей: var age_order = left.age - right.age;
  • отрицание в обратном порядке,return -last_nom_order || -first_nom_order || -age_order;

используя lodash или подчеркивание, его кусок пирога

> const sortedList = _.orderBy(objs, [last_nom], [asc]); // asc or desc

Способ 1 :

можно использовать Underscore.js. Сначала импортируйте подчеркивание.

 import * as _ from 'underscore';
 let SortedObjs = _.sortBy(objs, 'last_nom');

Способ 2 : Используйте функцию сравнения.

function compare(first, second) {
     if (first.last_nom < second.last_nom)
         return -1;
     if (first.last_nom > second.last_nom)
       return 1;
    return 0;
 }

objs.sort(compare);