Rimuovi i duplicati da un array


100

Ho una serie di oggetti che assomigliano a questo:

var array = [
    {id:123, value:"value1", name:"Name1"},
    {id:124, value:"value2", name:"Name1"},
    {id:125, value:"value3", name:"Name2"},
    {id:126, value:"value4", name:"Name2"}
    ...
];

Come puoi vedere, alcuni nomi vengono ripetuti. Voglio ottenere un nuovo array con solo nomi, ma se un nome si ripete non voglio aggiungerlo di nuovo. Voglio questo array:

var newArray = ["Name1", "Name2"];

Sto provando a farlo con map:

var newArray = array.map((a) => {
    return a.name;
});

Ma il problema è che questo ritorna:

newArray = ["Name1", "Name1", "Name2", "Name2"];

Come posso impostare una condizione all'interno map, in modo che non restituisca un elemento già esistente? Voglio farlo con mapo qualche altra funzione ECMAScript 5 o ECMAScript 6.


2
Quindi rimuovere i duplicati dall'array.
Tushar



Perché la riga contenente id: 125 non termina con una virgola?
Peter Mortensen

Si noti che tutte le risposte che utilizzano .indexOf () avranno prestazioni scadenti se l'array è grande, a causa della loro complessità temporale quadratica . Suggerirei di utilizzare ES6 Set o di utilizzare un oggetto ES5 come dizionario.
joeytwiddle

Risposte:


210

Con ES6, è possibile utilizzare Setper valori univoci, dopo aver mappato solo i nomi degli oggetti.

Questa proposta utilizza una sintassi diffusa... per raccogliere gli elementi in un nuovo array.

const array = [{ id: 123, value: "value1", name:"Name1" }, { id: 124, value: "value2", name: "Name1" }, { id: 125, value: "value3", name: "Name2" }, { id: 126, value: "value4", name: "Name2" }],
      names = [...new Set(array.map(a => a.name))];

console.log(names);


9
Per le mie info .. cosa fa ...?
Rajshekar Reddy

9
@RajshekarReddy, è una sintassi diffusa... per la creazione di un array con un oggetto iterabile.
Nina Scholz

5
Non sto votando per il "Set" stesso, ma piuttosto per l'uso intelligente dell'operatore di diffusione. È sempre bello imparare qualcosa di nuovo o vedere qualcosa di nuovo applicato a un esempio pratico, quindi questo post merita un voto positivo.
briosheje

33
È solo la mia opinione personale, ma piuttosto comune . L'uso Array.fromtrasmette l'intento di conversione molto meglio (ed è anche più facile da capire / cercare i neofiti), la sintassi diffusa dovrebbe essere usata solo dove l'elemento diffuso è uno dei tanti. Forse definirla una "cattiva pratica" è un po 'troppo dura, mi dispiace, spero solo di evitare che diventi un linguaggio comune.
Bergi

3
@KRyan è ES6 così nuovo? È stato finalizzato nel giugno 2015. È ora di iniziare a comprendere e utilizzare le nuove funzionalità
edc65

64

Se stai cercando una soluzione JavaScript che non sia ES 6 (no Set) puoi utilizzare il metodo Arrayreduce :

var array=[
  {id:123, value:"value1", name:"Name1"},
  {id:124, value:"value2", name:"Name1"},
  {id:125, value:"value3", name:"Name2"},
  {id:126, value:"value4", name:"Name2"}
];
var names = array.reduce(function (a, b) {
  if (a.indexOf(b.name) == -1) {
    a.push(b.name)
  }
  return a;
}, []);

console.log(names);


2
Questa risposta ha anche il vantaggio di non richiedere array o set intermedi, specialmente quelli contenenti solo i nomi stessi. (Va direttamente dall'array di oggetti all'array di oggetti.) +1
jpmc26

@ Iwrestledabearonce, questo secondo parametro non è lo stesso oggetto di quello restituito?
Kaiido

Sì, ma non passa direttamente dall'uno all'altro come l'ordinamento o il filtro, è ricostruito da un array vuoto.
Ho lottato con un orso una volta.

1
@Dekel - Versione più piccola :)var names = array.reduce(function(a, b) { a.indexOf(b.name) === -1 && a.push(b.name) return a; }, []);
Rayon

3
@ Rayon Per favore, non prestare troppa attenzione alla dimensione del codice sorgente, questa è una trappola comune per molti programmatori autoprodotti, soprattutto, il tuo programma dovrebbe essere efficiente e leggibile.
foglia

16

Personalmente non vedo perché tutti si stiano divertendo con ES 6. Se fosse il mio codice preferirei supportare più browser possibile.

var array=[
{id:123, value:"value1", name:"Name1"},
{id:124, value:"value2", name:"Name1"},
{id:125, value:"value3", name:"Name2"},
{id:126, value:"value4", name:"Name2"}
];

   // Create array of unique names
var a = (function(a){
  for (var i = array.length; i--;)
    if (a.indexOf(array[i].name) < 0) a.push(array[i].name);
  return a;
})([]);

console.log(a);


2
Non sarebbe più performante usare un oggetto come dizionario invece di indexOf-ing tutto il tempo? Ad esempio, fai added[name] = truee if(added[name])invece di if(a.indexOf(name)).
Bojidar Marinov

7
"personalmente non vedo perché tutti si stiano divertendo con es6" perché stai corteggiando a goto fail, facendo trapelare la tua variabile indice nello scope che lo racchiude, ecc. ecc. ES6 è stato codificato nello standard per buone ragioni, ed è per la maggior parte parte banalmente facile da polyfill / trasformare per i browser meno recenti.
Jared Smith

5
Più leggibile? È un pazzo per il ciclo che spinge gli elementi su un array. Non diventa molto più leggibile di così ...
ho lottato con un orso.

4
Onestamente, sembra che tu stia cercando troppo duramente di trovare qualcosa di cui lamentarti.
Ho lottato con un orso una volta.

2
"Mi riferisco al famoso bug SSL di Apple" - Non è così famoso che puoi semplicemente fare riferimento ad esso in commenti casuali fuori contesto e aspettarti che le persone lo riconoscano.
Hejazzman

14

Puoi anche semplicemente combinarlo mapconfilter

var array = [
  {id:123, value:"value1", name:"Name1"},
  {id:124, value:"value2", name:"Name1"},
  {id:125, value:"value3", name:"Name2"},
  {id:126, value:"value4", name:"Name2"}
];

var unique = array
  .map( item => item.name )
  .filter( ( item, idx, arr ) => arr.indexOf( item ) == idx ) 

console.log(unique)


11

È possibile utilizzare Object.keys () per ottenere l'array dei nomi di proprietà enumerabili di un determinato oggetto dal risultato dell'iterazione della arrayvariabile con Array.prototype.reduce () dove le chiavi sono i nomi distrutti

Codice:

const array = [{id:123, value:"value1", name:"Name1"}, {id:124, value:"value2", name:"Name1"}, {id:125, value:"value3", name:"Name2"}, {id:126, value:"value4", name:"Name2"}],
      names = Object.keys(array.reduce((a, { name }) => (a[name] = 1, a), {}))

console.log(names)


7

Molte buone risposte qui. Vorrei solo contribuire con un po 'di diversità con la speranza di darvi un'altra prospettiva.

Gli array sono di tipo oggetto in JavaScript, quindi possono essere usati come hash allo stesso tempo. Utilizzando questa funzionalità possiamo semplificare notevolmente il lavoro da svolgere in un'unica operazione di riduzione con O (n) tempo di complessità.

Se non sei soddisfatto del tuo array che detiene alcune proprietà diverse dalle chiavi dell'array, potresti considerare di tenere anche un oggetto hash separato.

var array = [{id:123, value:"value1", name:"Name1"},
             {id:124, value:"value2", name:"Name1"},
             {id:125, value:"value3", name:"Name2"},
             {id:126, value:"value4", name:"Name2"}
            ],
result = array.reduce((p,c) => p[c.name] ? p : (p[c.name] = true, p.push(c.name), p), []);
console.log(result);


Questa soluzione ES5 ha buone prestazioni, ma l'utilizzo dell'array per due scopi crea confusione e pericoloso se uno dei nomi è solo un numero. Consiglierei di fare le p[c.name]cose su un oggetto separato oe poi di scartarlo in seguito.
joeytwiddle

7

Sono d'accordo che se hai solo bisogno dei namevalori, a Setè la strada da percorrere.

Tuttavia , se si desidera ottenere una serie di oggetti univoci basati sulla nameproprietà, suggerirei di utilizzare un file Map. Un modo rapido per creare una mappa è tramite una serie di [key, value]array:

const array = [{ id: 123, value: "value1", name:"Name1" }, { id: 124, value: "value2", name: "Name1" }, { id: 125, value: "value3", name: "Name2" }, { id: 126, value: "value4", name: "Name2" }],
      unique = new Map(array.map(obj => [obj.name, obj]));

// To get the unique objects
const uniques = Array.from(unique.values());

// Get the names like you already did:
console.log("Names:", uniques.map(obj => obj.name));

// If you ever need the complete array of unique objects, you got a ref:
console.log(JSON.stringify(uniques));
.as-console-wrapper { min-height: 100%; }

Un ulteriore vantaggio di Mapè che si ottengono entrambe le filterfunzionalità che escludono i non unici, senza perdere la connessione con gli oggetti di origine. Ovviamente, è necessario solo se è necessario fare riferimento più volte all'insieme univoco di oggetti.



2

Con ES6 questo dovrebbe fare il lavoro.

var array=[
    {id:123, value:"value1", name:"Name1"},
    {id:124, value:"value2", name:"Name1"},
    {id:125, value:"value3", name:"Name2"},
    {id:126, value:"value4", name:"Name2"}
];

var set = new Set();

array.forEach((a)=>{
    set.add(a.name);
}); 

console.log(Array.from(set));


11
mapdovrebbe essere usato con funzioni pure, non per avere effetti collaterali. Questo sarebbe un lavoro per forEacho reduce.
Vincent van der Weele

1

Utilizzando UnderscoreJS,

array = [{id:123, value:"value1", name:"Name1"}, {id:124, value:"value2", name:"Name1"}, {id:125, value:"value3", name:"Name2"}, {id:126, value:"value4", name:"Name2"}];
get_names =  _.pluck(_.uniq(array, 'name'), 'name')
console.log(get_names)
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>

"


0

In ES5, utilizzare un oggetto come dizionario per le prestazioni O ( n ).

Funzionerà solo se tutte le chiavi sono stringhe.

var array = [
    {id: 123, value: "value1", name: "Name1"},
    {id: 124, value: "value2", name: "Name1"},
    {id: 125, value: "value3", name: "Name2"},
    {id: 126, value: "value4", name: "Name2"}
];

var allNames = array.map(item => item.name);

var map = {};
allNames.forEach(name => {
  map[name] = true;
});
var uniqueNames = Object.keys(map);

console.log(uniqueNames);

Puoi fare la stessa cosa in una sola espressione se ti piace:

var uniqueNames = Object.keys(allNames.reduce((m, n) => (m[n] = true, m), {}));

ma trovo la forma imperativa più facile da leggere.


Ho usato le funzioni freccia ES6 per chiarezza. Se stai davvero prendendo di mira ES5 senza un transpiler, dovrai invece utilizzare il modulo completo:function (item) { return item.name; }
joeytwiddle

In alternativa a Object.keys(), questa risposta utilizza filter()per mantenere solo quei nomi che non sono stati ancora memorizzati nella mappa, il che è piuttosto elegante.
joeytwiddle


0

Usa array#forEach()e array#indexOf()metodi come questo se vuoi ancora la massima compatibilità , sintassi concisa:

const array = [{ id: 123, value: "value1", name:"Name1" }, { id: 124, value: "value2", name: "Name1" }, { id: 125, value: "value3", name: "Name2" }, { id: 126, value: "value4", name: "Name2" }]

// initialize an empty array named names
let names = [];

// iterate through every element of `array` & check if it's 'name' key's value already in names array if not ADD it 
array.forEach(function(element) { if (names.indexOf(element.name) === -1) names.push(element.name) });
// or use tilde like this:
//array.forEach(function(element) { if (~names.indexOf(element.name)) names.push(element.name) });

console.log(names);

Tuttavia, se la compatibilità è non è un problema uso EcmaScript 6 's Setoggetto, array#mape Array.from()metodi come questo:

const array = [{ id: 123, value: "value1", name:"Name1" }, { id: 124, value: "value2", name: "Name1" }, { id: 125, value: "value3", name: "Name2" }, { id: 126, value: "value4", name: "Name2" }];

// iterate through every element from array using map and store it in Set(a Set won't have duplicates) then convert the Set back to Array(using Array.from)
let names = Array.from(new Set(array.map(element => element.name)));

console.log(names);


0

È così che l'ho fatto, utilizzando un array vuoto separato.

var array = [
   {id:123, value:"value1", name:"Name1"},
   {id:124, value:"value2", name:"Name1"},
   {id:125, value:"value3", name:"Name2"},
   {id:126, value:"value4", name:"Name2"}	    
];

var array2 = []		
		
for (i=0; i<array.length;i++){						
   if (array2.indexOf(array[i].name) == -1){				
     array2.push(array[i].name);
    }
}			

console.log(array2)	


-1

Per chi cerca una linea 1

const names = array.reduce((acc, {name}) => acc.includes(name) ? acc : [name, ...acc], []);

o senza utilizzare metodi sul prototipo dell'array

const { reduce, includes } = Array;
const names = reduce(array, (acc, {name}) => includes(acc, name) ? acc : [name, ...acc], []);

potrebbe essere utile per scrivere alcune funzioni pure per affrontare questo problema

const get_uniq_values = (key, arr) => reduce(arr, (a, o) => includes(a, o[key]) ? a : [o[key], ...a], []);

-1
var __array=[{id:123, value:"value1", name:"Name1"},{id:124, value:"value2", name:"Name1"},{id:125, value:"value3", name:"Name2"},{id:126, value:"value4", name:"Name2"}];

function __checkArray(__obj){
    var flag = true;
    for(let i=0; i < __array.length; i++){
        if(__obj.id == __array.id){
            flag = false;
            break;
        }
    }

    return flag;
}

var __valToPush = {id: 127, value: "value5", name: "Name3"};
if(__checkArray(__valToPush)){
    __array.push(__valToPush)
}

11
hai un motivo speciale per usare così tanti trattini bassi?
Nina Scholz
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.