javascript - recherche - sorted array js




Trier le tableau d'objets par valeur de propriété de chaîne (20)

Tri (plus) Tableaux complexes d'objets

Étant donné que vous rencontrez probablement des structures de données plus complexes comme ce tableau, je développerais la solution.

TL; DR

Une version plus connectable est-elle basée sur la très belle answer @ege-Özcan .

Problème

J'ai rencontré le dessous et je ne pouvais pas le changer. Je ne voulais pas non plus aplatir l'objet temporairement. Je ne voulais pas non plus utiliser le trait de soulignement / lodash, principalement pour des raisons de performance et le plaisir de le mettre en œuvre moi-même.

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

Objectif

Le but est de le trier principalement par People.Name.name et ensuite par People.Name.surname

Obstacles

Désormais, dans la solution de base, la notation entre crochets permet de calculer les propriétés à trier de manière dynamique. Ici, cependant, nous devrions également construire la notation entre crochets de manière dynamique, car vous vous attendriez à ce que certaines People['Name.name'] comme People['Name.name'] fonctionnent - ce qui ne fonctionne pas.

Faire simplement People['Name']['name'] , en revanche, est statique et ne vous permet que de descendre au n- ième niveau.

Solution

Le principal ajout ici sera de parcourir l’arborescence des objets et de déterminer la valeur de la dernière feuille, que vous devez spécifier, ainsi que toute feuille intermédiaire.

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

People.sort(dynamicMultiSort(['Name','name'], ['Name', '-surname']));
// Results in...
// [ { Name: { name: 'AAA', surname: 'ZZZ' }, Middlename: 'Abrams' },
//   { Name: { name: 'Name', surname: 'Surname' }, Middlename: 'JJ' },
//   { Name: { name: 'Name', surname: 'AAA' }, Middlename: 'Wars' } ]

// same logic as above, but strong deviation for dynamic properties 
function dynamicSort(properties) {
  var sortOrder = 1;
  // determine sort order by checking sign of last element of array
  if(properties[properties.length - 1][0] === "-") {
    sortOrder = -1;
    // Chop off sign
    properties[properties.length - 1] = properties[properties.length - 1].substr(1);
  }
  return function (a,b) {
    propertyOfA = recurseObjProp(a, properties)
    propertyOfB = recurseObjProp(b, properties)
    var result = (propertyOfA < propertyOfB) ? -1 : (propertyOfA > propertyOfB) ? 1 : 0;
    return result * sortOrder;
  };
}

/**
 * Takes an object and recurses down the tree to a target leaf and returns it value
 * @param  {Object} root - Object to be traversed.
 * @param  {Array} leafs - Array of downwards traversal. To access the value: {parent:{ child: 'value'}} -> ['parent','child']
 * @param  {Number} index - Must not be set, since it is implicit.
 * @return {String|Number}       The property, which is to be compared by sort.
 */
function recurseObjProp(root, leafs, index) {
  index ? index : index = 0
  var upper = root
  // walk down one level
  lower = upper[leafs[index]]
  // Check if last leaf has been hit by having gone one step too far.
  // If so, return result from last step.
  if (!lower) {
    return upper
  }
  // Else: recurse!
  index++
  // HINT: Bug was here, for not explicitly returning function
  // https://stackoverflow.com/a/17528613/3580261
  return recurseObjProp(lower, leafs, index)
}

/**
 * Multi-sort your array by a set of properties
 * @param {...Array} Arrays to access values in the form of: {parent:{ child: 'value'}} -> ['parent','child']
 * @return {Number} Number - number for sort algorithm
 */
function dynamicMultiSort() {
  var args = Array.prototype.slice.call(arguments); // slight deviation to base

  return function (a, b) {
    var i = 0, result = 0, numberOfProperties = args.length;
    // REVIEW: slightly verbose; maybe no way around because of `.sort`-'s nature
    // Consider: `.forEach()`
    while(result === 0 && i < numberOfProperties) {
      result = dynamicSort(args[i])(a, b);
      i++;
    }
    return result;
  }
}

Exemple

Exemple de travail sur JSBin

J'ai un tableau d'objets JavaScript:

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

Comment puis-je les trier par la valeur de last_nom en JavaScript?

Je connais les sort(a,b) , mais cela ne semble fonctionner que sur les chaînes et les nombres. Dois-je ajouter une toString() à mes objets?


A partir de 2018, il existe une solution beaucoup plus courte et élégante. Il suffit d'utiliser. developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… .

Exemple:

var items = [
  { name: 'Edward', value: 21 },
  { name: 'Sharpe', value: 37 },
  { name: 'And', value: 45 },
  { name: 'The', value: -12 },
  { name: 'Magnetic', value: 13 },
  { name: 'Zeros', value: 37 }
];

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

C'est assez facile d'écrire votre propre fonction de comparaison:

function compare(a,b) {
  if (a.last_nom < b.last_nom)
    return -1;
  if (a.last_nom > b.last_nom)
    return 1;
  return 0;
}

objs.sort(compare);

Ou en ligne (c / o Marco Demaio):

objs.sort((a,b) => (a.last_nom > b.last_nom) ? 1 : ((b.last_nom > a.last_nom) ? -1 : 0)); 

Dans ES6 / ES2015 ou version ultérieure, vous pouvez procéder comme suit:

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

Exemple d'utilisation:

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

Scénario:

/**
 * @description 
 * Returns a function which will sort an
 * array of objects by the given key.
 * 
 * @param  {String}  key
 * @param  {Boolean} reverse
 * @return {Function}     
 */
function sortBy(key, reverse) {

  // Move smaller items towards the front
  // or back of the array depending on if
  // we want to sort the array in reverse
  // order or not.
  var moveSmaller = reverse ? 1 : -1;

  // Move larger items towards the front
  // or back of the array depending on if
  // we want to sort the array in reverse
  // order or not.
  var moveLarger = reverse ? -1 : 1;

  /**
   * @param  {*} a
   * @param  {*} b
   * @return {Number}
   */
  return function(a, b) {
    if (a[key] < b[key]) {
      return moveSmaller;
    }
    if (a[key] > b[key]) {
      return moveLarger;
    }
    return 0;
  };

}

Il y a beaucoup de bonnes réponses ici, mais je voudrais souligner qu'elles peuvent être étendues très simplement pour réaliser un tri beaucoup plus complexe. La seule chose à faire est d'utiliser l'opérateur OR pour chaîner les fonctions de comparaison comme ceci:

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

fn1 , fn2 , ... sont les fonctions de tri qui retournent [-1,0,1]. Il en résulte "tri par fn1", "tri par fn2" qui est à peu près égal à ORDER BY en SQL.

Cette solution est basée sur le comportement de || opérateur qui évalue la première expression évaluée pouvant être convertie en true .

La forme la plus simple n'a qu'une seule fonction en ligne comme celle-ci:

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

Avoir deux étapes avec last_nom , l' first_nom tri first_nom ressemblerait à ceci:

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

Une fonction de comparaison générique pourrait ressembler à ceci:

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

Cette fonction pourrait être étendue pour prendre en charge les champs numériques, la sensibilité à la casse, les types de données arbitraires, etc.

Vous pouvez les utiliser en les chaînant par ordre de priorité:

// ORDER_BY last_nom, first_nom
objs.sort((a,b)=> cmp(a,b, "last_nom") || cmp(a,b, "first_nom") )
// ORDER_BY last_nom, first_nom DESC
objs.sort((a,b)=> cmp(a,b, "last_nom") || -cmp(a,b, "first_nom") )
// ORDER_BY last_nom DESC, first_nom DESC
objs.sort((a,b)=> -cmp(a,b, "last_nom") || -cmp(a,b, "first_nom") )

Le point ici est que JavaScript pur avec une approche fonctionnelle peut vous prendre beaucoup de temps sans bibliothèques externes ou code complexe. Il est également très efficace, car aucune analyse de chaîne ne doit être faite


Je sais que cette question est trop ancienne, mais je n'ai vu aucune mise en œuvre semblable à la mienne.
Cette version est basée sur l' idiome de transformation de Schwartzian .

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

Ne comprenez pas pourquoi les gens compliquent les choses:

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

Pour les moteurs plus stricts:

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

Échangez l'opérateur pour le faire trier par ordre alphabétique inverse.


Solution simple et rapide à ce problème en utilisant un héritage de prototype:

Array.prototype.sortBy = function(p) {
  return this.slice(0).sort(function(a,b) {
    return (a[p] > b[p]) ? 1 : (a[p] < b[p]) ? -1 : 0;
  });
}

Exemple / Utilisation

objs = [{age:44,name:'vinay'},{age:24,name:'deepak'},{age:74,name:'suresh'}];

objs.sortBy('age');
// Returns
// [{"age":24,"name":"deepak"},{"age":44,"name":"vinay"},{"age":74,"name":"suresh"}]

objs.sortBy('name');
// Returns
// [{"age":24,"name":"deepak"},{"age":74,"name":"suresh"},{"age":44,"name":"vinay"}]

Mise à jour: ne modifie plus le tableau d'origine.


Un moyen simple:

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

Voir que '.toLowerCase()' est nécessaire pour éviter les erreurs lors de la comparaison de chaînes.


Vous pouvez également créer une fonction de tri dynamique qui trie les objets en fonction de la valeur que vous transmettez:

function dynamicSort(property) {
    var sortOrder = 1;
    if(property[0] === "-") {
        sortOrder = -1;
        property = property.substr(1);
    }
    return function (a,b) {
        var result = (a[property] < b[property]) ? -1 : (a[property] > b[property]) ? 1 : 0;
        return result * sortOrder;
    }
}

Donc, vous pouvez avoir un tableau d'objets comme celui-ci:

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

... et ça marchera quand vous ferez:

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

En fait, cela répond déjà à la question. La partie ci-dessous est écrite parce que beaucoup de gens m'ont contacté, se plaignant que cela ne fonctionne pas avec plusieurs paramètres .

Paramètres multiples

Vous pouvez utiliser la fonction ci-dessous pour générer des fonctions de tri avec plusieurs paramètres de tri.

function dynamicSortMultiple() {
    /*
     * save the arguments object as it will be overwritten
     * note that arguments object is an array-like object
     * consisting of the names of the properties to sort by
     */
    var props = arguments;
    return function (obj1, obj2) {
        var i = 0, result = 0, numberOfProperties = props.length;
        /* try getting a different result from 0 (equal)
         * as long as we have extra properties to compare
         */
        while(result === 0 && i < numberOfProperties) {
            result = dynamicSort(props[i])(obj1, obj2);
            i++;
        }
        return result;
    }
}

Ce qui vous permettrait de faire quelque chose comme ça:

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

L'ajouter au prototype

(L'implémentation qui est juste en dessous est inspirée de la answer de Mike R )

Je ne recommanderais pas de modifier un prototype d'objet natif, mais juste pour donner un exemple afin que vous puissiez l'implémenter sur vos propres objets (pour les environnements qui le supportent, vous pouvez également utiliser Object.defineProperty comme indiqué dans la section suivante, qui au moins n'a pas l'effet secondaire d'être énumérable, comme décrit dans la dernière partie)

L’implémentation du prototype ressemblerait à ce qui suit ( voici un exemple de travail ):

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

La manière "correcte" de l'ajouter au prototype

Si vous ciblez IE v9.0 et les versions ultérieures, utilisez Object.defineProperty comme ceci ( exemple de travail ):

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

Cela peut constituer un compromis acceptable jusqu'à l'arrivée de l' opérateur de liaison .

Tous ces prototypes amusants permettent ceci:

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

Vous devriez lire ceci

Si vous utilisez la méthode d'accès directe au prototype (Object.defineProperty va bien) et si un autre code ne vérifie pas hasOwnProperty , les chatons meurent! Ok, pour être honnête, aucun chaton ne souffre vraiment, mais il est probable que tout se brise et que tous les autres développeurs de votre équipe vous haïssent:

Voir ce dernier "SortBy"? Ouais. Pas cool. Utilisez Object.defineProperty dans la mesure du possible et laissez le paramètre Array.prototype tel quel.


paramètres supplémentaires pour le code Ege Özcan

function dynamicSort(property, desc) {
    if (desc) {
        return function (a, b) {
            return (a[property] > b[property]) ? -1 : (a[property] < b[property]) ? 1 : 0;
        }   
    }
    return function (a, b) {
        return (a[property] < b[property]) ? -1 : (a[property] > b[property]) ? 1 : 0;
    }
}

underscore.js

utilisez le trait de soulignement, son petit et génial ...

sortBy_.sortBy (list, iterator, [context]) Retourne une copie triée de la liste, classée par ordre croissant en fonction des résultats de l'exécution de chaque valeur via l'itérateur. Itérateur peut également être le nom de chaîne de la propriété à trier (par exemple, longueur).

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

var sortedObjs = _.sortBy( objs, 'first_nom' );

Lodash.js (sur-ensemble de Underscore.js )

Il est bon de ne pas ajouter de cadre à chaque simple élément de logique, mais s’appuyer sur des cadres d’utilité bien testés, accélérer le développement et réduire le nombre de bogues écrits n’est pas une honte.

Lodash produit un code très propre et promeut un style de programmation plus fonctionnel , ce qui entraîne moins de bugs. En un coup d'œil, l'intention du code devient claire.

Le problème d'OP peut être simplement résolu comme suit:

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

Plus d'informations? Par exemple, nous avons un objet imbriqué suivant:

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

Nous pouvons maintenant utiliser le raccourci user.age pour spécifier le chemin d'accès à la propriété à rechercher. Nous allons trier les objets utilisateur en fonction de la propriété d'âge imbriquée. Oui, cela permet la correspondance des propriétés imbriquées!

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

Voulez-vous l'inverser? Aucun problème. Utilisez _.reverse .

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

Vous voulez combiner les deux en utilisant le Chaining ?

const sortedObjs = _.chain(users).sortBy('user.age').reverse().value();

Une fonction simple qui trie un tableau d'objets par une propriété

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

Usage:

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

sortArray(objs, "last_nom"); // Asc
sortArray(objs, "last_nom", -1); // Desc

Vous devrez peut-être les convertir en minuscules pour éviter toute confusion.

objs.sort(function (a,b) {

var nameA=a.last_nom.toLowerCase(), nameB=b.last_nom.toLowerCase()

if (nameA < nameB)
  return -1;
if (nameA > nameB)
  return 1;
return 0;  //no sorting

})

C'est un problème simple, je ne sais pas pourquoi les gens ont une solution aussi complexe.
Une fonction de tri simple (basée sur un algorithme de tri rapide):

function sortObjectsArray(objectsArray, sortKey)
        {
            // Quick Sort:
            var retVal;

            if (1 < objectsArray.length)
            {
                var pivotIndex = Math.floor((objectsArray.length - 1) / 2);  // middle index
                var pivotItem = objectsArray[pivotIndex];                    // value in the middle index
                var less = [], more = [];

                objectsArray.splice(pivotIndex, 1);                          // remove the item in the pivot position
                objectsArray.forEach(function(value, index, array)
                {
                    value[sortKey] <= pivotItem[sortKey] ?                   // compare the 'sortKey' proiperty
                        less.push(value) :
                        more.push(value) ;
                });

                retVal = sortObjectsArray(less, sortKey).concat([pivotItem], sortObjectsArray(more, sortKey));
            }
            else
            {
                retVal = objectsArray;
            }

            return retVal;
        }

Exemple d'utilisation:

var myArr = 
        [
            { val: 'x', idx: 3 },
            { val: 'y', idx: 2 },
            { val: 'z', idx: 5 },
        ];
myArr = sortObjectsArray(myArr, 'idx');

Je ne l' ai pas vu cette approche particulière a suggéré, voici donc une méthode de comparaison laconique Je préfère utiliser qui fonctionne à la fois stringet number:

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

const sortBy = fn => (a, b) => -(fn(a) < fn(b)) || +(fn(a) > fn(b))
const getLastName = o => o.last_nom
const sortByLastName = sortBy(getLastName)

objs.sort(sortByLastName)
console.log(objs.map(getLastName))

Voici une explication de sortBy():

sortBy()accepte a fnqui sélectionne la valeur d'un objet à utiliser à titre de comparaison et renvoie une fonction pouvant être directement transmise Array.prototype.sort(). Dans cet exemple, nous utilisons o.last_nomcomme valeur de comparaison. Ainsi, chaque fois que nous recevons deux objets, Array.prototype.sort()tels que

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

et

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

nous utilisons

(a, b) => -(fn(a) < fn(b)) || +(fn(a) > fn(b))

pour les comparer.

En fn = o => o.last_nomnous souvenant de cela , nous pouvons étendre la fonction de comparaison à l’équivalent

(a, b) => -(a.last_nom < b.last_nom) || +(a.last_nom > b.last_nom)

L' ||opérateur OU logique dispose d'une fonctionnalité de court-circuit très utile ici. En raison de la façon dont cela fonctionne, le corps de la fonction ci-dessus signifie

if (a.last_nom < b.last_nom) return -1
return +(a.last_nom > b.last_nom)

Donc si a < bnous revenons -1, sinon si a > bnous revenons +1, mais si a == b, alors a < bet a > bsont faux, alors il revient +0.

En prime, voici l'équivalent dans ECMAScript 5.1 sans les fonctions de flèche, ce qui, malheureusement, n'est pas aussi concis:

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

var sortBy = function (fn) {
  return function (a, b) {
    return -(fn(a) < fn(b)) || +(fn(a) > fn(b))
  }
}

var getLastName = function (o) { return o.last_nom }
var sortByLastName = sortBy(getLastName)

objs.sort(sortByLastName)
console.log(objs.map(getLastName))


Selon votre exemple, vous devez trier selon deux champs (nom de famille, prénom), plutôt qu'un. Vous pouvez utiliser la bibliothèque Alasql pour faire ce tri sur une seule ligne:

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

Essayez cet exemple sur jsFiddle .


function compare(propName) {
    return function(a,b) {
        if (a[propName] < b[propName])
            return -1;
        if (a[propName] > b[propName])
            return 1;
        return 0;
    };
}

objs.sort(compare("last_nom"));




sorting