Jak posortować tablicę obiektów według wielu pól?

147

Z tego pierwotnego pytania , jak zastosować sortowanie na wielu polach?

Korzystając z tej nieco dostosowanej struktury, jak posortować miasto (rosnąco), a następnie cenę (malejąco)?

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

Podobał mi się fakt, że otrzymałem odpowiedź, która zawierała ogólne podejście. Tam, gdzie planuję użyć tego kodu, będę musiał posortować daty i inne rzeczy. Możliwość „gruntowania” obiektu wydawała się przydatna, jeśli nie trochę kłopotliwa.

Próbowałem przekształcić tę odpowiedź w ładny ogólny przykład, ale nie mam szczęścia.

Mikrofon
źródło
Chcesz wyszukiwać czy sortować?
Felix Kling
Na czym dokładnie polega problem z użyciem drugiej odpowiedzi, którą łączysz?
kanon
To nie jest wystarczająco ogólne. Wydaje mi się, sort(["first-field", "ASC"], ["second-field", "DSC"]); że dodam morze kodu, kiedy po prostu chcę powiedzieć. Jest to jeszcze bardziej skomplikowane, gdy próbuję dodać logikę „podkładu” do pierwszej odpowiedzi, aby móc obsłużyć daty, rozróżnianie wielkości liter itp.
Mike
Lub możesz przyłożyć wagę do każdego pola
onmyway133
Możesz sprawdzić lodash.com/docs/4.17.11#orderBy , jeśli nie masz nic przeciwko korzystaniu z lodash
Arora

Odpowiedzi:

83

Wielowymiarowa metoda sortowania oparta na tej odpowiedzi :

Aktualizacja : Oto wersja „zoptymalizowana”. Wykonuje dużo więcej wstępnego przetwarzania i tworzy wcześniej funkcję porównawczą dla każdej opcji sortowania. Może potrzebować więcej pamięci (ponieważ przechowuje funkcję dla każdej opcji sortowania, ale powinien działać trochę lepiej, ponieważ nie musi określać poprawnych ustawień podczas porównania. Nie wykonałem jednak żadnego profilowania.

var sort_by;

(function() {
    // utility functions
    var default_cmp = function(a, b) {
            if (a == b) return 0;
            return a < b ? -1 : 1;
        },
        getCmpFunc = function(primer, reverse) {
            var dfc = default_cmp, // closer in scope
                cmp = default_cmp;
            if (primer) {
                cmp = function(a, b) {
                    return dfc(primer(a), primer(b));
                };
            }
            if (reverse) {
                return function(a, b) {
                    return -1 * cmp(a, b);
                };
            }
            return cmp;
        };

    // actual implementation
    sort_by = function() {
        var fields = [],
            n_fields = arguments.length,
            field, name, reverse, cmp;

        // preprocess sorting options
        for (var i = 0; i < n_fields; i++) {
            field = arguments[i];
            if (typeof field === 'string') {
                name = field;
                cmp = default_cmp;
            }
            else {
                name = field.name;
                cmp = getCmpFunc(field.primer, field.reverse);
            }
            fields.push({
                name: name,
                cmp: cmp
            });
        }

        // final comparison function
        return function(A, B) {
            var a, b, name, result;
            for (var i = 0; i < n_fields; i++) {
                result = 0;
                field = fields[i];
                name = field.name;

                result = field.cmp(A[name], B[name]);
                if (result !== 0) break;
            }
            return result;
        }
    }
}());

Przykładowe użycie:

homes.sort(sort_by('city', {name:'price', primer: parseInt, reverse: true}));

PRÓBNY


Oryginalna funkcja:

var sort_by = function() {
   var fields = [].slice.call(arguments),
       n_fields = fields.length;

   return function(A,B) {
       var a, b, field, key, primer, reverse, result, i;

       for(i = 0; i < n_fields; i++) {
           result = 0;
           field = fields[i];

           key = typeof field === 'string' ? field : field.name;

           a = A[key];
           b = B[key];

           if (typeof field.primer  !== 'undefined'){
               a = field.primer(a);
               b = field.primer(b);
           }

           reverse = (field.reverse) ? -1 : 1;

           if (a<b) result = reverse * -1;
           if (a>b) result = reverse * 1;
           if(result !== 0) break;
       }
       return result;
   }
};

PRÓBNY

Felix Kling
źródło
2
Dla przypomnienia, ta funkcja mogłaby zostać ulepszona poprzez wstępne przetwarzanie listy argumentów i utworzenie jednolitej "tablicy opcji sortowania". Czytelnikowi pozostawiam to jako ćwiczenie;)
Felix Kling,
@Mike: Ok ... wreszcie;) Widzisz, teraz jest to bardziej skomplikowane, ponieważ opcje są wstępnie przetworzone, ale ostateczna funkcja porównawcza (patrz komentarz) jest znacznie prostsza, co (miejmy nadzieję) prowadzi do lepszej wydajności. Im więcej masz opcji sortowania, tym większa jest korzyść z tej metody.
Felix Kling,
165

aby uzyskać nieogólne, proste rozwiązanie konkretnego problemu:

homes.sort(
   function(a, b) {          
      if (a.city === b.city) {
         // Price is only important when cities are the same
         return b.price - a.price;
      }
      return a.city > b.city ? 1 : -1;
   });
Wypalony śniegiem
źródło
6
Myślę, że to demo jest tym, czego chce OP => jsfiddle.net/zJ6UA/533
Amin Jafari
3
To dobry pomysł, ale logika jest błędna. Nie możesz odjąć nieliczbowego ciągu od innego ciągu, a ifinstrukcja nie ma sensu.
JLRishe
6
Możesz użyć a.localeCompare(b)w ostatnim wierszu do porównania ciągów ... zobacz dokumentację
Michael P
2
Czy pierwsze porównanie miast nie powinno polegać na sprawdzaniu równości, a nie nierówności? Innymi słowy, czy linia nie powinna być if (a.city === b.city)? Oznacza to, że jeśli oba miasta są takie same, porównaj ceny, w przeciwnym razie porównaj miasta.
Steven Rands
2
jedna z najlepszych odpowiedzi. tnx.
jonathana
54

Możesz użyć metody sortowania łańcuchowego, biorąc deltę wartości, aż osiągnie wartość różną od zera.

var data = [{ 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" }];

data.sort(function (a, b) {
    return a.city.localeCompare(b.city) || b.price - a.price;
});

console.log(data);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Lub używając es6, po prostu:

data.sort((a, b) => a.city.localeCompare(b.city) || b.price - a.price);
Nina Scholz
źródło
16
Czy coś mi brakuje? Po co używać 60 linii kodu do czegoś, co można zrobić w 1. Prosty, jasny, zwięzły. Powinna być akceptowana odpowiedź IMO.
Erez Cohen
jednym z największych problemów w SO jest to, że stare odpowiedzi - często dobrze zastępowane przez lepsze rozwiązania wykorzystujące nowe funkcje językowe (np. ES5-6-7) zachowują swoje stare wyniki i wszyscy musimy przewijać w dół, aby znaleźć „prawdziwy” najlepszy rozwiązania! SO powinien z czasem wygasać głosy, aby rozwiązać ten problem, ponieważ problem staje się coraz gorszy w miarę upływu czasu.
Andy Lorenz
53

Oto proste podejście funkcjonalne. Określ kolejność sortowania za pomocą tablicy. Dodaj minus na początku, aby określić kolejność malejącą.

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

homes.sort(fieldSorter(['city', '-price']));
// homes.sort(fieldSorter(['zip', '-state', 'price'])); // alternative

function fieldSorter(fields) {
    return function (a, b) {
        return fields
            .map(function (o) {
                var dir = 1;
                if (o[0] === '-') {
                   dir = -1;
                   o=o.substring(1);
                }
                if (a[o] > b[o]) return dir;
                if (a[o] < b[o]) return -(dir);
                return 0;
            })
            .reduce(function firstNonZeroValue (p,n) {
                return p ? p : n;
            }, 0);
    };
}

Edycja: w ES6 jest jeszcze krócej!

"use strict";
const fieldSorter = (fields) => (a, b) => fields.map(o => {
    let dir = 1;
    if (o[0] === '-') { dir = -1; o=o.substring(1); }
    return a[o] > b[o] ? dir : a[o] < b[o] ? -(dir) : 0;
}).reduce((p, n) => p ? p : n, 0);

const 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}];
const sortedHomes = homes.sort(fieldSorter(['state', '-price']));

document.write('<pre>' + JSON.stringify(sortedHomes, null, '\t') + '</pre>')

chriskelly
źródło
6
Uważam, że ta funkcja jest całkiem fajna, więc dokonałem niewielkiej poprawy wydajności do 90% w zależności od parsera. Zrobiłem streszczenie i zestaw testów .
php_nub_qq
Na podstawie przykładowych danych wygląda jak numery są klasyfikowane zgodnie z oczekiwaniami, ale kiedy próbowałem wykonawczych to numery gdzie sortowania więcej jak struny ... [10,100,11,9]. Przegapiłem coś?
Mark Carpenter Jr
@MarkCarpenterJr. Nie wiem co masz na myśli. mój przykład poprawnie sortuje typy liczbowe. Czy możesz udostępnić swoją implementację jako pytanie i odnieść się do mnie w komentarzach, abym ją widział? Wtedy mogę sprawdzić.
chriskelly
@MarkCarpenterJr. Właśnie to zauważyłem. Dodałem wyjaśnienie w komentarzach.
chriskelly
32

Zrobiłem dzisiaj dość ogólny sortownik wielu funkcji. Możesz rzucić okiem na thenBy.js tutaj: https://github.com/Teun/thenBy.js

Pozwala na użycie standardowego Array.sort, ale ze stylem firstBy (). ThenBy (). ThenBy (). Jest to o wiele mniej kodu i złożoności niż opisane powyżej rozwiązania.

Teun D
źródło
8
Cóż, gdy zadzwonisz 3 razy, drugie połączenie nie gwarantuje, że kolejność pierwszego pozostanie nietknięta w przypadku przedmiotów, dla których drugie połączenie nie ma znaczenia.
Teun D
13

Poniższa funkcja umożliwia sortowanie tablicy obiektów według jednej lub wielu właściwości, rosnąco (domyślnie) lub malejąco według każdej właściwości, i pozwala wybrać, czy mają być wykonywane porównania z uwzględnieniem wielkości liter. Domyślnie ta funkcja wykonuje sortowanie bez uwzględniania wielkości liter.

Pierwszym argumentem musi być tablica zawierająca obiekty. Kolejne argumenty muszą być listą ciągów oddzielonych przecinkami, które odwołują się do różnych właściwości obiektu do sortowania. Ostatni argument (który jest opcjonalny) jest wartością logiczną, która pozwala wybrać, czy sortować z trueuwzględnieniem wielkości liter, czy nie - użyj go do sortowania z uwzględnieniem wielkości liter.

Funkcja domyślnie posortuje każdą właściwość / klucz w porządku rosnącym. Jeśli chcesz konkretny klucz do sortowania w kolejności malejącej, a następnie zamiast przechodzić w tablicy w tym formacie: ['property_name', true].

Oto kilka przykładowych zastosowań funkcji, po których następuje wyjaśnienie (gdzie homesjest tablicą zawierającą obiekty):

objSort(homes, 'city') -> sortuj według miasta (rosnąco, z uwzględnieniem wielkości liter)

objSort(homes, ['city', true]) -> sortuj według miasta (malejąco, z uwzględnieniem wielkości liter)

objSort(homes, 'city', true)-> sortowanie wg miasta wówczas cena (rosnąco, sprawa wrażliwa )

objSort(homes, 'city', 'price') -> sortuj według miasta, a następnie ceny (zarówno rosnąco, z uwzględnieniem wielkości liter)

objSort(homes, 'city', ['price', true]) -> sortuj według miasta (rosnąco), a następnie ceny (malejąco), z uwzględnieniem wielkości liter)

I bez zbędnych ceregieli, oto funkcja:

function objSort() {
    var args = arguments,
        array = args[0],
        case_sensitive, keys_length, key, desc, a, b, i;

    if (typeof arguments[arguments.length - 1] === 'boolean') {
        case_sensitive = arguments[arguments.length - 1];
        keys_length = arguments.length - 1;
    } else {
        case_sensitive = false;
        keys_length = arguments.length;
    }

    return array.sort(function (obj1, obj2) {
        for (i = 1; i < keys_length; i++) {
            key = args[i];
            if (typeof key !== 'string') {
                desc = key[1];
                key = key[0];
                a = obj1[args[i][0]];
                b = obj2[args[i][0]];
            } else {
                desc = false;
                a = obj1[args[i]];
                b = obj2[args[i]];
            }

            if (case_sensitive === false && typeof a === 'string') {
                a = a.toLowerCase();
                b = b.toLowerCase();
            }

            if (! desc) {
                if (a < b) return -1;
                if (a > b) return 1;
            } else {
                if (a > b) return -1;
                if (a < b) return 1;
            }
        }
        return 0;
    });
} //end of objSort() function

A oto kilka przykładowych danych:

var homes = [{
    "h_id": "3",
    "city": "Dallas",
    "state": "TX",
    "zip": "75201",
    "price": 162500
}, {
    "h_id": "4",
    "city": "Bevery Hills",
    "state": "CA",
    "zip": "90210",
    "price": 1000000
}, {
    "h_id": "5",
    "city": "new york",
    "state": "NY",
    "zip": "00010",
    "price": 1000000
}, {
    "h_id": "6",
    "city": "Dallas",
    "state": "TX",
    "zip": "85000",
    "price": 300000
}, {
    "h_id": "7",
    "city": "New York",
    "state": "NY",
    "zip": "00020",
    "price": 345000
}];
Jake
źródło
8

To kompletny oszustwo, ale myślę, że dodaje wartości do tego pytania, ponieważ jest to w zasadzie funkcja biblioteki w puszkach, z której można korzystać po wyjęciu z pudełka.

Jeśli twój kod ma dostęp lodashlub bibliotekę kompatybilną z lodash, jak na przykład underscore, możesz użyć tej _.sortBymetody. Poniższy fragment kodu jest kopiowany bezpośrednio z dokumentacji lodash .

Skomentowane wyniki w przykładach wyglądają tak, jakby zwracały tablice tablic, ale to tylko pokazuje kolejność, a nie rzeczywiste wyniki, które są tablicą obiektów.

var users = [
  { 'user': 'fred',   'age': 48 },
  { 'user': 'barney', 'age': 36 },
  { 'user': 'fred',   'age': 40 },
  { 'user': 'barney', 'age': 34 }
];

_.sortBy(users, [function(o) { return o.user; }]);
 // => objects for [['barney', 36], ['barney', 34], ['fred', 48], ['fred', 40]]

_.sortBy(users, ['user', 'age']);
// => objects for [['barney', 34], ['barney', 36], ['fred', 40], ['fred', 48]]
Chłopak
źródło
7

Oto kolejny, być może bliższy Twojemu pomysłowi na składnię

function sortObjects(objArray, properties /*, primers*/) {
    var primers = arguments[2] || {}; // primers are optional

    properties = properties.map(function(prop) {
        if( !(prop instanceof Array) ) {
            prop = [prop, 'asc']
        }
        if( prop[1].toLowerCase() == 'desc' ) {
            prop[1] = -1;
        } else {
            prop[1] = 1;
        }
        return prop;
    });

    function valueCmp(x, y) {
        return x > y ? 1 : x < y ? -1 : 0; 
    }

    function arrayCmp(a, b) {
        var arr1 = [], arr2 = [];
        properties.forEach(function(prop) {
            var aValue = a[prop[0]],
                bValue = b[prop[0]];
            if( typeof primers[prop[0]] != 'undefined' ) {
                aValue = primers[prop[0]](aValue);
                bValue = primers[prop[0]](bValue);
            }
            arr1.push( prop[1] * valueCmp(aValue, bValue) );
            arr2.push( prop[1] * valueCmp(bValue, aValue) );
        });
        return arr1 < arr2 ? -1 : 1;
    }

    objArray.sort(function(a, b) {
        return arrayCmp(a, b);
    });
}

// just for fun use this to reverse the city name when sorting
function demoPrimer(str) {
    return str.split('').reverse().join('');
}

// Example
sortObjects(homes, ['city', ['price', 'desc']], {city: demoPrimer});

Demo: http://jsfiddle.net/Nq4dk/2/


Edycja: dla zabawy, oto odmiana, która po prostu przyjmuje ciąg podobny do sql, więc możesz to zrobićsortObjects(homes, "city, price desc")

function sortObjects(objArray, properties /*, primers*/) {
    var primers = arguments[2] || {};

    properties = properties.split(/\s*,\s*/).map(function(prop) {
        prop = prop.match(/^([^\s]+)(\s*desc)?/i);
        if( prop[2] && prop[2].toLowerCase() === 'desc' ) {
            return [prop[1] , -1];
        } else {
            return [prop[1] , 1];
        }
    });

    function valueCmp(x, y) {
        return x > y ? 1 : x < y ? -1 : 0; 
    }

    function arrayCmp(a, b) {
        var arr1 = [], arr2 = [];
        properties.forEach(function(prop) {
            var aValue = a[prop[0]],
                bValue = b[prop[0]];
            if( typeof primers[prop[0]] != 'undefined' ) {
                aValue = primers[prop[0]](aValue);
                bValue = primers[prop[0]](bValue);
            }
            arr1.push( prop[1] * valueCmp(aValue, bValue) );
            arr2.push( prop[1] * valueCmp(bValue, aValue) );
        });
        return arr1 < arr2 ? -1 : 1;
    }

    objArray.sort(function(a, b) {
        return arrayCmp(a, b);
    });
}
Flambino
źródło
to rozwiązanie jest czyste, ale nie wydajne z powodu porównania macierzy. możesz po prostu przejrzeć właściwości, śledzić porównywalną wartość i nie jest to zero, zwróć. to dużo szybciej.
amankapur91
4

Prostszy:

var someArray = [...];

function generateSortFn(props) {
    return function (a, b) {
        for (var i = 0; i < props.length; i++) {
            var prop = props[i];
            var name = prop.name;
            var reverse = prop.reverse;
            if (a[name] < b[name])
                return reverse ? 1 : -1;
            if (a[name] > b[name])
                return reverse ? -1 : 1;
        }
        return 0;
    };
};

someArray.sort(generateSortFn([{name: 'prop1', reverse: true}, {name: 'prop2'}]));
Ravshan Samandarov
źródło
3

Podoba mi się podejście SnowBurnta, ale wymaga to poprawki, aby przetestować równoważność w mieście, a NIE różnicę.

homes.sort(
   function(a,b){
      if (a.city==b.city){
         return (b.price-a.price);
      } else {
         return (a.city-b.city);
      }
   });
James Kenny
źródło
3

Oto ogólne sortowanie wielowymiarowe, umożliwiające odwracanie i / lub mapowanie na każdym poziomie.

Napisane na maszynie. W przypadku JavaScript, sprawdź to JSFiddle

Kod

type itemMap = (n: any) => any;

interface SortConfig<T> {
  key: keyof T;
  reverse?: boolean;
  map?: itemMap;
}

export function byObjectValues<T extends object>(keys: ((keyof T) | SortConfig<T>)[]): (a: T, b: T) => 0 | 1 | -1 {
  return function(a: T, b: T) {
    const firstKey: keyof T | SortConfig<T> = keys[0];
    const isSimple = typeof firstKey === 'string';
    const key: keyof T = isSimple ? (firstKey as keyof T) : (firstKey as SortConfig<T>).key;
    const reverse: boolean = isSimple ? false : !!(firstKey as SortConfig<T>).reverse;
    const map: itemMap | null = isSimple ? null : (firstKey as SortConfig<T>).map || null;

    const valA = map ? map(a[key]) : a[key];
    const valB = map ? map(b[key]) : b[key];
    if (valA === valB) {
      if (keys.length === 1) {
        return 0;
      }
      return byObjectValues<T>(keys.slice(1))(a, b);
    }
    if (reverse) {
      return valA > valB ? -1 : 1;
    }
    return valA > valB ? 1 : -1;
  };
}

Przykłady użycia

Sortowanie tablicy osób według nazwiska, a następnie imienia:

interface Person {
  firstName: string;
  lastName: string;
}

people.sort(byObjectValues<Person>(['lastName','firstName']));

Sortuj kody języków według nazwy , a nie kodu języka (zobacz map), a następnie według wersji malejącej (zobacz reverse).

interface Language {
  code: string;
  version: number;
}

// languageCodeToName(code) is defined elsewhere in code

languageCodes.sort(byObjectValues<Language>([
  {
    key: 'code',
    map(code:string) => languageCodeToName(code),
  },
  {
    key: 'version',
    reverse: true,
  }
]));
Joshua Hansen
źródło
2

Dynamiczny sposób na zrobienie tego za pomocą WIELU klawiszy:

  • filtruj unikalne wartości z każdej kolumny / klucza sortowania
  • uporządkować lub odwrócić
  • dodaj wagi szerokość zeropad dla każdego obiektu na podstawie wartości kluczy indexOf (value)
  • sortuj według obliczonych wag

wprowadź opis obrazu tutaj

Object.defineProperty(Array.prototype, 'orderBy', {
value: function(sorts) { 
    sorts.map(sort => {            
        sort.uniques = Array.from(
            new Set(this.map(obj => obj[sort.key]))
        );

        sort.uniques = sort.uniques.sort((a, b) => {
            if (typeof a == 'string') {
                return sort.inverse ? b.localeCompare(a) : a.localeCompare(b);
            }
            else if (typeof a == 'number') {
                return sort.inverse ? (a < b) : (a > b ? 1 : 0);
            }
            else if (typeof a == 'boolean') {
                let x = sort.inverse ? (a === b) ? 0 : a? -1 : 1 : (a === b) ? 0 : a? 1 : -1;
                return x;
            }
            return 0;
        });
    });

    const weightOfObject = (obj) => {
        let weight = "";
        sorts.map(sort => {
            let zeropad = `${sort.uniques.length}`.length;
            weight += sort.uniques.indexOf(obj[sort.key]).toString().padStart(zeropad, '0');
        });
        //obj.weight = weight; // if you need to see weights
        return weight;
    }

    this.sort((a, b) => {
        return weightOfObject(a).localeCompare( weightOfObject(b) );
    });

    return this;
}
});

Posługiwać się:

// works with string, number and boolean
let sortered = your_array.orderBy([
    {key: "type", inverse: false}, 
    {key: "title", inverse: false},
    {key: "spot", inverse: false},
    {key: "internal", inverse: true}
]);

wprowadź opis obrazu tutaj

Leonardo Filipe
źródło
1

Oto ogólna wersja rozwiązania @ Snowburnt:

var sortarray = [{field:'city', direction:'asc'}, {field:'price', direction:'desc'}];
array.sort(function(a,b){
    for(var i=0; i<sortarray.length; i++){
        retval = a[sortarray[i].field] < b[sortarray[i].field] ? -1 : a[sortarray[i].field] > b[sortarray[i].field] ? 1 : 0;
        if (sortarray[i].direction == "desc") {
            retval = retval * -1;
        }
        if (retval !== 0) {
            return retval;
        }
    }
}


})

Jest to oparte na rutynie sortowania, której używam. Nie testowałem tego konkretnego kodu, więc może zawierać błędy, ale masz pomysł. Chodzi o to, aby posortować dane na podstawie pierwszego pola, które wskazuje na różnicę, a następnie zatrzymać się i przejść do następnego rekordu. Tak więc, jeśli sortujesz według trzech pól, a pierwsze pole w porównaniu wystarczy do określenia kolejności sortowania dwóch sortowanych rekordów, zwróć wynik sortowania i przejdź do następnego rekordu.

Przetestowałem to (właściwie z trochę bardziej złożoną logiką sortowania) na 5000 płyt i zrobiłem to w mgnieniu oka. Jeśli faktycznie ładujesz więcej niż 1000 rekordów do klienta, prawdopodobnie powinieneś używać sortowania i filtrowania po stronie serwera.

Ten kod nie obsługuje rozróżniania wielkości liter, ale pozostawiam czytelnikowi obsługę tej trywialnej modyfikacji.

His DivineShadow
źródło
1

Oto moje rozwiązanie oparte na idiomie transformacji Schwartza , mam nadzieję, że uznasz je za przydatne.

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: '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'));
a8m
źródło
1
Wypróbowałem kilka rzeczy na tej (i innych stronach). To rozwiązanie przez a8m było tylko jednym, które zadziałało w mojej sytuacji: gist.github.com/cemerson/f1f1434286c1262b403f3d85c96688e0
Christopher D. Emerson
1

Inny sposób

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"}
    ];
function sortBy(ar) {
  return ar.sort((a, b) => a.city === b.city ?
      b.price.toString().localeCompare(a.price) :
      a.city.toString().localeCompare(b.city));
}
console.log(sortBy(homes));

Mihai
źródło
0
function sortMultiFields(prop){
    return function(a,b){
        for(i=0;i<prop.length;i++)
        {
            var reg = /^\d+$/;
            var x=1;
            var field1=prop[i];
            if(prop[i].indexOf("-")==0)
            {
                field1=prop[i].substr(1,prop[i].length);
                x=-x;
            }

            if(reg.test(a[field1]))
            {
                a[field1]=parseFloat(a[field1]);
                b[field1]=parseFloat(b[field1]);
            }
            if( a[field1] > b[field1])
                return x;
            else if(a[field1] < b[field1])
                return -x;
        }
    }
}

Jak używać (wstaw - (minus) znak przed polem, jeśli chcesz posortować w kolejności malejącej poszczególne pola)

homes.sort(sortMultiFields(["city","-price"]));

Korzystając z powyższej funkcji, możesz sortować dowolną tablicę json z wieloma polami. W ogóle nie ma potrzeby zmiany ciała funkcji

Nikhil SHETH
źródło
0

Adaptacja odpowiedzi @chriskelly.


Większość odpowiedzi pomija fakt, że cena nie zostanie prawidłowo posortowana, jeśli wartość będzie wynosić dziesięć tysięcy i mniej lub ponad milion. Rezonans JS sortuje alfabetycznie. Tutaj odpowiedź była całkiem dobra. Dlaczego JavaScript nie może sortować "5, 10, 1" i tutaj Jak poprawnie posortować tablicę liczb całkowitych .

Ostatecznie musimy dokonać oceny, jeśli pole lub węzeł, według którego sortujemy, jest liczbą. Nie mówię, że użycie parseInt()w tym przypadku jest poprawną odpowiedzią, ważniejsze są posortowane wyniki.

var homes = [{
  "h_id": "2",
  "city": "Dallas",
  "state": "TX",
  "zip": "75201",
  "price": "62500"
}, {
  "h_id": "1",
  "city": "Dallas",
  "state": "TX",
  "zip": "75201",
  "price": "62510"
}, {
  "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"
}];

homes.sort(fieldSorter(['price']));
// homes.sort(fieldSorter(['zip', '-state', 'price'])); // alternative

function fieldSorter(fields) {
  return function(a, b) {
    return fields
      .map(function(o) {
        var dir = 1;
        if (o[0] === '-') {
          dir = -1;
          o = o.substring(1);
        }
        if (!parseInt(a[o]) && !parseInt(b[o])) {
          if (a[o] > b[o]) return dir;
          if (a[o] < b[o]) return -(dir);
          return 0;
        } else {
          return dir > 0 ? a[o] - b[o] : b[o] - a[o];
        }
      })
      .reduce(function firstNonZeroValue(p, n) {
        return p ? p : n;
      }, 0);
  };
}
document.getElementById("output").innerHTML = '<pre>' + JSON.stringify(homes, null, '\t') + '</pre>';
<div id="output">

</div>


Skrzypce do przetestowania

Mark Carpenter Jr
źródło
Problem dotyczy danych, które próbujesz posortować. pricew przykładzie jest w formacie ciągu. Jeśli chcesz, aby działało poprawnie z moim przykładem, użyj mapy, aby najpierw przekonwertować pole, które chcesz sformatować. ieconst correctedHomes = homes.map(h => ({...h, price: +h.price}))
chriskelly
0

Wow, są tutaj złożone rozwiązania. Tak skomplikowane, że postanowiłem wymyślić coś prostszego, ale też dość potężnego. Tutaj jest;

function sortByPriority(data, priorities) {
  if (priorities.length == 0) {
    return data;
  }

  const nextPriority = priorities[0];
  const remainingPriorities = priorities.slice(1);

  const matched = data.filter(item => item.hasOwnProperty(nextPriority));
  const remainingData = data.filter(item => !item.hasOwnProperty(nextPriority));

  return sortByPriority(matched, remainingPriorities)
    .sort((a, b) => (a[nextPriority] > b[nextPriority]) ? 1 : -1)
    .concat(sortByPriority(remainingData, remainingPriorities));
}

A oto przykład tego, jak go używasz.

const data = [
  { id: 1,                         mediumPriority: 'bbb', lowestPriority: 'ggg' },
  { id: 2, highestPriority: 'bbb', mediumPriority: 'ccc', lowestPriority: 'ggg' },
  { id: 3,                         mediumPriority: 'aaa', lowestPriority: 'ggg' },
];

const priorities = [
  'highestPriority',
  'mediumPriority',
  'lowestPriority'
];


const sorted = sortByPriority(data, priorities);

Spowoduje to najpierw sortowanie według pierwszeństwa atrybutów, a następnie według wartości atrybutów.

Steztric
źródło
0

Oto rozszerzalny sposób sortowania według wielu pól.

homes.sort(function(left, right) {
    var city_order = left.city.localeCompare(right.city);
    var price_order = parseInt(left.price) - parseInt(right.price);
    return city_order || -price_order;
});

Uwagi

  • a.localeCompare(b)jest powszechnie obsługiwana i wraca -1,0,1 jeśli a<b, a==b, a>bodpowiednio.
  • Odejmowanie działa na polach numerycznych.
  • ||w ostatniej linii daje citypierwszeństwo price.
  • Negate, aby odwrócić kolejność w dowolnym polu, jak w -price_order
  • Porównanie data , var date_order = new Date(left.date) - new Date(right.date);działa jak numerycznych ponieważ data matematyki zamienia się w milisekundach od 1970 roku.
  • Dodaj pola do łańcucha lub, return city_order || -price_order || date_order;
Bob Stein
źródło
0

Myślę, że to może być najłatwiejszy sposób na zrobienie tego.

https://coderwall.com/p/ebqhca/javascript-sort-by-two-fields

To naprawdę proste i wypróbowałem je z 3 różnymi parami klucz-wartość i działało świetnie.

Oto prosty przykład, spójrz na link, aby uzyskać więcej informacji

testSort(data) {
    return data.sort(
        a['nameOne'] > b['nameOne'] ? 1
        : b['nameOne'] > a['nameOne'] ? -1 : 0 ||
        a['date'] > b['date'] ||
        a['number'] - b['number']
    );
}
JDinar
źródło
0

Oto moje w celach informacyjnych, z przykładem:

function msort(arr, ...compFns) {
  let fn = compFns[0];
  arr = [].concat(arr);
  let arr1 = [];
  while (arr.length > 0) {
    let arr2 = arr.splice(0, 1);
    for (let i = arr.length; i > 0;) {
      if (fn(arr2[0], arr[--i]) === 0) {
        arr2 = arr2.concat(arr.splice(i, 1));
      }
    }
    arr1.push(arr2);
  }

  arr1.sort(function (a, b) {
    return fn(a[0], b[0]);
  });

  compFns = compFns.slice(1);
  let res = [];
  arr1.map(a1 => {
    if (compFns.length > 0) a1 = msort(a1, ...compFns);
    a1.map(a2 => res.push(a2));
  });
  return res;
}

let tstArr = [{ id: 1, sex: 'o' }, { id: 2, sex: 'm' }, { id: 3, sex: 'm' }, { id: 4, sex: 'f' }, { id: 5, sex: 'm' }, { id: 6, sex: 'o' }, { id: 7, sex: 'f' }];

function tstFn1(a, b) {
  if (a.sex > b.sex) return 1;
  else if (a.sex < b.sex) return -1;
  return 0;
}

function tstFn2(a, b) {
  if (a.id > b.id) return -1;
  else if (a.id < b.id) return 1;
  return 0;
}

console.log(JSON.stringify(msort(tstArr, tstFn1, tstFn2)));
//output:
//[{"id":7,"sex":"f"},{"id":4,"sex":"f"},{"id":5,"sex":"m"},{"id":3,"sex":"m"},{"id":2,"sex":"m"},{"id":6,"sex":"o"},{"id":1,"sex":"o"}]
zamek błyskawiczny
źródło
0

Szukałem czegoś podobnego i skończyło się na tym:

Najpierw mamy jedną lub więcej funkcji sortujących, zawsze zwracających 0, 1 lub -1:

const sortByTitle = (a, b): number => 
  a.title === b.title ? 0 : a.title > b.title ? 1 : -1;

Możesz utworzyć więcej funkcji dla każdej innej właściwości, według której chcesz sortować.

Następnie mam funkcję, która łączy te funkcje sortowania w jedną:

const createSorter = (...sorters) => (a, b) =>
  sorters.reduce(
    (d, fn) => (d === 0 ? fn(a, b) : d),
    0
  );

Można to wykorzystać do połączenia powyższych funkcji sortowania w czytelny sposób:

const sorter = createSorter(sortByTitle, sortByYear)

items.sort(sorter)

Gdy funkcja sortowania zwróci 0, zostanie wywołana następna funkcja sortowania w celu dalszego sortowania.

Soesah
źródło
0

Po prostu inna opcja. Rozważ użycie następującej funkcji narzędzia:

/** Performs comparing of two items by specified properties
 * @param  {Array} props for sorting ['name'], ['value', 'city'], ['-date']
 * to set descending order on object property just add '-' at the begining of property
 */
export const compareBy = (...props) => (a, b) => {
  for (let i = 0; i < props.length; i++) {
    const ascValue = props[i].startsWith('-') ? -1 : 1;
    const prop = props[i].startsWith('-') ? props[i].substr(1) : props[i];
    if (a[prop] !== b[prop]) {
      return a[prop] > b[prop] ? ascValue : -ascValue;
    }
  }
  return 0;
};

Przykład użycia (w Twoim przypadku):

homes.sort(compareBy('city', '-price'));

Należy zauważyć, że tę funkcję można jeszcze bardziej uogólnić, aby móc używać zagnieżdżonych właściwości, takich jak „address.city” lub „style.size.width” itp.

Dmitry Anch
źródło
0

Jest to algorytm rekurencyjny do sortowania według wielu pól z możliwością formatowania wartości przed porównaniem.

var data = [
{
    "id": 1,
    "ship": null,
    "product": "Orange",
    "quantity": 7,
    "price": 92.08,
    "discount": 0
},
{
    "id": 2,
    "ship": "2017-06-14T23:00:00.000Z".toDate(),
    "product": "Apple",
    "quantity": 22,
    "price": 184.16,
    "discount": 0
},
...
]
var sorts = ["product", "quantity", "ship"]

// comp_val formats values and protects against comparing nulls/undefines
// type() just returns the variable constructor
// String.lower just converts the string to lowercase.
// String.toDate custom fn to convert strings to Date
function comp_val(value){
    if (value==null || value==undefined) return null
    var cls = type(value)
    switch (cls){
        case String:
            return value.lower()
    }
    return value
}

function compare(a, b, i){
    i = i || 0
    var prop = sorts[i]
    var va = comp_val(a[prop])
    var vb = comp_val(b[prop])

    // handle what to do when both or any values are null
    if (va == null || vb == null) return true

    if ((i < sorts.length-1) && (va == vb)) {
        return compare(a, b, i+1)
    } 
    return va > vb
}

var d = data.sort(compare);
console.log(d);

Jeśli a i b są równe, po prostu spróbuje przejść do następnego pola, aż żadne nie będzie dostępne.

Mackraken
źródło
-1
homes.sort(function(a,b) { return a.city - b.city } );
homes.sort(function(a,b){
    if (a.city==b.city){
        return parseFloat(b.price) - parseFloat(a.price);
    } else {
        return 0;
    }
});
Jonan Pineda
źródło
Dlaczego po prostu nie umieścić wszystkiego w jednej funkcji? Jeśli miasto nie jest równe, zwróć ich różnicę, w przeciwnym razie porównaj cenę.
Mad Physicist
-1

Tutaj „AffiliateDueDate” i „Title” są kolumnami, obie są posortowane w kolejności rosnącej.

array.sort(function(a, b) {

               if (a.AffiliateDueDate > b.AffiliateDueDate ) return 1;
               else if (a.AffiliateDueDate < b.AffiliateDueDate ) return -1;
               else if (a.Title > b.Title ) return 1;
               else if (a.Title < b.Title ) return -1;
               else return 0;
             })
Amay Kulkarni
źródło
-1

Sortowanie według dwóch pól daty i przykład pola liczbowego:

var generic_date =  new Date(2070, 1, 1);
checkDate = function(date) {
  return Date.parse(date) ? new Date(date): generic_date;
}

function sortData() {  
  data.sort(function(a,b){
    var deltaEnd = checkDate(b.end) - checkDate(a.end);
    if(deltaEnd) return deltaEnd;

    var deltaRank = a.rank - b.rank;
    if (deltaRank) return deltaRank;

    var deltaStart = checkDate(b.start) - checkDate(a.start);
    if(deltaStart) return deltaStart;

    return 0;
  });
}

http://jsfiddle.net/hcWgf/57/

Igor Vaschuk
źródło
-1
function sort(data, orderBy) {
        orderBy = Array.isArray(orderBy) ? orderBy : [orderBy];
        return data.sort((a, b) => {
            for (let i = 0, size = orderBy.length; i < size; i++) {
                const key = Object.keys(orderBy[i])[0],
                    o = orderBy[i][key],
                    valueA = a[key],
                    valueB = b[key];
                if (!(valueA || valueB)) {
                    console.error("the objects from the data passed does not have the key '" + key + "' passed on sort!");
                    return [];
                }
                if (+valueA === +valueA) {
                    return o.toLowerCase() === 'desc' ? valueB - valueA : valueA - valueB;
                } else {
                    if (valueA.localeCompare(valueB) > 0) {
                        return o.toLowerCase() === 'desc' ? -1 : 1;
                    } else if (valueA.localeCompare(valueB) < 0) {
                        return o.toLowerCase() === 'desc' ? 1 : -1;
                    }
                }
            }
        });
    }

Za pomocą :

sort(homes, [{city : 'asc'}, {price: 'desc'}])

Elias Pinheiro
źródło
-1

A co z tym prostym rozwiązaniem:

const sortCompareByCityPrice = (a, b) => {
    let comparison = 0
    // sort by first criteria
    if (a.city > b.city) {
        comparison = 1
    }
    else if (a.city < b.city) {
        comparison = -1
    }
    // If still 0 then sort by second criteria descending
    if (comparison === 0) {
        if (parseInt(a.price) > parseInt(b.price)) {
            comparison = -1
        }
        else if (parseInt(a.price) < parseInt(b.price)) {
            comparison = 1
        }
    }
    return comparison 
}

Na podstawie tego pytania javascript sortuj tablicę według wielu pól (liczb)

ramvanet
źródło