web-dev-qa-db-fra.com

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

En supposant que j'ai les éléments suivants:

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

Quel est le meilleur moyen de pouvoir obtenir un tableau de tous les âges, de manière à obtenir un tableau de résultats de:

[17, 35]

Existe-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 son existence par rapport à un autre, et à l'ajouter sinon?

S'il y avait un moyen de sortir des âges différents sans itérer ...

Je voudrais améliorer la manière actuellement inefficace ... Si cela signifie qu'au lieu de "tableau", il s'agisse d'un tableau d'objets, mais d'une "carte" d'objets avec une clé unique (c'est-à-dire "1,2,3") qui serait ok aussi Je cherche simplement le moyen le plus efficace et performant.

Voici comment je le fais actuellement, mais pour moi, l'itération semble ê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)
229
Rolando

Si c'était PHP, je construirais un tableau avec les clés et prendrais array_keys à la fin, mais JS n'a pas ce 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);
}
99
Niet the Dark Absol

Si vous utilisez ES6/ES2015 ou une version ultérieure, procédez comme suit:

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

Ici est un exemple sur la façon de le faire.

357
Vlad Bezden

Vous pouvez utiliser une approche de dictionnaire comme celle-ci. Fondamentalement, vous affectez la valeur que vous voulez distinguer comme 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émo de travail: http://jsfiddle.net/jbUKP/1

Ce sera O(n) où n est le nombre d'objets dans le tableau et m 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 une fois.

Performance

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

102
Travis J

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]
89
Ivan Nosov

Je voudrais juste cartographier et supprimer les doublons:

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]

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

51
elclanrs

voici comment résoudre ce problème à l'aide du nouveau Set via ES6 pour TypeScript à compter du 25 août 2017.

Array.from(new Set(yourArray.map((item: any) => item.id)))
47
Christian Matthew

En utilisant les fonctionnalités de ES6, vous pouvez faire quelque chose comme:

const uniqueAges = [...new Set( array.map(obj => obj.age)) ];
45
Russell Vea

La version forEach de la réponse de @ travis-j (utile pour 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 (un peu plus lente que la carte directe, mais c'est prévu):

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]
17
Mrchief

J'ai commencé à coller Underscore dans tous les nouveaux projets par défaut, afin de ne jamais avoir à penser à ces petits problèmes d'élimination 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].

13
Eevee

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]
9
Ivan Nosov

Voici un autre moyen de résoudre ceci:

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

Je ne sais pas à quelle vitesse cette solution est comparée aux autres, mais j'aime le look plus propre. ;-) 


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

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

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

La méthode 1 (la solution de TS) est très rapide. Il est intéressant de noter que la méthode 7 surpasse toutes les autres solutions. Ici, je viens de me débarrasser de .indexOf () et d’utiliser une implémentation "manuelle" de celle-ci, en évitant les appels de fonctions 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 performances entre Safari et Firefox est incroyable, et il semble que Chrome fasse le meilleur travail possible en matière d'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. ;-)

7
BaM

Il existe déjà de nombreuses réponses valables, 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]
6
Harry Stevens
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;
}
5
Dmitriy Kupriyanov

Utiliser Lodash

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

_.chain(array).map('age').unique().value();

Retours [17,35]

5
Yash Vekaria

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

je pense que vous recherchez une 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/

4
Amil Sajeev
var unique = array
    .map(p => p.age)
    .filter((age, index, arr) => arr.indexOf(age) == index)
    .sort(); // sorting is optional

// or in ES6

var unique = [...new Set(array.map(p => p.age))];
4
oleg gabureac

Si vous avez Array.prototype.includes ou êtes prêt à polyfill it, cela fonctionne:

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

Si, comme moi, vous préférez une fonction plus "fonctionnelle" sans compromettre la vitesse, cet exemple utilise la recherche rapide par dictionnaire intégrée à la fermeture réduite.

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

2
bmaggi

Je viens de trouver cela et j'ai pensé 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'}]

cela vous donnera uniquement les objets uniques.

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 mappe simplement cette liste vers le 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 variable key est renvoyée sous forme de chaîne. Par conséquent, si vous avez besoin d’entiers, vous devriez le faire.

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

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

items : 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;
    }, []);
}

Usage

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

Vous pouvez ajouter ceci à votre prototype Array et omettre 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 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());
}
1
Steven Spungin
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;
}
1
Devonte
const x = [
  {"id":"93","name":"CVAM_NGP_KW"},
  {"id":"94","name":"CVAM_NGP_PB"},
  {"id":"93","name":"CVAM_NGP_KW"},
  {"id":"94","name":"CVAM_NGP_PB"}
].reduce(
  (accumulator, current) => accumulator.some(x => x.id === current.id)? accumulator: [...accumulator, current ], []
)

console.log(x)

/* output 
[ 
  { id: '93', name: 'CVAM_NGP_KW' },
  { id: '94', name: 'CVAM_NGP_PB' } 
]
*/
1
Ruben Morales Felix

Mon code ci-dessous montrera le tableau unique des âges ainsi que le nouveau tableau n'ayant pas d'âge en double

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

var unique = [];
var tempArr = [];
data.forEach((value, index) => {
    if (unique.indexOf(value.age) === -1) {
        unique.Push(value.age);
    } else {
        tempArr.Push(index);    
    }
});
tempArr.reverse();
tempArr.forEach(ele => {
    data.splice(ele, 1);
});
console.log('Unique Ages', unique);
console.log('Unique Array', data);```
1
Shridhar Sagari

Simple one-liner avec d'excellentes performances. 6% plus rapide que les solutions ES6 dans mes tests .

var ages = array.map (function (o) {return o.age}). filter (fonction (v, i, a) {return a.indexOf (v) === i});

0
Craig

Juste essayer

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);
0
Pravin Pareek

Je sais que c'est une question ancienne et relativement bien répondue et la réponse que je donne récupèrera l'objet complet (ce que je vois suggéré dans de nombreux commentaires sur ce post). C'est peut-être "collant", mais en termes de lisibilité, cela semble beaucoup plus propre (bien que moins efficace) que beaucoup d'autres solutions.

Cela retournera un tableau unique des objets complets à l'intérieur du tableau.

let productIds = data.map(d => { 
   return JSON.stringify({ 
      id    : d.sku.product.productId,
      name  : d.sku.product.name,
      price : `${d.sku.product.price.currency} ${(d.sku.product.price.gross / d.sku.product.price.divisor).toFixed(2)}`
   })
})
productIds = [ ...new Set(productIds)].map(d => JSON.parse(d))```
0
Conor Reid

Il existe une bibliothèque qui fournit des collections interrogeables fortement typées dans TypeScript.

Les collections sont:

  • Liste
  • Dictionnaire

La bibliothèque s'appelle ts-generic-collections .

Code source sur GitHub:

https://github.com/VeritasSoftware/ts-generic-collections

Vous pouvez obtenir des valeurs distinctes comme ci-dessous

  it('distinct', () => {
    let numbers: number[] = [1, 2, 3, 1, 3];
    let list = new List(numbers);

    let distinct = list.distinct(new EqualityComparer());

    expect(distinct.length == 3);
    expect(distinct.elementAt(0) == 1);
    expect(distinct.elementAt(1) == 2);
    expect(distinct.elementAt(2) == 3);
  });

  class EqualityComparer implements IEqualityComparer<number> {
    equals(x: number, y: number) : boolean {
      return x == y;
    }
  }
0
John

L'utilisation des nouvelles fonctionnalités d'Ecma est excellente, mais tous les utilisateurs ne disposent pas encore de celles disponibles. 

Le code suivant va associer une nouvelle fonction nommée distinct à l'objet Tableau global . Si vous essayez d'obtenir des valeurs distinctes d'un tableau d'objets, vous pouvez passer le nom de la valeur pour obtenir les valeurs distinctes de cet 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;};

Découvrez mon post dans CodePen pour une démonstration.

0
Hasan Savran

J'ai écrit le mien dans TypeScript, pour un cas générique, comme celui de Kotlin Array.distinctBy {}...

function distinctBy<T, U extends string | number>(array: T[], mapFn: (el: T) => U) {
  const uniqueKeys = new Set(array.map(mapFn));
  return array.filter((el) => uniqueKeys.has(mapFn(el)));
}

U est bien sûr lavable. Pour les objets, vous aurez peut-être besoin de https://www.npmjs.com/package/es6-json-stable-stringify

0
Polv