Питання Сортувати масив об'єктів за допомогою значення властивості рядка в JavaScript


У мене є масив об'єктів 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 до моїх об'єктів?


1829
2017-07-15 03:17


походження


Цей сценарій дозволяє вам зробити це, якщо ви не хочете написати власну функцію порівняння або сортування: thomasfrank.se/sorting_things.html - Baversjo


Відповіді:


Досить легко написати власну функцію порівняння:

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

Або інлайн (c / o Marco Demaio):

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

2716
2017-07-15 03:35



Або вбудований: objs.sort (функція (a, b) {повернення (a.last_nom> b.last_nom) 1: ((b.last_nom> a.last_nom)? -1: 0);}); - Marco Demaio
Офіційні документи: developer.mozilla.org/en/JavaScript/Reference/Global_Objects/... - mikemaccana
return a.last_nom.localeCompare(b.last_nom) буде працювати теж. - Cerbrus
для тих, хто шукає сорт, де поле є числовим, функція порівняння: return a.value - b.value; (ASC) - Andre Figueiredo
@ Кербрус localeCompare важливо при використанні акцентованих символів на іноземних мовах, а також більш елегантним. - Marcos Lima


Ви також можете створити динамічну функцію сортування, яка сортує об'єкти за їхнім значенням, які ви передаєте:

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

Додавання до прототипу

(Втілення, наведене нижче, натхненне Майк Р.с відповісти)

Я б не рекомендував змінювати прототип власного об'єкта, а просто навести приклад, щоб ви могли його реалізувати на власних об'єктах (Для середовищ, які підтримують його, ви також можете використовувати Object.defineProperty як показано в наступному розділі, що, принаймні, не має негативного побічного ефекту від перерахунку, як це описано в останній частині)

Реалізація прототипу буде приблизно такою (Ось робочий приклад):

//Don't just copy-paste this code. You will break the "for-in" loops
!function() {
    function _dynamicSortMultiple(attr) {
       /* dynamicSortMultiple function body comes here */
    }
    function _dynamicSort(property) {
        /* dynamicSort function body comes here */
    }
    Array.prototype.sortBy = function() {
        return this.sort(_dynamicSortMultiple.apply(null, arguments));
    }
}();

"ОК" спосіб додати його до прототипу

Якщо ви націлені на IE v9.0 і вище, як я вже згадував, використовуйте Object.defineProperty подобається це (робочий приклад):

//Won't work below IE9, but totally safe otherwise
!function() {
    function _dynamicSortMultiple(attr) {
       /* dynamicSortMultiple function body comes here */
    }
    function _dynamicSort(property) {
        /* dynamicSort function body comes here */
    }
    Object.defineProperty(Array.prototype, "sortBy", {
        enumerable: false,
        writable: true,
        value: function() {
            return this.sort(_dynamicSortMultiple.apply(null, arguments));
        }
    });
}();

Це може бути прийнятним компромісом до того, як пов'язати оператора прибуває

Всі ці розваги прототипу дозволяють це:

People.sortBy("Name", "-Surname");

Ви повинні прочитати це

Якщо ви використовуєте метод прямого прототипу (Object.defineProperty це добре), а інший код не перевіряє hasOwnProperty, кошенята вмирають! Гаразд, чесно кажучи, ніякої шкоди не приходить кожен дійсно, але, ймовірно, речі зламуються, і кожен інший розробник у вашій команді ненавидить вас:

evil

Див останній "SortBy"? Так. Не круто. Використовуйте Object.defineProperty, де ви можете, і залиште аргумент.prototype окремо.


650
2018-01-21 15:03



Зверніть увагу, що назви властивостей у JavaScript можуть бути будь-якими рядками, і якщо у вас є властивості, починаючи з "-" (вкрай малоймовірно і ймовірно не гарна ідея), вам доведеться змінити функцію dynamicSort, щоб використати щось інше як зворотний індикатор сортування. - Ege Özcan
Я помітив, що dynamicSort() у наведеному вище прикладі буде розміщувати великі літери перед малими літерами. Наприклад, якщо у мене є значення APd, Aklin, і Abe - результати в сорту ASC повинні бути Abe, Aklin, APd. Але з вашого прикладу, результати є APd, Abe, Aklin. У будь-якому випадку, щоб виправити цю поведінку? - Lloyd Banks
@LloydBanks, якщо ви використовуєте це строго для рядків, то ви можете використовувати var result = a[property].localeCompare(b[property]); замість var result = (a[property] < b[property]) ? -1 : (a[property] > b[property]) ? 1 : 0;. - Ege Özcan
Точно так само, як найпопулярніша відповідь, цей файл не працює у випадках, коли існують невизначені властивості. [ { a: 5 }, {a:2}, {}, {a:1} ] - dzh
@ EgeÖzcan Це повинно або поставити елементи вгорі чи внизу, ось реалізація я закінчила, використовуючи pastebin.com/g4dt23jU - dzh


underscore.js

використовуйте підкреслення, його маленький і дивовижний ...

sortBy_.sortBy (список, ітератор, [context]) Повертає відсортовану копію   список, що розраховується в порядку зростання за результатами запуску кожного значення   через ітератор. Ітератор може також бути ланцюжковим найменуванням властивості   сортувати за (наприклад, довжина).

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

155
2018-05-10 21:24



Давид, ти міг би відредагувати відповідь, сказати var sortedObjs = _.sortBy( objs, 'first_nom' );. objs воля ні внаслідок цього самі сортуються. Функція буде повернутися сортований масив. Це зробить його більш явним. - Jess
Для зворотного сортування: var reverseSortedObjs = _.sortBy( objs, 'first_nom' ).reverse(); - Erdal G.
вам потрібно завантажити JavaScript-ліберій "підкреслення": <script src="http://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"> </script> - and-bri
Також доступний в Lodash для тих, хто воліє цього - WoJ


Не зрозумійте, чому люди роблять це настільки складним:

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

Обмін оператором, щоб він був відсортований у зворотному алфавітному порядку.


141
2018-01-24 19:35



Це насправді не правильно, оскільки функція, використовувана у сортуванні, повинна повертати -1, 0 або 1, але вищенаведена функція повертає логічний. Сортування добре працює в хрому, але, наприклад, не вдається виконати у PhantomJS. Побачити code.google.com/p/phantomjs/issues/detail?id=1090 - schup
Деякі двигуни сприймають дурість, таким чином це було свого роду експлуатацією. Я оновив мою відповідь з відповідною версією. - p3lim
Я пропоную редагувати, щоб вийняти першу версію. Його більш стислий вигляд виглядає більш привабливо, але це не працює, принаймні, не надійно. Якщо хтось спробує це в одному веб-переглядачі, і він працює, вони можуть навіть не зрозуміти, що у них є проблеми (особливо, якщо вони не читали коментарі). Друга версія працює належним чином, тому для першого не потрібно. - Kate
Чи існує версія першого з декількома параметрами, особливо для сортування за рахунком, за алфавітом - Lion789
@Simon Я дуже радий, що в першу чергу має "трохи неправильну" версію, оскільки жорстке здійснення займає кілька секунд, щоб проаналізувати та зрозуміти, і без цього буде набагато складніше. - Aaron Sherman


У ES6 / ES2015 або вище ви можете зробити це таким чином:

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

138
2018-01-29 19:44



це було доступно, оскільки JS 1.1, стовпчик жирної стрілки до цього є частиною ES6 / 2015. Але все ж таки дуже корисно і найкраще відповісти на мій погляд - Jon Harding
@PratikKelwalkar: якщо вам потрібно повернути назад, просто переключіть a та b порівняння: objs.sort ((a, b) => b.last_nom.localeCompare (a.last_nom)); - Vlad Bezden
Чи можна використовувати індекс для вирішення поля для сортування: замість last_nom використовуйте лише номер у масиві: 1 ? - and-bri
@ВладБезден спасибі за вашу відповідь! Це рішення є першим з дуже невеликим програмним зусиллям та правильними результатами сортування та рядовим масивом: ["Name1", "Name10", "Name2", "щось інше", "Name11"]. Я сортував, щоб правильно працювати objs.sort((a, b) => a.last_nom.localeCompare(b.last_nom, undefined, {numberic: true})); - scipper


Якщо у вас є повторювані прізвища, ви можете сортувати їх за іменем-

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

51
2017-07-15 04:03



Це a< b  a >b форматування цікаво. - Dodekeract


Просте та швидке вирішення цієї проблеми за допомогою прототипу спадщини:

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

Оновлення: Більше не змінює оригінальний масив.


38
2017-07-10 11:54



Це не тільки не поверне інший масив. але насправді сортує оригінальний !. - Vinay Aggarwal
Якщо ви хочете переконатися, що ви використовуєте природний сорт за допомогою цифр (наприклад, 0,1,2,10,11 тощо ...), використовуйте parseInt з набором Radix. developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/... так: return (parseInt (a [p], 10)> parseInt (b [p], 10))? 1: (parseInt (a [p], 10) <parseInt (b [p], 10))? -1: 0; - Paul
@codehuntr Дякуємо, що виправили його. але я думаю, замість того, щоб виконувати функцію сортування, щоб зробити цю сенсибілізацію, краще, якщо ми зробимо окрему функцію для виправлення типів даних. Оскільки функція сортування не може визначити, яка властивість буде містити які дані. :) - Vinay Aggarwal
Дуже добре. Зворотні стрілки, щоб отримати ефект ASC / DESC. - Abdul Sadik Yalcin


Замість того, щоб використовувати власну функцію порівняння, ви також можете створити тип об'єкта за умовчанням 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();

24
2017-07-15 07:21





Тут є багато хороших відповідей, але я хотів би зазначити, що їх можна просто розширити, щоб досягти набагато більш складного сортування. Єдине, що вам потрібно зробити - це скористатись оператором OR, щоб виконувати такі порівняльні функції, як:

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

Де fn1, fn2, ... це сортові функції, які повертають [-1,0,1]. Це призводить до "сортування за допомогою fn1", "сортування за допомогою fn2", яке практично дорівнює ORDER BY в 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])

Ця функція може бути розповсюджена на підтримку числових полів, чутливих до регістру, афіційних типів даних тощо.

Ви можете використовувати їх з поєднанням їх за пріоритетом сортування:

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


14
2018-05-05 11:36