Eliminar duplicados de una matriz

100

Tengo una matriz de objetos que se parece a esto:

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"}
    ...
];

Como puede ver, se repiten algunos nombres. Quiero obtener una nueva matriz solo con nombres, pero si algún nombre se repite, no quiero volver a agregarlo. Quiero esta matriz:

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

Estoy tratando de hacer esto con map:

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

Pero el problema es que esto vuelve:

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

¿Cómo puedo establecer alguna condición interna mappara que no devuelva un elemento que ya existe? Quiero hacer esto con mapo alguna otra característica de ECMAScript 5 o ECMAScript 6.

snoopy25
fuente
2
Luego elimine los duplicados de la matriz.
Tushar
1
posible duplicado de Eliminar duplicados de la matriz de JavaScript
Bergi
¿Por qué la línea que contiene id: 125 no termina con una coma?
Peter Mortensen
Tenga en cuenta que todas las respuestas que utilicen .indexOf () tendrán un rendimiento deficiente si la matriz es grande, debido a su complejidad de tiempo cuadrático . Recomendaría usar ES6 Set o usar un objeto ES5 como diccionario.
joeytwiddle

Respuestas:

210

Con ES6, puede utilizar Setpara valores únicos, después de asignar solo los nombres de los objetos.

Esta propuesta utiliza una sintaxis de extensión... para recopilar los elementos en una nueva matriz.

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

Nina Scholz
fuente
9
Para mi información .. ¿qué hace ...?
Rajshekar Reddy
9
@RajshekarReddy, es una sintaxis extendida... para construir una matriz con un objeto iterable.
Nina Scholz
5
No estoy votando a favor del "Conjunto" en sí, sino más bien por el uso inteligente del operador de propagación. Siempre es bueno aprender algo nuevo o ver algo nuevo aplicado a un ejemplo práctico, por lo que esta publicación merece un voto positivo.
briosheje
33
Es solo mi opinión personal, pero bastante común . El uso Array.fromtransmite la intención de conversión mucho mejor (y también es más fácil de entender / buscar novatos), la sintaxis de propagación debe usarse solo cuando el elemento de propagación sea uno de muchos. Quizás llamarlo "mala práctica" sea un poco duro, lo siento, solo espero evitar que se convierta en un modismo común.
Bergi
3
@KRyan ¿ES6 es tan nuevo? Se finalizó en junio de 2015. Es hora de comenzar a comprender y usar las nuevas funciones
edc65
64

Si está buscando una solución de JavaScript que no sea ES 6 (no Set), puede usar el método de 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);

Dekel
fuente
2
Esta respuesta también tiene la ventaja de que no requiere matrices o conjuntos intermedios, especialmente aquellos que contienen solo los nombres. (Va directamente de una matriz de objetos a una matriz de objetos). +1
jpmc26
@Iwrestledabearonce, ¿no es este segundo parámetro el mismo objeto que el devuelto?
Kaiido
Sí, pero no va directamente de uno a otro como clasificación o filtro, se reconstruye a partir de una matriz vacía.
Luché con un oso una vez.
1
@Dekel - Versión más pequeña :)var names = array.reduce(function(a, b) { a.indexOf(b.name) === -1 && a.push(b.name) return a; }, []);
Rayon
3
@Rayon Por favor, no preste demasiada atención al tamaño del código fuente, este es un error común para muchos programadores hechos por ellos mismos, lo más importante es que su programa debe ser eficiente y legible.
hoja
16

Personalmente, no veo por qué todo el mundo se está volviendo elegante con ES 6. Si fuera mi código, preferiría admitir tantos navegadores como sea posible.

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

Una vez luché con un oso.
fuente
2
¿No será más eficaz utilizar un objeto como diccionario en lugar de indexOf-ing todo el tiempo? Por ejemplo, hacer added[name] = truey en if(added[name])lugar de if(a.indexOf(name)).
Bojidar Marinov
7
"personalmente, no veo por qué todo el mundo se está volviendo elegante con es6" por qué está cortejando a goto fail, filtrando su variable de índice en el alcance adjunto, etc. etc. ES6 fue codificado en el estándar por buenas razones, y es para la mayoría parte trivialmente fácil de rellenar / transformar para navegadores más antiguos.
Jared Smith
5
¿Más legible? Es un maldito bucle que empuja elementos a una matriz. No hay nada más legible que eso ......
Luché con un oso una vez.
4
Honestamente, se siente como si estuvieras esforzándote demasiado para encontrar algo de qué quejarte.
Luché con un oso una vez.
2
"Me refiero al famoso error de SSL de Apple". No es tan famoso que puedas simplemente ir y referirte a él en comentarios aleatorios fuera de contexto y esperar que la gente lo reconozca.
Hejazzman
14

También puedes simplemente combinar 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)

DavidDomain
fuente
11

Puede usar Object.keys () para obtener la matriz de los nombres de propiedad enumerables propios de un objeto dado del resultado del objeto de la arrayvariable iterativa con Array.prototype.reduce () donde las claves son los nombres destruidos

Código:

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)

Yosvel Quintero Argüelles
fuente
7

Muchas buenas respuestas aquí. Solo me gustaría contribuir con algo de diversidad con la esperanza de darles otra perspectiva.

Las matrices son del tipo de objeto en JavaScript, por lo que se pueden usar como hash al mismo tiempo. Al utilizar esta funcionalidad, podemos simplificar enormemente el trabajo a realizar en una sola operación de reducción con una complejidad de tiempo O (n).

Si no está satisfecho con que su matriz tenga algunas propiedades distintas de las claves de la matriz, podría considerar mantener un objeto hash separado también.

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

Redu
fuente
Esta solución ES5 tiene un buen rendimiento, pero usar la matriz para dos propósitos es confuso y peligroso si uno de los nombres es solo un número. Recomendaría hacer las p[c.name]cosas en un objeto separado oy luego desecharlo.
joeytwiddle
7

Estoy de acuerdo en que si solo necesita los namevalores, a Setes el camino a seguir.

Sin embargo , si desea obtener una matriz de objetos únicos basados ​​en la namepropiedad, le sugiero que use un archivo Map. Una forma rápida de crear un mapa es a través de una matriz de [key, value]matrices:

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 beneficio adicional Mapes que obtiene la filterfuncionalidad que elimina los no únicos, sin perder la conexión con los objetos de origen. Por supuesto, solo es necesario si necesita hacer referencia al conjunto único de objetos varias veces.

usuario3297291
fuente
3

Si está limitado a ES5, usaría Lodash's_.uniq

var newArray = _.uniq(array.map(function(a) {
  return a.name;
}));
AndrewHenderson
fuente
2

Con ES6 esto debería funcionar.

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

kkreft
fuente
11
mapse supone que debe usarse con funciones puras, no para tener efectos secundarios. Este sería un trabajo para forEacho reduce.
Vincent van der Weele
1

Usando 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>

'

Mohideen bin Mohammed
fuente
0

En ES5, use un objeto como diccionario para el rendimiento de O ( n ).

Esto solo funcionará si todas las claves son cadenas.

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

Puede hacer lo mismo en una expresión si lo desea:

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

pero encuentro la forma imperativa más fácil de leer.

joeytwiddle
fuente
Usé las funciones de flecha ES6 para mayor claridad. Si realmente está apuntando a ES5 sin un transpiler, entonces deberá usar el formulario completo en su lugar:function (item) { return item.name; }
joeytwiddle
Como alternativa a Object.keys(), esta respuesta utiliza filter()para mantener solo aquellos nombres que aún no se han almacenado en el mapa, lo cual es bastante elegante.
joeytwiddle
0

Prueba esto:

nArr = [];
array.forEach((a) => {
    if (nArr.indexOf(a.name) < 0) { 
        nArr.push(a.name); 
    }
}); 
Rafael Gomes Francisco
fuente
0

Utilice métodos array#forEach()y array#indexOf()como este si desea la máxima compatibilidad , sintaxis 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);

Sin embargo, si la compatibilidad es no es un problema uso ECMAScript 6 's Setde objeto, array#mapy Array.from()métodos como este:

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

Barba Negra
fuente
0

Así es como lo hice, usando una matriz vacía separada.

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)	

Abhishek Gurjar
fuente
-1

Para aquellos que buscan un delineador 1

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

o sin usar métodos en el prototipo de la matriz

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

podría ser útil para escribir algunas funciones puras para lidiar con esto

const get_uniq_values = (key, arr) => reduce(arr, (a, o) => includes(a, o[key]) ? a : [o[key], ...a], []);
Tyrell
fuente
-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)
}
AKHIL
fuente
11
¿Tiene una razón especial para usar tantos guiones bajos?
Nina Scholz