Comment obtenir des valeurs distinctes d'un ensemble d'objets en JavaScript?

En supposant que j'ai ce qui suit:

var array = [ {"name":"Joe", "age":17}, {"name":"Bob", "age":17}, {"name":"Carl", "age": 35} ] 

Quelle est la meilleure façon d'obtenir un ensemble de tous les âges distincts, de sorte que j'obtiens une gamme de résultats:

 [17, 35] 

Existe-t-il un moyen de structurer les données ou une meilleure méthode de telle sorte que je n'aurais pas à itérer à travers chaque tableau en vérifiant la valeur de "age" et à vérifier contre un autre tableau pour son existence et l'ajouter si non?

S'il y avait quelque chose, je pourrais simplement sortir les différents âges sans itérer …

Je ne veux pas améliorer … Si cela signifie que, au lieu de "tableau", il s'agit d'un ensemble d'objets, mais d'une "carte" d'objets avec une clé unique (c'est-à-dire "1,2,3") qui serait D'accord aussi. Je cherche juste le moyen le plus performant.

Voici comment je le fais actuellement, mais pour moi, l'itération semble être tout à fait délicate même si ça fonctionne …

 var distinct = [] for (var i = 0; i < array.length; i++) if (array[i].age not in distinct) distinct.push(array[i].age) 

S'il s'agissait de PHP, je construisais un tableau avec les clés et array_keys à la fin, mais JS n'a pas de luxe tel. Au lieu de cela, essayez ceci:

 var flags = [], output = [], l = array.length, i; for( i=0; i<l; i++) { if( flags[array[i].age]) continue; flags[array[i].age] = true; output.push(array[i].age); } 

Si vous utilisez ES6 / ES2015 ou plus tard, vous pouvez le faire de cette façon:

 var unique = [...new Set(array.map(item => item.age))]; 

Voici un exemple sur la façon de le faire.

ÉDITION basée sur la suggestion @Jonas Kello:

 const unique = [...new Set(array.map(item => item.age))]; 

Vous pouvez utiliser une approche de dictionnaire comme celle-ci. Fondamentalement, vous attribuez la valeur que vous voulez être distincte en tant que clé dans un dictionnaire. Si la clé n'existait pas, vous ajoutez cette valeur distincte.

 var unique = {}; var distinct = []; for( var i in array ){ if( typeof(unique[array[i].age]) == "undefined"){ distinct.push(array[i].age); } unique[array[i].age] = 0; } 

Voici une démonstration de travail: http://jsfiddle.net/jbUKP/1

Ce sera O (n) où n est le nombre d'objets dans le tableau et m est le nombre de valeurs uniques. Il n'y a pas de moyen plus rapide que O (n) car il faut inspecter chaque valeur au moins une fois.

Performance

http://jsperf.com/filter-versus-dictionary Quand j'ai couru ce dictionnaire était 30% plus rapide.

Je viens de mapper et de supprimer dups:

 var ages = array.map(function(obj) { return obj.age; }); ages = ages.filter(function(v,i) { return ages.indexOf(v) == i; }); console.log(ages); //=> [17, 35] 

Edit: Aight! Pas le moyen le plus efficace en termes de performances, mais l'OMI la plus simple et la plus lisible. Si vous vous souciez vraiment de la micro-optimisation ou que vous disposez d'énormes quantités de données, une boucle régulière sera plus «efficace».

En utilisant ES6

 let array = [ { "name": "Joe", "age": 17 }, { "name": "Bob", "age": 17 }, { "name": "Carl", "age": 35 } ]; array.map(item => item.age) .filter((value, index, self) => self.indexOf(value) === index) > [17, 35] 

La version pour forEach version de @ travis-j (utile sur les navigateurs modernes et Node JS world):

 var unique = {}; var distinct = []; array.forEach(function (x) { if (!unique[x.age]) { distinct.push(x.age); unique[x.age] = true; } }); 

34% plus rapide sur Chrome v29.0.1547: http://jsperf.com/filter-versus-dictionary/3

Et une solution générique qui prend une fonction de mappeur (plus lente que la carte directe, mais c'est attendu):

 function uniqueBy(arr, fn) { var unique = {}; var distinct = []; arr.forEach(function (x) { var key = fn(x); if (!unique[key]) { distinct.push(key); unique[key] = true; } }); return distinct; } // usage uniqueBy(array, function(x){return x.age;}); // outputs [17, 35] 

J'ai commencé à souligner le soulignement dans tous les nouveaux projets par défaut, alors je n'ai jamais à penser à ces petits problèmes de partage de données.

 var array = [{"name":"Joe", "age":17}, {"name":"Bob", "age":17}, {"name":"Carl", "age": 35}]; console.log(_.chain(array).map(function(item) { return item.age }).uniq().value()); 

Produit [17, 35] .

En utilisant lodash

 var array = [ { "name": "Joe", "age": 17 }, { "name": "Bob", "age": 17 }, { "name": "Carl", "age": 35 } ]; _.chain(array).pluck('age').unique().value(); > [17, 35] 

Voici une autre façon de résoudre ceci:

 var result = {}; for(var i in array) { result[array[i].age] = null; } result = Object.keys(result); 

Je n'ai aucune idée de la rapidité avec laquelle cette solution est comparée aux autres, mais j'aime le look plus propre. 😉


EDIT: D'accord, ce qui précède semble être la solution la plus lente de tous ici.

J'ai créé un cas de test de performance ici: http://jsperf.com/distinct-values-from-array

Au lieu de tester les âges (Integers), j'ai choisi de comparer les noms (Strings).

La méthode 1 (solution TS) est très rapide. Il est intéressant de noter que la Méthode 7 est supérieure à toutes les autres solutions. Je viens de me débarrasser de .indexOf () et j'ai utilisé une mise en œuvre "manuelle" pour éviter les appels en fonction en boucle:

 var result = []; loop1: for (var i = 0; i < array.length; i++) { var name = array[i].name; for (var i2 = 0; i2 < result.length; i2++) { if (result[i2] == name) { continue loop1; } } result.push(name); } 

La différence de performance à l'aide de Safari & Firefox est incroyable, et il semble que Chrome soit le meilleur travail d'optimisation.

Je ne suis pas vraiment sûr de savoir pourquoi les extraits ci-dessus sont si rapides par rapport aux autres, peut-être quelqu'un plus sage que moi a une réponse. 😉

_.uniq(_.pluck(array,"age"))

Utilisation de Lodash

 var array = [ { "name": "Joe", "age": 17 }, { "name": "Bob", "age": 17 }, { "name": "Carl", "age": 35 } ]; _.chain(array).map('age').unique().value(); 

Retour [17,35]

 function get_unique_values_from_array_object(array,property){ var unique = {}; var distinct = []; for( var i in array ){ if( typeof(unique[array[i][property]]) == "undefined"){ distinct.push(array[i]); } unique[array[i][property]] = 0; } return distinct; } 

Je l'ai trouvé juste et j'ai pensé que c'était utile

 _.map(_.indexBy(records, '_id'), function(obj){return obj}) 

Encore une fois en utilisant le trait de soulignement , donc si vous avez un objet comme celui-ci

 var records = [{_id:1,name:'one', _id:2,name:'two', _id:1,name:'one'}] 

Il vous donnera les objets uniques seulement.

Ce qui se passe ici, c'est que indexBy renvoie une carte comme celle-ci

 { 1:{_id:1,name:'one'}, 2:{_id:2,name:'two'} } 

Et juste parce que c'est une carte, toutes les clés sont uniques.

Ensuite, je suis en train de mapper cette liste au tableau.

Dans le cas où vous n'avez besoin que des valeurs distinctes

 _.map(_.indexBy(records, '_id'), function(obj,key){return key}) 

Gardez à l'esprit que la key est renvoyée en tant que chaîne, de sorte que, si vous avez besoin d'entiers à la place, vous devriez faire

 _.map(_.indexBy(records, '_id'), function(obj,key){return parseInt(key)}) 

En utilisant les fonctions ES6, vous pouvez faire quelque chose comme:

 const filteredValues = [...new Set( array.map((obj) => obj.name)) ]; 

Si vous avez Array.prototype.inclique ou est prêt à le polyfill , cela fonctionne:

 var ages = []; array.forEach(function(x) { if (!ages.includes(x.age)) ages.push(x.age); }); 

Si comme moi, vous préférez un plus «fonctionnel» sans compromettre la vitesse, cet exemple utilise une recherche rapide de dictionnaire enveloppé à l'intérieur, réduisez la fermeture.

 var array = [ {"name":"Joe", "age":17}, {"name":"Bob", "age":17}, {"name":"Carl", "age": 35} ] var uniqueAges = array.reduce((p,c,i,a) => { if(!p[0][c.age]) { p[1].push(p[0][c.age] = c.age); } if(i<a.length-1) { return p } else { return p[1] } }, [{},[]]) 

Selon ce test, ma solution est deux fois plus rapide que la réponse proposée

Voici une solution polyvalente qui utilise la réduction, permet le mappage et maintient l'ordre d'insertion.

Éléments : un tableau

Mapper : une fonction unary qui mappe l'élément aux critères, ou vide pour cartographier l'objet lui-même.

 function distinct(items, mapper) { if (!mapper) mapper = (item)=>item; return items.map(mapper).reduce((acc, item) => { if (acc.indexOf(item) === -1) acc.push(item); return acc; }, []); } 

Usage

 const distinctLastNames = distinct(items, (item)=>item.lastName); const distinctItems = distinct(items); 

Vous pouvez l'ajouter à votre prototype Array et laisser le paramètre des éléments si c'est votre style …

 const distinctLastNames = items.distinct( (item)=>item.lastName) ) ; const distinctItems = items.distinct() ; 

Vous pouvez également utiliser un Ensemble au lieu d'un Array pour accélérer la correspondance.

 function distinct(items, mapper) { if (!mapper) mapper = (item)=>item; return items.map(mapper).reduce((acc, item) => { acc.add(item); return acc; }, new Set()); } 

Je pense que vous recherchez la fonction groupBy (en utilisant Lodash)

 _personsList = [{"name":"Joe", "age":17}, {"name":"Bob", "age":17}, {"name":"Carl", "age": 35}]; _uniqAgeList = _.groupBy(_personsList,"age"); _uniqAges = Object.keys(_uniqAgeList); 

Produit le résultat:

 17,35 

Démo jsFiddle: http://jsfiddle.net/4J2SX/201/

Mes deux cents sur cette fonction:

 var result = []; for (var len = array.length, i = 0; i < len; ++i) { var age = array[i].age; if (result.indexOf(age) > -1) continue; result.push(age); } 

Vous pouvez voir le résultat ici (Méthode 8) http://jsperf.com/distinct-values-from-array/3

L'utilisation de nouvelles fonctionnalités Ecma est géniale, mais tous les utilisateurs ne sont pas encore disponibles.

Le code suivant associe une nouvelle fonction nommée distincte à l'objet Global Array. Si vous essayez d'obtenir des valeurs distinctes d'un ensemble d'objets, vous pouvez passer le nom de la valeur pour obtenir les valeurs distinctes de ce type.

 Array.prototype.distinct = function(item){ var results = []; for (var i = 0, l = this.length; i < l; i++) if (!item){ if (results.indexOf(this[i]) === -1) results.push(this[i]); } else { if (results.indexOf(this[i][item]) === -1) results.push(this[i][item]); } return results;}; 

Consultez ma publication dans CodePen pour une démo.

Essaye juste

 var x = [] ; for (var i = 0 ; i < array.length ; i++) { if(x.indexOf(array[i]['age']) == -1) { x.push(array[i]['age']); } } console.log(x); 

En utilisant d3.js v3 :

  ages = d3.set( array.map(function (d) { return d.age; }) ).values();