Comment obtenir des valeurs distinctes à partir d'un tableau 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 tableau de tous les âges distincts de sorte que j'obtienne un tableau de résultats de:
[17, 35]
Y a-t-il un moyen de structurer alternativement les données ou une meilleure méthode de sorte que je n'aurais pas à parcourir chaque tableau en vérifiant la valeur de "age" et en vérifiant par rapport à un autre tableau son existence, et l'ajouter sinon?
S'il y avait un moyen de sortir les âges distincts sans itération...
Manière inefficace actuelle que je voudrais améliorer... Si cela signifie qu'au lieu de "tableau" étant un tableau d'objets, mais une " carte "d'objets avec une clé unique (c'est-à-dire" 1,2,3"), ce serait bien aussi. Je cherche juste le moyen le plus efficace.
Ce qui suit est la façon dont je le fais actuellement, mais pour moi, l'itération semble juste être minable pour l'efficacité même si cela fonctionne...
var distinct = []
for (var i = 0; i < array.length; i++)
if (array[i].age not in distinct)
distinct.push(array[i].age)
26 réponses
Si C'était PHP, je construirais un tableau avec les clés et prendrais array_keys
à la fin, mais JS n'a pas un tel luxe. 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 une version ultérieure, vous pouvez le faire de cette façon:
const unique = [...new Set(array.map(item => item.age))];
Ici est un exemple sur la façon de le faire.
Vous pouvez utiliser une approche de dictionnaire comme celle-ci. Fondamentalement, vous attribuez la valeur que vous voulez être distincte comme clé dans un dictionnaire. Si la clé n'existait pas, vous ajoutez cette valeur comme 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émo 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 vous devez inspecter chaque valeur au moins lorsque.
Les Performances
Http://jsperf.com/filter-versus-dictionary Quand j'ai couru ce dictionnaire était 30% plus rapide.
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]
Je voudrais juste mapper et 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 performance, mais L'OMI la plus simple et la plus lisible. Si vous vous souciez vraiment de la micro-optimisation ou vous avez d'énormes quantités de données ordinaire for
boucle va être plus "efficace".
En utilisant les fonctionnalités ES6, vous pouvez faire quelque chose comme:
const uniqueAges = [...new Set( array.map(obj => obj.age)) ];
Voici comment vous résoudriez cela en utilisant un nouvel ensemble via ES6 pour Typescript à partir du 25 août 2017
Array.from(new Set(yourArray.map((item: any) => item.id)))
La version forEach
de la réponse de @ travis-j (utile sur les navigateurs modernes et le monde Node JS):
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 (tad plus lent que direct map, 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é à coller Underscore dans tous les nouveaux projets par défaut juste pour ne jamais avoir à penser à ces petits problèmes de gestion 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 ce problème:
var result = {};
for(var i in array) {
result[array[i].age] = null;
}
result = Object.keys(result);
Je n'ai aucune idée de la vitesse à 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 (entiers), j'ai choisi de comparer les noms (chaînes).
La Méthode 1 (solution de TS) est très rapide. Fait intéressant, la méthode 7 surpasse toutes les autres solutions, ici je viens de me débarrasser .indexOf () et utilisé une implémentation "manuelle" de celui-ci, en évitant l'appel de 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 en utilisant Safari et Firefox est incroyable, et il semble que Chrome fait le meilleur travail sur l'optimisation.
Je ne sais pas exactement pourquoi les extraits ci-dessus sont si rapides par rapport aux autres, peut-être que quelqu'un de plus sage que moi a une réponse. ;-)
En Utilisant Lodash
var array = [
{ "name": "Joe", "age": 17 },
{ "name": "Bob", "age": 17 },
{ "name": "Carl", "age": 35 }
];
_.chain(array).map('age').unique().value();
Renvoie [17,35]
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/
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 viens de trouver ceci et je pensais que c'était utile
_.map(_.indexBy(records, '_id'), function(obj){return obj})
Encore une fois en utilisant underscore , 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 est que indexBy
retourne une carte
{ 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 juste en train de mapper cette liste au tableau.
Au cas où vous n'auriez besoin que des valeurs distinctes
_.map(_.indexBy(records, '_id'), function(obj,key){return key})
Gardez à l'esprit que la key
est retourné comme une chaîne donc, si vous avez besoin d'entiers à la place, vous devriez faire
_.map(_.indexBy(records, '_id'), function(obj,key){return parseInt(key)})
Si vous avez un tableau.prototype.comprend ou sont prêts à 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 la recherche de dictionnaire rapide enveloppé dans réduire 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
Il y a déjà beaucoup de réponses valides, mais je voulais en ajouter une qui utilise uniquement la méthode reduce()
car elle est propre et simple.
function uniqueBy(arr, prop){
return arr.reduce((a, d) => {
if (!a.includes(d[prop])) a.push(d[prop]);
return a;
}, []);
}
Utilisez-le comme ceci:
var array = [
{"name": "Joe", "age": 17},
{"name": "Bob", "age": 17},
{"name": "Carl", "age": 35}
];
var ages = uniqueBy(array, "age");
console.log(ages); // [17, 35]
Voici une solution polyvalente qui utilise reduce, permet le mappage et maintient l'ordre d'insertion.
les éléments de: Un tableau
mapper : une fonction unaire qui mappe l'élément aux critères, ou vide pour mapper l'élément 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;
}, []);
}
Utilisation
const distinctLastNames = distinct(items, (item)=>item.lastName);
const distinctItems = distinct(items);
Vous pouvez ajouter ceci à votre prototype de tableau et laisser de côté le paramètre items 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 tableau pour correspondre.
function distinct(items, mapper) {
if (!mapper) mapper = (item)=>item;
return items.map(mapper).reduce((acc, item) => {
acc.add(item);
return acc;
}, new Set());
}
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 n'en ont pas encore disponibles.
Le code suivant attachera une nouvelle fonction nommée distinct à l'objet tableau Global. Si vous essayez d'obtenir les valeurs distinctes d'un tableau 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 mon message dans CodePen pour une démo.
Essayez simplement
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);
unique(obj, prop) {
let result = [];
let seen = new Set();
Object.keys(obj)
.forEach((key) => {
let value = obj[key];
let test = !prop
? value
: value[prop];
!seen.has(test)
&& seen.add(test)
&& result.push(value);
});
return result;
}
Cette fonction peut unique tableau et objet
function oaunic(x,n=0){
if(n==0) n = "elem";
else n = "elem."+n;
var uval = [];
var unic = x.filter(function(elem, index, self){
if(uval.indexOf(eval(n)) < 0){
uval.push(eval(n));
return index == self.indexOf(elem);
}
})
return unic;
}
Utilisez-le comme ceci
tags_obj = [{name:"milad"},{name:"maziar"},{name:"maziar"}]
tags_arr = ["milad","maziar","maziar"]
console.log(oaunic(tags_obj,"name")) //for object
console.log(oaunic(tags_arr)) //for array