Sortuj tablicę obiektów według wartości właściwości ciągu

2763

Mam tablicę obiektów JavaScript:

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

Jak mogę je posortować według wartości last_nomw JavaScript?

Wiem o tym sort(a,b), ale wydaje się, że działa to tylko na ciągi i liczby. Czy muszę dodać toString()metodę do moich obiektów?

Tyrone Slothrop
źródło
7
Ten skrypt pozwala ci to zrobić, chyba że chcesz napisać własną funkcję porównawczą lub sorter: thomasfrank.se/sorting_things.html
Baversjo 15.07.2009
najszybszym sposobem jest użycie izomorficznego modułu macierzy sortowania , który działa natywnie zarówno w przeglądarce, jak i w węźle, obsługując dowolny typ danych wejściowych, pól obliczeniowych i niestandardowe porządki sortowania.
Lloyd

Odpowiedzi:

3918

Łatwo jest napisać własną funkcję porównania:

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

Lub inline (bez Marco Demaio):

objs.sort((a,b) => (a.last_nom > b.last_nom) ? 1 : ((b.last_nom > a.last_nom) ? -1 : 0)); 
Wogan
źródło
441
Lub inline: objs.sort (funkcja (a, b) {return (a.last_nom> b.last_nom)? 1: ((b.last_nom> a.last_nom)? -1: 0);});
Marco Demaio
176
return a.last_nom.localeCompare(b.last_nom)też będzie działać.
Cerbrus
146
dla tych, którzy szukają rodzaju, w którym pole jest numeryczne, porównaj ciało funkcji porównawczej: return a.value - b.value;(ASC)
Andre Figueiredo
20
@Cerbrus localeComparejest ważny, gdy używa się znaków akcentowanych w językach obcych, a także jest bardziej elegancki.
Marcos Lima
839

Możesz także utworzyć funkcję dynamicznego sortowania, która sortuje obiekty według przekazywanej wartości:

function dynamicSort(property) {
    var sortOrder = 1;
    if(property[0] === "-") {
        sortOrder = -1;
        property = property.substr(1);
    }
    return function (a,b) {
        /* next line works with strings and numbers, 
         * and you may want to customize it to your needs
         */
        var result = (a[property] < b[property]) ? -1 : (a[property] > b[property]) ? 1 : 0;
        return result * sortOrder;
    }
}

Możesz mieć tablicę takich obiektów:

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

... i zadziała, gdy wykonasz:

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

Właściwie to już odpowiada na pytanie. Poniższa część została napisana, ponieważ wiele osób skontaktowało się ze mną, narzekając, że nie działa z wieloma parametrami .

Wiele parametrów

Poniższej funkcji można użyć do wygenerowania funkcji sortowania z wieloma parametrami sortowania.

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

Co pozwoli ci zrobić coś takiego:

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

Tablica podklas

Szczęście wśród nas, którzy mogą korzystać z ES6, który pozwala rozszerzać obiekty rodzime:

class MyArray extends Array {
    sortBy(...args) {
        return this.sort(dynamicSortMultiple.apply(null, args));
    }
}

Umożliwiłoby to:

MyArray.from(People).sortBy("Name", "-Surname");
Ege Özcan
źródło
Pamiętaj, że nazwy właściwości w JavaScript mogą być dowolnymi ciągami znaków i jeśli masz właściwości zaczynające się od „-” (bardzo mało prawdopodobne i prawdopodobnie nie jest to dobry pomysł), musisz zmodyfikować funkcję dynamicSort, aby użyć czegoś innego do sortowania wstecznego wskaźnik.
Ege Özcan
Zauważyłem, że dynamicSort()w powyższym przykładzie umieścisz wielkie litery przed małymi literami. Na przykład, jeśli mam wartości APd, Aklinoraz Abe- wyników w ASC porządek powinien być Abe, Aklin, APd. Ale ze swoim przykładzie, wyniki są APd, Abe, Aklin. W każdym razie, aby poprawić to zachowanie?
Lloyd Banks
6
@LloydBanks, jeśli używasz tego ściśle dla ciągów, możesz użyć var result = a[property].localeCompare(b[property]);zamiast var result = (a[property] < b[property]) ? -1 : (a[property] > b[property]) ? 1 : 0;.
Ege Özcan
1
@ EgeÖzcan Powinien albo umieścić elementy na górze lub na dole, oto implementacja Skończyło się na pastebin.com/g4dt23jU
dzh
1
To świetne rozwiązanie, ale masz jeden problem, jeśli porównasz liczby. Dodaj to przed sprawdzeniem:if( !isNaN(a[property]) ) a[property] = Number(a[property]); if( !isNaN(b[property]) ) b[property] = Number(b[property]);
Ivijan Stefan Stipić
414

W ES6 / ES2015 lub nowszym możesz to zrobić w następujący sposób:

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

Przed ES6 / ES2015

objs.sort(function(a, b) {
    return a.last_nom.localeCompare(b.last_nom)
});
Vlad Bezden
źródło
29
jest to dostępne od JS 1.1, grubą strzałką do tego jest ES6 / 2015. Ale nadal bardzo przydatna i według mnie najlepsza odpowiedź
Jon Harding
13
@PratikKelwalkar: jeśli potrzebujesz odwrócić, po prostu przełącz porównanie a i b: objs.sort ((a, b) => b.last_nom.localeCompare (a.last_nom));
Vlad Bezden
możliwe jest użycie indeksu, a także zająć się pole do sortowania: zamiast last_nomstosowania tylko numer w tablicy: 1?
and-bri
4
@VladBezden dzięki za odpowiedź! To pierwsze rozwiązanie z bardzo małym nakładem programowym i poprawnymi wynikami sortowania oraz tablicą łańcuchów takich jak: [„Nazwa1”, „Nazwa10”, „Nazwa2”, „coś innego”, „Nazwa11”]. Mam sortowanie do poprawnej pracy zobjs.sort((a, b) => a.last_nom.localeCompare(b.last_nom, undefined, {numberic: true}));
scipper
1
Aby to zrobić przy malejących liczbach: .sort ((a, b) => b.numberProperty - a.numberProperty). Rosnąco: .sort ((a, b) => a.numberProperty - b.numberProperty)
Sebastian Patten
188

underscore.js

użyj podkreślenia, jego małego i niesamowitego ...

sortBy_.sortBy (lista, iterator, [kontekst]) Zwraca posortowaną kopię listy, uszeregowaną w porządku rosnącym według wyników uruchomienia każdej wartości przez iterator. Iterator może być również nazwą ciągu właściwości do sortowania według (np. Długości).

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' );
David Morrow
źródło
18
David, można edytować odpowiedź do powiedzenia var sortedObjs = _.sortBy( objs, 'first_nom' );. objsbędzie nie być sortowane się w wyniku tego. Funkcja zwróci posortowaną tablicę. To by było bardziej wyraźne.
Jess
10
Aby odwrócić sortowanie:var reverseSortedObjs = _.sortBy( objs, 'first_nom' ).reverse();
Erdal G.
2
musisz załadować bibliotekę javascript „podkreślenie”:<script src="http://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"> </script>
and-bri
5
Dostępne również Lodashdla tych, którzy wolą ten
WoJ
2
W przypadku lodash byłoby to takie samo: var sortedObjs = _.sortBy( objs, 'first_nom' );lub jeśli chcesz, aby było w innej kolejności:var sortedObjs = _.orderBy( objs, ['first_nom'],['dsc'] );
Travis Heeter
186

Nie rozumiem, dlaczego ludzie komplikują to:

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

W przypadku bardziej rygorystycznych silników:

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

Zamień operatora, aby sortować go w odwrotnej kolejności alfabetycznej.

p3lim
źródło
17
W rzeczywistości nie jest to poprawne, ponieważ funkcja używana w sortowaniu powinna zwracać -1, 0 lub 1, ale powyższa funkcja zwraca wartość logiczną. Sortowanie działa dobrze w chrome, ale na przykład nie działa w PhantomJS. Zobacz code.google.com/p/phantomjs/issues/detail?id=1090
schup
Niektóre silniki odpowiadają za głupotę, w ten sposób można to wykorzystać. Zaktualizowałem swoją odpowiedź odpowiednią wersją.
p3lim
19
Sugeruję edycję, aby wyjąć pierwszą wersję. Jest bardziej zwięzły, więc wygląda atrakcyjniej, ale nie działa, przynajmniej nie niezawodnie. Jeśli ktoś spróbuje go w jednej przeglądarce i zadziała, może nawet nie zdawać sobie sprawy, że ma problem (zwłaszcza jeśli nie przeczytał komentarzy). Druga wersja działa poprawnie, więc naprawdę nie ma potrzeby pierwszej.
Kate
2
@ Simon Naprawdę doceniam posiadanie „nieco złej” wersji, ponieważ bardziej rygorystyczna implementacja zajmuje kilka sekund, aby przeanalizować i zrozumieć i bez niej byłoby znacznie trudniej.
Aaron Sherman,
1
@ Lion789 po prostu zrób to:if(a.count == b.count) return a.name > b.name; else return a.count > b.count;
p3lim
62

Jeśli masz zduplikowane nazwiska, możesz je posortować według imion -

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;
});
Kennebec
źródło
@BadFeelingAboutTo oznacza zwrot 1 lub 1? Rozumiem, że -1 dosłownie oznacza, że ​​A jest mniejsze niż B tylko przez składnię, ale po co używać 1 lub -1? Widzę, że wszyscy używają tych liczb jako wartości zwracanych, ale dlaczego? Dzięki.
Chris22,
1
@ Chris22 zwrócona liczba ujemna oznacza, że bpowinna ona nastąpić po atablicy. Jeśli zwracana jest liczba dodatnia, oznacza to, że apowinna nastąpić później b. Jeśli 0zostanie zwrócony, oznacza to, że są uważane za równe. Zawsze możesz przeczytać dokumentację: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
BadFeelingAboutThis
@BadFeelingAboutTo dzięki za wyjaśnienie i link. Wierzcie lub nie, przeglądałem różne fragmenty kodu, używając tego, 1, 0, -1zanim o to poprosiłem. Po prostu nie znalazłem potrzebnych informacji.
Chris22,
48

Proste i szybkie rozwiązanie tego problemu za pomocą dziedziczenia prototypów:

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

Przykład / użycie

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

Aktualizacja: Nie modyfikuje już oryginalnej tablicy.

Vinay Aggarwal
źródło
5
Nie zwraca tylko innej tablicy. ale tak naprawdę sortuje oryginał!
Vinay Aggarwal
Jeśli chcesz się upewnić, że używasz sortowania naturalnego z liczbami (tj. 0,1,2,10,11 itd.), Użyj parseInt z zestawem Radix. developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… więc: return (parseInt (a [p], 10)> parseInt (b [p], 10))? 1: (parsowanie (a [p], 10) <parsowanie (b [p], 10))? -1: 0;
Paul
@codehuntr Dziękujemy za poprawienie. ale wydaje mi się, że zamiast funkcji sortowania w celu wykonania tego uczulenia, lepiej jest, jeśli stworzymy osobną funkcję do naprawy typów danych. Ponieważ funkcja sortowania nie może stwierdzić, która właściwość będzie zawierała rodzaj danych. :)
Vinay Aggarwal
Bardzo dobrze. Odwróć strzałki, aby uzyskać efekt asc / desc.
Abdul Sadik Yalcin
4
nigdy, nigdy nie sugeruj rozwiązania, które modyfikuje prototyp obiektu podstawowego
pbanka
39

Od 2018 r. Istnieje znacznie krótsze i eleganckie rozwiązanie. Po prostu użyj. Array.prototype.sort () .

Przykład:

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;
});
0leg
źródło
7
W pytaniu zastosowano ciągi porównawcze zamiast liczb. Twoja odpowiedź świetnie nadaje się do sortowania według liczb, ale nie jest tak dobra do porównywania według ciągów.
smcstewart
a.value - b.valueUżywane do porównywania atrybutów obiektu ( numery w tym przypadku) mogą być przyjęte dla różnych porach danych. Na przykład, wyrażenie regularne może być użyte do porównania każdej pary sąsiednich ciągów .
0leg
1
@ 0leg Chciałbym zobaczyć tutaj przykład użycia wyrażenia regularnego do porównywania ciągów w ten sposób.
Bob Stein
Ta implementacja jest całkiem dobra, jeśli musisz posortować ją według identyfikatora. Tak, zasugerowałeś użycie wyrażenia regularnego do porównania sąsiedniego ciągu, co komplikuje rozwiązanie, podczas gdy celem tej uproszczonej wersji będzie inaczej, jeśli wyrażenie regularne zostanie użyte razem z danym rozwiązaniem. Prostota jest najlepsza.
surendrapanday,
33

Stara odpowiedź, która jest nieprawidłowa:

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

AKTUALIZACJA

Z komentarza Beauchamp:

arr.sort((a, b) => a.name < b.name ? -1 : (a.name > b.name ? 1 : 0))

Bardziej czytelny format:

arr.sort((a, b) => {
  if (a.name < b.name) return -1
  return a.name > b.name ? 1 : 0
})

Bez zagnieżdżonych trójskładników:

arr.sort((a, b) => a.name < b.name ? - 1 : Number(a.name > b.name))

Objaśnienie: Number()będzie przesyłany truedo 1i falsedo 0.

Damjan Pavlica
źródło
Działa, ale z jakiegoś powodu wynik jest niestabilny
TitanFighter
@ AO17 nie, nie będzie. Nie można odejmować ciągów.
Patrick Roberts,
15
Powinno to zrobić:arr.sort((a, b) => a.name < b.name ? -1 : (a.name > b.name ? 1 : 0))
Jean-François Beauchamp
3
@ Jean-FrançoisBeauchamp, Twoje rozwiązanie działa idealnie dobrze i znacznie lepiej.
Ahsan
trzeci z numerem jest prosty i przyjemny!
Kojo
29

Zamiast korzystać z niestandardowej funkcji porównania, możesz również utworzyć typ obiektu za pomocą niestandardowej toString()metody (która jest wywoływana przez domyślną funkcję porównania):

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();
Christoph
źródło
29

Lodash.js ( nadzbiór Underscore.js )

Dobrze jest nie dodawać frameworka dla każdej prostej logiki, ale poleganie na dobrze przetestowanych frameworkach użytkowych może przyspieszyć rozwój i zmniejszyć liczbę błędów.

Lodash tworzy bardzo czysty kod i promuje bardziej funkcjonalny styl programowania . Na pierwszy rzut oka staje się jasne, jaki jest cel kodu.

Problem OP można po prostu rozwiązać jako:

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

Więcej informacji? Np. Mamy następujący zagnieżdżony obiekt:

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

Teraz możemy użyć skrótu _.property,user.age aby określić ścieżkę do właściwości, która powinna być dopasowana. Posortujemy obiekty użytkownika według zagnieżdżonej właściwości age. Tak, pozwala na dopasowanie zagnieżdżonych właściwości!

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

Chcesz to odwrócić? Nie ma problemu. Użyj _.reverse .

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

Chcesz połączyć oba za pomocą łańcucha ?

const { chain } = require('lodash');
const sortedObjs = chain(users).sortBy('user.age').reverse().value();

Lub kiedy wolisz przepływ ponad łańcuchem

const { flow, reverse, sortBy } = require('lodash/fp');
const sortedObjs = flow([sortBy('user.age'), reverse])(users); 
Nico Van Belle
źródło
28

Możesz użyć

Najłatwiejszy sposób: Lodash

( https://lodash.com/docs/4.17.10#orderBy )

Ta metoda jest podobna do _.sortBy, z tą różnicą, że pozwala na określenie kolejności sortowania iteratów do sortowania według. Jeśli zamówienia nie są określone, wszystkie wartości są sortowane w porządku rosnącym. W przeciwnym razie podaj porządek „malejący” dla malejącego lub „rosnący” dla rosnącego sortowania odpowiednich wartości.

Argumenty

kolekcja (Array | Object): kolekcja do iteracji. [iteratees = [_. tożsamość]] (Array [] | Function [] | Object [] | string []): Ieruje sortowanie według. [zamówienia] (ciąg []): Porządki sortowania iteratów.

Zwroty

(Array): Zwraca nową posortowaną tablicę.


var _ = require('lodash');
var homes = [
    {"h_id":"3",
     "city":"Dallas",
     "state":"TX",
     "zip":"75201",
     "price":"162500"},
    {"h_id":"4",
     "city":"Bevery Hills",
     "state":"CA",
     "zip":"90210",
     "price":"319250"},
    {"h_id":"6",
     "city":"Dallas",
     "state":"TX",
     "zip":"75000",
     "price":"556699"},
    {"h_id":"5",
     "city":"New York",
     "state":"NY",
     "zip":"00010",
     "price":"962500"}
    ];

_.orderBy(homes, ['city', 'state', 'zip'], ['asc', 'desc', 'asc']);
Harvey
źródło
23

Istnieje wiele dobrych odpowiedzi, ale chciałbym zauważyć, że można je bardzo łatwo rozszerzyć, aby uzyskać znacznie bardziej złożone sortowanie. Jedyne, co musisz zrobić, to użyć operatora OR do połączenia funkcji porównania w następujący sposób:

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

gdzie fn1,fn2 ... są zwracane funkcje sortowania [-1,0,1]. Powoduje to „sortowanie według fn1”, „sortowanie według fn2”, co jest prawie równe ORDER BY w SQL.

To rozwiązanie opiera się na zachowaniu ||operatora, który ocenia pierwsze ocenione wyrażenie, które można przekonwertować na true .

Najprostsza forma ma tylko jedną wbudowaną funkcję:

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

Mając dwa kroki z last_nom, first_nomporządek będzie wyglądać następująco:

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

Ogólna funkcja porównania może wyglądać mniej więcej tak:

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

Funkcję tę można rozszerzyć o obsługę pól numerycznych, rozróżnianie wielkości liter, arbitralne typy danych itp.

Możesz ich użyć do łączenia ich według priorytetów sortowania:

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

Chodzi o to, że czysty JavaScript z funkcjonalnym podejściem może zabrać cię daleko bez zewnętrznych bibliotek lub złożonego kodu. Jest również bardzo skuteczny, ponieważ nie trzeba wykonywać analizy ciągów

Tero Tolonen
źródło
23

Przykładowe użycie:

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

Scenariusz:

/**
 * @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;
  };
};
Jamie Mason
źródło
dziękuję za podzielenie tego, próbuję zrozumieć, dlaczego cyfry 1, 0, -1są używane do sortowania sortowania. Nawet z powyższym wyjaśnieniem, które wygląda bardzo dobrze - wciąż nie rozumiem tego. Zawsze myślę o tym, -1jak przy użyciu właściwości długości tablicy, tzn .: arr.length = -1oznacza, że ​​element nie został znaleziony. Prawdopodobnie mieszam tutaj różne rzeczy, ale czy możesz mi pomóc zrozumieć, dlaczego cyfry 1, 0, -1są używane do określania kolejności? Dzięki.
Chris22,
1
Nie jest to do końca dokładne, ale może pomóc myśleć o tym w ten sposób: funkcja przekazana do array.sort jest wywoływana raz dla każdego elementu w tablicy, jako argument o nazwie „a”. Zwracana wartość każdego wywołania funkcji jest sposobem zmiany indeksu (numer bieżącej pozycji) pozycji „a” w porównaniu z kolejną pozycją „b”. Indeks określa kolejność tablicy (0, 1, 2 itd.). Jeśli więc „a” ma indeks 5, a zwracamy -1, to 5 + -1 == 4 (przesuń go bliżej przodu) 5 + 0 == 5 (zachowaj go tam, gdzie jest) itd. Przechodzi tablicę, porównując za każdym razem 2 sąsiadów, aż do końca, pozostawiając posortowaną tablicę.
Jamie Mason
dziękuję za poświęcenie czasu na wyjaśnienie tego dalej. Korzystając z twojego wyjaśnienia i zestawu MDN Array.prototype.sort , powiem ci, co myślę o tym: w porównaniu do ai b, jeśli ajest większe niż bdodaj 1 do indeksu ai umieść go z tyłu b, jeśli ajest mniejsze niż b, odejmij 1 ai umieść przed b. Jeśli ai bsą takie same, dodaj 0 do ai pozostaw to miejsce.
Chris22,
22

Nie widziałem tego szczególnego podejścia sugerowanego, więc oto sposób porównanie lakoniczny Lubię używać który pracuje dla obu stringoraz 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));

Oto wyjaśnienie sortBy() :

sortBy()akceptuje wartość a, fnktóra wybiera wartość z obiektu do użycia jako porównanie, i zwraca funkcję, do której można przekazać bezpośrednio Array.prototype.sort(). W tym przykładzie używamy o.last_nomjako wartości do porównania, więc za każdym razem, gdy otrzymujemy dwa obiekty, Array.prototype.sort()takie jak

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

i

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

Używamy

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

aby je porównać.

Pamiętając o tym fn = o => o.last_nom, możemy rozszerzyć funkcję porównania do odpowiednika

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

Logiczny ||operator OR ma funkcję zwarcia, która jest bardzo przydatna w tym przypadku. Z powodu tego, jak to działa, oznacza to powyższą treść

if (fa < fb) return -1;
if (fa > fb) return 1;
return 0;

Jako dodatkowy bonus, oto ekwiwalent w ECMAScript 5 bez funkcji strzałek, co jest niestety bardziej szczegółowe:

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

Patrick Roberts
źródło
17

Wiem, że to pytanie jest za stare, ale nie widziałem żadnej implementacji podobnej do mojej.
Ta wersja oparta jest na idiomie transformacji Schwartziana .

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

Oto przykład, jak go używać:

let games = [
  { name: 'Mashraki',          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'));
a8m
źródło
14

Sortowanie (więcej) Złożone tablice obiektów

Ponieważ prawdopodobnie napotykasz bardziej złożone struktury danych, takie jak ta tablica, rozwinąłbym to rozwiązanie.

TL; DR

Są bardziej wtykową wersją opartą na bardzo uroczej odpowiedzi @ ege-Özcan .

Problem

Spotkałem poniżej i nie mogłem tego zmienić. Nie chciałem też tymczasowo spłaszczyć obiektu. Nie chciałem też używać podkreślenia / lodash, głównie ze względu na wydajność i zabawę, aby samodzielnie go wdrożyć.

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

Cel

Celem jest posortowanie go według People.Name.name a następnie wedługPeople.Name.surname

Przeszkody

Teraz w rozwiązaniu podstawowym używa notacji nawiasowej do obliczania właściwości do sortowania dynamicznego. Tutaj jednak musielibyśmy również dynamicznie konstruować notację nawiasową, ponieważ można by się spodziewać podobnychPeople['Name.name'] będą działać - co nie działa.

People['Name']['name']Z drugiej strony samo robienie jest statyczne i pozwala tylko zejść n ty poziom.

Rozwiązanie

Głównym dodatkiem tutaj będzie przejście po drzewie obiektów i określenie wartości ostatniego liścia, który musisz określić, a także dowolnego liścia pośredniego.

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

Przykład

Przykład roboczy na JSBin

eljefedelrodeodeljefe
źródło
2
Dlaczego? To nie jest odpowiedź na pierwotne pytanie, a „cel” można rozwiązać po prostu za pomocą People.sort ((a, b) => {return a.Name.name.localeCompare (b.Name.name) || a.Name .sname.localeCompare (b.Name.nazwa)})
Tero Tolonen
12

Jeszcze jedna opcja:

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

domyślnie sortuje rosnąco.

Ravshan Samandarov
źródło
1
Lekko zmieniona wersja do sortowania według wielu pól jest dostępna w razie potrzeby: stackoverflow.com/questions/6913512/…
Ravshan Samandarov
wygląda na to, że może być następny: funkcja eksportowania wygenerowaćSortFn (prop: string, reverse: boolean = false): (... args: any) => number {return (a, b) => {return a [prop ] <b [prop]? odwrócić ? 1: -1: a [prop]> b [prop]? odwrócić ? -1: 1: 0; }; }
Den Kerny
Wolę kod bardziej czytelny niż najkrótszy.
Ravshan Samandarov
Zgadzam się, ale w niektórych przypadkach nie muszę patrzeć na funkcje narzędziowe.
Den Kerny
11

Prosta funkcja sortująca tablicę obiektów według właściwości

function sortArray(array, property, direction) {
    direction = direction || 1;
    array.sort(function compare(a, b) {
        let comparison = 0;
        if (a[property] > b[property]) {
            comparison = 1 * direction;
        } else if (a[property] < b[property]) {
            comparison = -1 * direction;
        }
        return comparison;
    });
    return array; // Chainable
}

Stosowanie:

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

sortArray(objs, "last_nom"); // Asc
sortArray(objs, "last_nom", -1); // Desc
Francois Girard
źródło
To rozwiązanie działało idealnie dla mnie w sortowaniu dwukierunkowym. Dziękujemy u
manjuvreddy
10

Prosty sposób:

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

Zobacz, co '.toLowerCase()'jest konieczne, aby zapobiec błędom przy porównywaniu ciągów.

Caio Ladislau
źródło
4
Możesz użyć funkcji strzałek, aby kod był nieco bardziej elegancki:objs.sort( (a,b) => b.last_nom.toLowerCase() < a.last_nom.toLowerCase() );
Sertage
Jest to złe z tego samego powodu, co wyjaśniono tutaj .
Patrick Roberts,
2
Funkcje strzałek nie są warte ES5. Mnóstwo silników jest nadal ograniczone do ES5. W moim przypadku odpowiedź powyżej jest znacznie lepsza, ponieważ korzystam z silnika ES5 (wymuszonego przez moją firmę)
dylanh724
9

dodatkowe parametry opisowe dla kodu 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;
    }
}
Behnam Shomali
źródło
9

Łącząc dynamiczne rozwiązanie Ege z pomysłem Vinay, otrzymujesz ładne, solidne rozwiązanie:

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

Stosowanie:

// 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");
Mike R.
źródło
1
Dzięki za pomysł! Nawiasem mówiąc, proszę nie zachęcać ludzi do zmiany prototypu macierzy (zobacz ostrzeżenie na końcu mojego przykładu).
Ege Özcan
8

Na przykład, musisz posortować według dwóch pól (nazwisko, imię), a nie jednego. Możesz użyć biblioteki Alasql, aby dokonać tego sortowania w jednym wierszu:

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

Spróbuj tego przykładu w jsFiddle .

agershun
źródło
8
objs.sort(function(a,b){return b.last_nom>a.last_nom})
Roshni Bokade
źródło
1
Właściwie to nie działało, musiało użyć zaakceptowanej odpowiedzi. Niepoprawne sortowanie.
madprops
8

Biorąc pod uwagę oryginalny przykład:

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

Sortuj według wielu pól:

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

Notatki

  • a.localeCompare(b)jest powszechnie obsługiwana i wraca -1,0,1 jeśli a<b, a==b,a>b odpowiednio.
  • ||w ostatnim wierszu daje last_nompierwszeństwofirst_nom .
  • Odejmowanie działa na polach numerycznych: var age_order = left.age - right.age;
  • Neguj do odwrotnej kolejności, return -last_nom_order || -first_nom_order || -age_order;
Bob Stein
źródło
8

Spróbuj tego,

UPTO ES5

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


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


IN ES6 & above:

// Ascending sort
items.sort((a, b) => a.value - b.value);

// Descending Sort
 items.sort((a, b) => b.value - a.value);
Abhishek
źródło
najlepsze i łatwe rozwiązanie
Omar Hasan
7

Może być konieczne przekonwertowanie ich na małe litery, aby uniknąć pomyłek.

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

})
Burak Keceli
źródło
7
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"));
Evgenii
źródło
1
Rozważ edycję swojego posta, aby dodać więcej wyjaśnień na temat tego, co robi Twój kod i dlaczego rozwiąże problem. Odpowiedź, która w większości zawiera tylko kod (nawet jeśli działa), zwykle nie pomaga OP zrozumieć jego problemu.
Drenmi
7

Korzystając z Ramda,

npm install 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)
Sridhar Sg
źródło
6

To prosty problem, nie wiem, dlaczego ludzie mają tak złożone rozwiązanie.
Prosta funkcja sortowania (oparta na algorytmie szybkiego sortowania ):

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

Użyj przykładu:

var myArr = 
        [
            { val: 'x', idx: 3 },
            { val: 'y', idx: 2 },
            { val: 'z', idx: 5 },
        ];
myArr = sortObjectsArray(myArr, 'idx');
Gil Epshtain
źródło
5
Jak wdrożenie szybkiego sortowania w js jest prostym rozwiązaniem? Prosty algorytm, ale nie proste rozwiązanie.
Andrew,
Jest to proste, ponieważ nie używa żadnych zewnętrznych bibliotek i nie zmienia prototypu obiektu. Moim zdaniem długość kodu nie ma bezpośredniego wpływu na złożoność kodu
Gil Epshtain
2
Cóż, pozwól mi wypróbować inne słowa: jak wynalezienie koła jest prostym rozwiązaniem?
Roberto14,