Comment trier un tableau d'objets par plusieurs champs?

148

À partir de cette question d'origine , comment appliquer un tri sur plusieurs champs?

En utilisant cette structure légèrement adaptée, comment puis-je trier la ville (croissant) et ensuite le prix (décroissant)?

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

J'ai aimé le fait qu'une réponse a été donnée qui a fourni une approche générale. Là où je prévois d'utiliser ce code, je devrai trier les dates ainsi que d'autres choses. La capacité à «amorcer» l'objet semblait pratique, sinon un peu encombrante.

J'ai essayé de transformer cette réponse en un bel exemple générique, mais je n'ai pas beaucoup de chance.

Mike
la source
Voulez-vous rechercher ou trier?
Felix Kling
Quel est exactement le problème que vous rencontrez avec la deuxième réponse que vous avez liée?
canon du
Ce n'est pas assez générique. Il semble que j'ajoute une mer de code alors que je voudrais simplement dire que sort(["first-field", "ASC"], ["second-field", "DSC"]); c'est encore plus compliqué lorsque j'essaie d'ajouter la logique «d'amorce» de la première réponse afin de pouvoir gérer les dates, l'insensibilité à la casse, etc.
Mike
Ou vous pouvez appliquer un poids à chaque champ
onmyway133
Vous pouvez vérifier lodash.com/docs/4.17.11#orderBy , si vous êtes d'accord avec lodash
Deepanshu Arora

Réponses:

83

Une méthode de tri multidimensionnelle, basée sur cette réponse :

Mise à jour : Voici une version "optimisée". Il fait beaucoup plus de prétraitement et crée au préalable une fonction de comparaison pour chaque option de tri. Il peut avoir besoin de plus de mémoire (car il stocke une fonction pour chaque option de tri, mais il devrait se préformer un peu mieux car il n'a pas à déterminer les paramètres corrects lors de la comparaison. Je n'ai cependant pas fait de profilage).

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

Exemple d'utilisation:

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

DEMO


Fonction d'origine:

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

DEMO

Félix Kling
la source
2
Pour mémoire, cette fonction pourrait encore être améliorée en prétraitant la liste d'arguments et en créant un "tableau d'options de tri" uniforme. Ceci est laissé comme exercice pour le lecteur;)
Felix Kling
@Mike: Ok ... enfin;) Vous voyez que c'est plus complexe maintenant, car les options sont prétraitées, mais la fonction de comparaison finale (voir commentaire) est beaucoup plus simple ce qui (espérons-le) conduit à de meilleures performances. Plus vous disposez d'options de tri, plus cette méthode est avantageuse.
Felix Kling
167

pour une solution simple et non générique à votre problème exact:

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;
   });
Snowburnt
la source
6
Je pense que cette démo est ce que l'OP veut => jsfiddle.net/zJ6UA/533
Amin Jafari
3
Cela a la bonne idée, mais la logique est complètement fausse. Vous ne pouvez pas soustraire une chaîne non numérique d'une autre chaîne et l' ifinstruction n'a aucun sens.
JLRishe
6
Vous pouvez utiliser a.localeCompare(b)dans la dernière ligne pour la comparaison de chaîne ... voir la documentation
Michael P
2
La première comparaison de villes ne devrait-elle pas vérifier l'égalité et non l'inégalité? En d'autres termes, la ligne ne devrait-elle pas l'être if (a.city === b.city)? Autrement dit, si les deux villes sont identiques, comparez les prix, sinon comparez les villes.
Steven Rands
2
une des meilleures réponses. tnx.
jonathana
56

Vous pouvez utiliser une approche de tri chaîné en prenant le delta des valeurs jusqu'à ce qu'il atteigne une valeur différente de zéro.

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

Ou, en utilisant es6, simplement:

data.sort((a, b) => a.city.localeCompare(b.city) || b.price - a.price);
Nina Scholz
la source
17
Est-ce que je manque quelque chose? Pourquoi utiliser 60 lignes de code pour quelque chose qui peut être fait en 1. Simple, clair, concis. Devrait être la réponse acceptée IMO.
Erez Cohen
l'un des gros problèmes de SO est maintenant que les anciennes réponses - souvent bien remplacées par de meilleures solutions utilisant de nouvelles fonctionnalités de langage (par exemple ES5-6-7) conservent leurs anciens scores, et nous devons tous faire défiler vers le bas pour trouver le "vrai" meilleur solutions! SO devrait expirer les votes au fil du temps pour résoudre ce problème, car le problème s'aggrave avec le temps.
Andy Lorenz le
53

Voici une approche fonctionnelle simple. Spécifiez l'ordre de tri à l'aide du tableau. Ajoutez moins pour spécifier l'ordre décroissant.

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

Edit: dans ES6 c'est encore plus court!

"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
la source
6
J'ai trouvé cette fonction assez soignée, j'ai donc apporté une petite amélioration des performances allant jusqu'à 90% en fonction de l'analyseur. J'ai fait un point essentiel et la suite de tests .
php_nub_qq
Sur la base des données de l' échantillon , il ressemble à un nombre sont triés comme prévu, mais quand j'ai essayé la mise en œuvre ce nombre , où le tri plus comme des cordes ... [10,100,11,9]. Ai-je oublié quelque chose?
Mark Carpenter Jr
@MarkCarpenterJr. Pas sûr de ce que vous voulez dire. mon exemple trie correctement les types numériques. Pouvez-vous partager votre implémentation sous forme de question et me référer dans les commentaires pour que je la voie? Ensuite, je peux vérifier.
chriskelly
@MarkCarpenterJr. Je viens de le repérer. J'ai ajouté une explication dans les commentaires.
chriskelly
32

J'ai fait aujourd'hui un trieur multifonction assez générique. Vous pouvez consulter thenBy.js ici: https://github.com/Teun/thenBy.js

Il vous permet d'utiliser le standard Array.sort, mais avec le style firstBy (). ThenBy (). ThenBy (). C'est beaucoup moins de code et de complexité que les solutions affichées ci-dessus.

Teun D
la source
8
Eh bien, lorsque vous appelez 3 fois, il n'est pas garanti que le deuxième appel laisse intact l'ordre du premier pour les éléments où le deuxième appel ne fait pas de différence.
Teun D
13

La fonction suivante vous permettra de trier un tableau d'objets sur une ou plusieurs propriétés, par ordre croissant (par défaut) ou décroissant sur chaque propriété, et vous permettra de choisir d'effectuer ou non des comparaisons sensibles à la casse. Par défaut, cette fonction effectue des tris insensibles à la casse.

Le premier argument doit être le tableau contenant les objets. Le ou les arguments suivants doivent être une liste de chaînes séparées par des virgules qui référencent les différentes propriétés d'objet à trier. Le dernier argument (qui est facultatif) est un booléen pour choisir d'effectuer ou non des tris sensibles à la casse - à utiliser truepour les tris sensibles à la casse.

La fonction triera chaque propriété / clé par ordre croissant par défaut. Si vous voulez une clé particulière pour trier par ordre décroissant, puis passer à la place dans un tableau dans ce format: ['property_name', true].

Voici quelques exemples d'utilisation de la fonction suivis d'une explication (où se homestrouve un tableau contenant les objets):

objSort(homes, 'city') -> trier par ville (croissant, sensible à la casse)

objSort(homes, ['city', true]) -> trier par ville (décroissant, sensible à la casse)

objSort(homes, 'city', true)-> trier par ville puis prix (croissant, sensible à la casse )

objSort(homes, 'city', 'price') -> trier par ville puis prix (à la fois croissant, sensible à la casse)

objSort(homes, 'city', ['price', true]) -> trier par ville (croissant) puis prix (décroissant), sensible à la casse)

Et sans plus tarder, voici la fonction:

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

Et voici quelques exemples de données:

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
la source
8

C'est une triche complète, mais je pense que cela ajoute de la valeur à cette question car il s'agit essentiellement d'une fonction de bibliothèque prédéfinie que vous pouvez utiliser prête à l'emploi.

Si votre code a accès à lodashou à une bibliothèque compatible avec lodash, underscorevous pouvez utiliser la _.sortByméthode. L'extrait ci-dessous est copié directement à partir de la documentation de lodash .

Les résultats commentés dans les exemples semblent renvoyer des tableaux de tableaux, mais cela montre simplement l'ordre et non les résultats réels qui sont un tableau d'objets.

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]]
Gars
la source
7

En voici un autre qui est peut-être plus proche de votre idée de la syntaxe

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

Démo: http://jsfiddle.net/Nq4dk/2/


Edit: Juste pour le plaisir, voici une variation qui prend juste une chaîne de type SQL, donc vous pouvez le fairesortObjects(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
la source
cette solution est propre mais pas performante en raison de la comparaison des baies. vous pouvez simplement parcourir les propriétés pour garder une trace de la valeur comparée et ce n'est pas zéro, retour. c'est beaucoup plus rapide.
amankapur91
4

Plus simple:

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
la source
3

J'aime l'approche de SnowBurnt, mais il faut un ajustement pour tester l'équivalence sur la ville PAS une différence.

homes.sort(
   function(a,b){
      if (a.city==b.city){
         return (b.price-a.price);
      } else {
         return (a.city-b.city);
      }
   });
James Kenny
la source
3

Voici un tri multidimensionnel générique, permettant l'inversion et / ou le mappage à chaque niveau.

Écrit en tapuscrit. Pour Javascript, consultez ce JSFiddle

Le code

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

Exemples d'utilisation

Trier un tableau de personnes par nom de famille, puis par prénom:

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

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

Triez les codes de langue par leur nom , pas leur code de langue (voir map), puis par version décroissante (voir 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
la source
2

Une façon dynamique de faire cela avec PLUSIEURS touches:

  • filtrer les valeurs uniques de chaque col / clé de tri
  • mettre en ordre ou inverser
  • ajouter des poids width zeropad pour chaque objet en fonction des valeurs des clés indexOf (value)
  • trier à l'aide de poids calculés

entrez la description de l'image ici

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

Utilisation:

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

entrez la description de l'image ici

Léonard de Filipe
la source
1

Voici une version générique de la solution de @ 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;
        }
    }
}


})

Ceci est basé sur une routine de tri que j'utilise. Je n'ai pas testé ce code spécifique, il peut donc contenir des erreurs, mais vous voyez l'idée. L'idée est de trier en fonction du premier champ qui indique une différence, puis de s'arrêter et de passer à l'enregistrement suivant. Donc, si vous triez selon trois champs et que le premier champ de la comparaison est suffisant pour déterminer l'ordre de tri des deux enregistrements en cours de tri, renvoyez ce résultat de tri et passez à l'enregistrement suivant.

Je l'ai testé (en fait avec une logique de tri un peu plus complexe) sur 5000 disques et il l'a fait en un clin d'œil. Si vous chargez réellement plus de 1000 enregistrements sur le client, vous devriez probablement utiliser le tri et le filtrage côté serveur.

Ce code ne gère pas la casse mais je laisse au lecteur le soin de gérer cette modification triviale.

HisDivineShadow
la source
1

Voici ma solution basée sur l' idiome de transformation Schwartzian , j'espère que vous la trouverez utile.

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

Voici un exemple comment l'utiliser:

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
la source
1
J'ai essayé quelques choses à ce sujet (et sur d'autres pages). Cette solution par a8m était la seule à fonctionner pour ma situation: gist.github.com/cemerson/f1f1434286c1262b403f3d85c96688e0
Christopher D. Emerson
1

Autrement

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

Comment utiliser (mettre - (moins) signe avant le champ si vous souhaitez trier par ordre décroissant un champ particulier)

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

En utilisant la fonction ci-dessus, vous pouvez trier n'importe quel tableau json avec plusieurs champs. Pas besoin de changer de corps de fonction du tout

Nikhil sHETH
la source
0

Adaptation de la réponse de @chriskelly.


La plupart des réponses oublient que le prix ne sera pas trié correctement si la valeur se situe dans les dizaines de milliers et moins ou plus d'un million. La resaon étant JS trie par ordre alphabétique. Il a été répondu assez bien ici, Pourquoi JavaScript ne peut pas trier "5, 10, 1" et ici Comment trier correctement un tableau d'entiers .

En fin de compte, nous devons faire une évaluation si le champ ou le nœud sur lequel nous trions est un nombre. Je ne dis pas que l'utilisation parseInt()dans ce cas est la bonne réponse, les résultats triés sont plus importants.

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>


Un violon à tester

Mark Carpenter Jr
la source
Le problème vient des données que vous essayez de trier. pricedans l'exemple est au format chaîne. Si vous voulez que cela fonctionne correctement avec mon exemple, utilisez map pour convertir d'abord le champ que vous souhaitez en format numérique. ieconst correctedHomes = homes.map(h => ({...h, price: +h.price}))
chriskelly
0

Wow, il y a des solutions complexes ici. Si complexe, j'ai décidé de proposer quelque chose de plus simple mais aussi assez puissant. C'est ici;

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

Et voici un exemple de la façon dont vous l'utilisez.

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

Cela triera d'abord par ordre de priorité des attributs, puis par valeur des attributs.

Steztric
la source
0

Voici un moyen extensible de trier par plusieurs champs.

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

Remarques

  • a.localeCompare(b)est universellement supporté et retourne -1,0,1 si a<b, a==b, a>brespectivement.
  • La soustraction fonctionne sur les champs numériques.
  • ||dans la dernière ligne donne la citypriorité sur price.
  • Annuler pour inverser l'ordre dans n'importe quel champ, comme dans -price_order
  • Comparaison de la date , var date_order = new Date(left.date) - new Date(right.date);fonctionne comme numerics parce que les mathématiques date se transforme en millisecondes depuis 1970.
  • Ajouter des champs dans la chaîne or, return city_order || -price_order || date_order;
Bob Stein
la source
0

Je pense que c'est peut-être le moyen le plus simple de le faire.

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

C'est vraiment simple et je l'ai essayé avec 3 paires de valeurs clés différentes et cela a très bien fonctionné.

Voici un exemple simple, regardez le lien pour plus de détails

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
la source
0

Voici le mien pour votre référence, avec exemple:

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"}]
fermeture éclair
la source
0

Je cherchais quelque chose de similaire et j'ai fini avec ceci:

Tout d'abord, nous avons une ou plusieurs fonctions de tri, renvoyant toujours 0, 1 ou -1:

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

Vous pouvez créer plus de fonctions pour chaque autre propriété sur laquelle vous souhaitez trier.

Ensuite, j'ai une fonction qui combine ces fonctions de tri en une seule:

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

Cela peut être utilisé pour combiner les fonctions de tri ci-dessus de manière lisible:

const sorter = createSorter(sortByTitle, sortByYear)

items.sort(sorter)

Lorsqu'une fonction de tri renvoie 0, la fonction de tri suivante sera appelée pour un tri ultérieur.

Soesah
la source
0

Juste une autre option. Pensez à utiliser la fonction utilitaire suivante:

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

Exemple d'utilisation (dans votre cas):

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

Il est à noter que cette fonction peut être encore plus généralisée afin de pouvoir utiliser des propriétés imbriquées comme 'address.city' ou 'style.size.width' etc.

Ancre Dmitry
la source
0

Il s'agit d'un algorithme récursif permettant de trier par plusieurs champs tout en ayant la possibilité de formater les valeurs avant comparaison.

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

Si a et b sont égaux, il essaiera simplement le champ suivant jusqu'à ce qu'aucun ne soit disponible.

Mackraken
la source
-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
la source
Pourquoi ne pas tout mettre en une seule fonction? Si la ville n'est pas égale, renvoyez-en le diff, sinon, différez le prix.
Mad Physicist
-1

Ici, «AffiliateDueDate» et «Title» sont des colonnes, les deux sont triées par ordre croissant.

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
la source
-1

Tri sur deux champs de date et un exemple de champ numérique:

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

En utilisant :

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

Elias Pinheiro
la source
-1

Que diriez-vous de cette solution simple:

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 
}

Basé sur cette question, javascript trie le tableau par plusieurs champs (nombre)

ramvanet
la source