Remova duplicatas de uma matriz

100

Tenho uma série de objetos parecidos com:

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 você pode ver, alguns nomes se repetem. Quero obter uma nova matriz apenas com nomes, mas se algum nome se repetir, não quero adicioná-lo novamente. Eu quero esta matriz:

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

Estou tentando fazer isso com map:

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

Mas o problema é que isso retorna:

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

Como posso definir alguma condição interna map, para que não retorne um elemento que já existe? Desejo fazer isso com mapou algum outro recurso ECMAScript 5 ou ECMAScript 6.

snoopy 25
fonte
2
Em seguida, remova as duplicatas da matriz.
Tushar
1
possível duplicata de Remove Duplicates from JavaScript Array
Bergi
Por que a linha que contém id: 125 não termina com uma vírgula?
Peter Mortensen
Observe que todas as respostas usando .indexOf () terão desempenho ruim se o array for grande, devido à sua complexidade quadrática de tempo . Eu recomendaria usar o conjunto ES6 ou usar um objeto ES5 como dicionário.
joeytwiddle

Respostas:

210

Com o ES6, você pode usar Setpara valores únicos, após mapear apenas os nomes dos objetos.

Esta proposta usa uma sintaxe difusa... para coletar os itens em uma nova 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
fonte
9
Para minha informação .. o que ...fazer?
Rajshekar Reddy
9
@RajshekarReddy, é uma sintaxe difundida... para construir um array com um objeto iterável.
Nina Scholz
5
Não estou votando positivamente pelo "Set" em si, mas sim pelo uso inteligente do operador spread. É sempre bom aprender algo novo ou ver algo novo aplicado a um exemplo prático, então este post merece um voto positivo.
briosheje
33
É apenas minha opinião pessoal, mas bastante comum . Usar Array.fromtransmite a intenção de conversão muito melhor (e também é mais fácil de entender / pesquisar por novatos), a sintaxe de propagação deve ser usada apenas onde o elemento de propagação é um entre muitos. Talvez chamar isso de "prática ruim" seja um pouco duro demais, desculpe, só espero evitar que isso se torne um idioma comum.
Bergi
3
@KRyan é o ES6 tão novo? Foi finalizado em junho de 2015. É hora de começar a entender e usar os novos recursos
edc65
64

Se você estiver procurando por uma solução JavaScript que não seja ES 6 (sem Set), você pode usar o método 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
fonte
2
Essa resposta também tem a vantagem de não exigir matrizes ou conjuntos intermediários, especialmente aqueles que contêm apenas os próprios nomes. (Vai diretamente de matriz de objetos para matriz de objetos.) +1
jpmc26
@Iwrestledabearonce, este segundo parâmetro não é o mesmo objeto que o retornado?
Kaiido
Sim, mas não vai direto de um para o outro como tipo ou filtro, é reconstruído a partir de uma matriz vazia.
Lutei com um urso uma vez.
1
@Dekel - Versão menor :)var names = array.reduce(function(a, b) { a.indexOf(b.name) === -1 && a.push(b.name) return a; }, []);
Rayon
3
@Rayon Por favor, não preste muita atenção ao tamanho do código-fonte, esta é uma armadilha comum para muitos programadores feitos por eles mesmos, o mais importante, seu programa deve ser eficiente e legível.
folha de
16

Pessoalmente, não vejo por que todo mundo está ficando tão extravagante com o ES 6. Se fosse meu código, preferiria oferecer suporte ao maior número de navegadores possível.

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

Uma vez eu lutei com um urso.
fonte
2
Não seria mais eficiente usar um objeto como dicionário em vez de usar o indexOf-ing o tempo todo? Por exemplo, faça added[name] = truee em if(added[name])vez de if(a.indexOf(name)).
Bojidar Marinov
7
"pessoalmente, não vejo por que todo mundo está ficando todo animado com es6" por que você está cortejando um goto fail, vazando sua variável de índice para o escopo delimitador, etc. etc. ES6 foi codificado no padrão por boas razões e é para a maioria parte trivialmente fácil de polyfill / transform para navegadores mais antigos.
Jared Smith
5
Mais legível? É um loop que empurra itens para uma matriz. Não fica muito mais legível do que isso ...
Lutei com um urso uma vez.
4
Sinceramente, parece que você está tentando muito encontrar algo do que reclamar.
Lutei com um urso uma vez.
2
"Estou me referindo ao famoso bug SSL da Apple" - Não é tão famoso que você possa simplesmente consultá-lo em comentários aleatórios fora do contexto e esperar que as pessoas o reconheçam.
Hejazzman
14

Você também pode simplesmente combinar mapcomfilter

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
fonte
11

Você pode usar Object.keys () para obter a matriz dos nomes de propriedade enumeráveis ​​de um determinado objeto a partir do resultado do objeto da arrayvariável iterativa com Array.prototype.reduce () onde as chaves são os nomes destruídos

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 Arguelles
fonte
7

Muitas boas respostas aqui. Eu só gostaria de contribuir com alguma diversidade na esperança de dar a vocês uma outra perspectiva.

Os arrays são do tipo de objeto em JavaScript, portanto, podem ser usados ​​como hash ao mesmo tempo. Ao usar essa funcionalidade, podemos simplificar muito o trabalho a ser feito em uma única operação de redução com complexidade de tempo O (n).

Se você não está satisfeito com seu array contendo algumas propriedades além das chaves de array, você pode considerar manter um objeto hash separado também.

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
fonte
Esta solução ES5 tem bom desempenho, mas usar o array para duas finalidades é confuso e perigoso se um dos nomes for apenas um número. Eu recomendaria fazer as p[c.name]coisas em um objeto separado oe depois descartá-lo.
joeytwiddle
7

Concordo que, se você só precisa dos namevalores, a Seté o caminho a seguir.

No entanto , se você deseja obter uma matriz de objetos exclusivos com base na namepropriedade, sugiro usar um Map. Uma maneira rápida de criar um mapa é por meio de uma matriz de [key, value]matrizes:

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%; }

Um benefício adicional do Mapé que você obtém a filterfuncionalidade que corta os não-únicos, sem perder a conexão com os objetos de origem. Claro, ele só é necessário se você precisar fazer referência ao conjunto exclusivo de objetos várias vezes.

user3297291
fonte
3

Se você está limitado a ES5, eu usaria o Lodash's_.uniq

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

Com ES6, isso deve fazer o trabalho.

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
fonte
11
mapdeve ser usado com funções puras, não para ter efeitos colaterais. Isso seria um trabalho para forEachou 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
fonte
0

No ES5, use um objeto como um dicionário para desempenho O ( n ).

Isso só funcionará se todas as chaves forem Strings.

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

Você poderia fazer a mesma coisa em uma expressão se quiser:

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

mas acho a forma imperativa mais fácil de ler.

joeytwiddle
fonte
Usei as funções de seta ES6 para maior clareza. Se você realmente está mirando no ES5 sem um transpiler, então você precisará usar o formulário completo:function (item) { return item.name; }
joeytwiddle
Como alternativa a Object.keys(), esta resposta costuma filter()manter apenas os nomes que ainda não foram armazenados no mapa, o que é bastante elegante.
joeytwiddle
0

Experimente isto:

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

Use array#forEach()e array#indexOf()métodos como este se você deseja compatibilidade máxima ainda, sintaxe 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);

No entanto, se a compatibilidade não for um problema use o objeto ECMAScript 6 e métodos como este: Setarray#mapArray.from()

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
fonte
0

Foi assim que fiz, usando um array vazio separado.

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
fonte
-1

Para quem procura um 1 liner

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

ou sem usar métodos no protótipo do array

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

pode ser útil para escrever algumas funções puras para lidar com este

const get_uniq_values = (key, arr) => reduce(arr, (a, o) => includes(a, o[key]) ? a : [o[key], ...a], []);
Tyrell
fonte
-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
fonte
11
você tem um motivo especial para usar tantos sublinhados?
Nina Scholz de