web-dev-qa-db-fra.com

Comment trier un tableau d'objets par plusieurs champs?

De cette question originale , comment pourrais-je appliquer un tri sur plusieurs champs?

En utilisant cette structure légèrement adaptée, comment pourrais-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 answer ait été fournie, laquelle fournissait une approche générale. Là où je compte utiliser ce code, je devrai trier les dates ainsi que d’autres choses. La capacité à "amorcer" l’objet semblait utile, si ce n’est un peu encombrant.

J'ai essayé de construire cette answer dans un exemple générique de Nice, mais je n'ai pas beaucoup de chance.

86
Mike

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

Update: Voici une version "optimisée". Il effectue beaucoup plus de prétraitement et crée au préalable une fonction de comparaison pour chaque option de tri. Il faudra peut-être plus de mémoire (car il stocke une fonction pour chaque option de tri, mais il devrait être un peu plus performant, car il n'est pas nécessaire de déterminer les paramètres corrects lors de la comparaison. Je n'ai cependant pas effectué 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}));

D&EACUTE;MO


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

D&EACUTE;MO

65
Felix Kling

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

Voici une approche fonctionnelle simple. Spécifiez l'ordre de tri en utilisant un tableau. Ajoutez le préfixe minus 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>')

32
chriskelly

J'ai fabriqué une trieuse multifonctions assez générique aujourd'hui. Vous pouvez consulter thenBy.js ici: https://github.com/Teun/thenBy.js

Cela 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 présentées ci-dessus.

29
Teun D

La fonction suivante vous permettra de trier un tableau d'objets sur une ou plusieurs propriétés, par ordre croissant ou par défaut, et vous permettra de choisir d'effectuer ou non des comparaisons sensibles à la casse. Par défaut, cette fonction effectue des tris sans distinction de 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, faisant référence aux 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 - utilisez true pour les tris sensibles à la casse.

La fonction va trier chaque propriété/clé par ordre croissant. Si vous souhaitez qu'une clé particulière soit triée par ordre décroissant, transmettez plutôt un tableau au format suivant: ['property_name', true].

Voici quelques exemples d'utilisations de la fonction suivis d'une explication (où homes est un tableau contenant les objets):

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

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

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

objSort(homes, 'city', 'price') -> trier par ville puis par prix (tous les deux croissants, insensibles à 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
}];
11
jake

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 variante qui prend juste une chaîne de type SQL, de sorte que vous pouvez faire 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);
    });
}
7
Flambino

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 immédiatement.

Si votre code a accès à lodash ou à une bibliothèque compatible lodash telle que underscore, vous pouvez utiliser la méthode _.sortBy. L'extrait ci-dessous est copié directement à partir de lodash documentation .

Les résultats commentés dans les exemples donnent l'impression qu'ils renvoient des tableaux de tableaux, mais il s'agit simplement d'indiquer 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]]
5
Guy

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'}]));
4
Ravshan Samandarov

J'aime l'approche de SnowBurnt, mais il faut un Tweak pour tester l'équivalence sur city, 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);
      }
   });
3
james kenny

Vous pouvez utiliser une approche de tri chaîné en prenant le delta de valeurs jusqu'à atteindre 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; }

1
Nina Scholz

Voici ma solution basée sur le idiome de transformation de Schwartzian , j'espère que vous le 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 d'utilisation:

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

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 y avoir des erreurs, mais vous avez une idée. L'idée est de trier en fonction du premier champ indiquant une différence, puis de s'arrêter et de passer à l'enregistrement suivant. Ainsi, si vous triez par trois champs et que le premier champ de la comparaison suffit à déterminer l'ordre de tri des deux enregistrements en cours de tri, renvoyez le résultat du tri et passez à l'enregistrement suivant. 

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

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

1
HisDivineShadow

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

1
Mihai

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 convient de noter que cette fonction peut être encore plus généralisée afin de pouvoir utiliser des propriétés imbriquées telles que 'address.city' ou 'style.size.width' etc.

0
Dmitry Anch

Adaptation de la réponse de @chriskelly. 


La plupart des réponses négligent le fait que le prix ne sera pas réglé correctement si la valeur est comprise dans les dizaines de milliers et inférieure ou supérieure à un million. La résonance étant JS trie par ordre alphabétique. Il a été très bien répondu 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 procéder à une évaluation si le champ ou le noeud sur lequel nous effectuons le tri est un nombre. Je ne dis pas que l'utilisation de parseInt() dans ce cas est la réponse correcte, 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 avec

0
Mark Carpenter Jr

Wow, il y a des solutions complexes ici. Tellement complexe que 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 la priorité des attributs, puis par la valeur des attributs.

0
Steztric

Voici un tri générique multidimensionnel, permettant d'inverser et/ou de mapper à chaque niveau.

Écrit en TypeScript. Pour Javascript, regardez ceci 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

Tri d'un tableau de personnes par nom de famille, puis prénom:

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

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

Triez les codes de langue par leur nom nom , et non par leur code de langue (voir map), puis par décroissant version (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,
  }
]));
0
Joshua Hansen

solution simple:

items.sort((a, b) => {
  const compare_name = a.name.localeCompare(b.name);
  const compare_title = a.title.localeCompare(b.title);
  const compare_city = a.city.localeCompare(b.city);

  return compare_name || compare_title || compare_city;
});

Si vous avez besoin de trier plus de champs, ajoutez plus ||

0
Leonardo Filipe

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 pris en charge et renvoie -1,0,1 si a<b, a==b, a>b respectivement.
  • La soustraction fonctionne sur des champs numériques.
  • || dans la dernière ligne donne city priorité sur price.
  • Inverser l'ordre inverse dans n'importe quel champ, comme dans -price_order
  • La comparaison date , var date_order = new Date(left.date) - new Date(right.date); fonctionne comme une méthode numérique car date math se transforme en millisecondes depuis 1970.
  • Ajouter des champs dans la chaîne ou, return city_order || -price_order || date_order;
0
Bob Stein

Il s'agit d'un algorithme récursif permettant de trier selon plusieurs champs tout en ayant la possibilité de formater les valeurs avant la 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 que plus aucun ne soit disponible.

0
Mackraken

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 très 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']
    );
}
0
JDinar
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 le signe (mettre - (moins) avant champ si vous souhaitez trier par ordre décroissant le champ)

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 le corps de la fonction

0
Nikhil sHETH

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

Je cherchais quelque chose de similaire et je me suis retrouvé avec ceci:

Nous avons d’abord 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 propriété que 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
  );

Ceci 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.

0
Soesah