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.


2
Em seguida, remova as duplicatas da matriz.
Tushar



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


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


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


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)


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)


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


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.



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


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>

`


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.


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

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


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)	


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

-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
você tem um motivo especial para usar tantos sublinhados?
Nina Scholz de
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.