Obter todos os valores exclusivos em uma matriz JavaScript (remover duplicatas)

1488

Eu tenho uma matriz de números que eu preciso ter certeza de que são únicos. Encontrei o trecho de código abaixo na internet e ele funciona muito bem até que o array tenha zero. Encontrei esse outro script aqui no Stack Overflow que se parece quase exatamente com ele, mas não falha.

Então, para me ajudar a aprender, alguém pode me ajudar a determinar onde o script do protótipo está errado?

Array.prototype.getUnique = function() {
 var o = {}, a = [], i, e;
 for (i = 0; e = this[i]; i++) {o[e] = 1};
 for (e in o) {a.push (e)};
 return a;
}

Mais respostas da pergunta duplicada:

Pergunta semelhante:

Mottie
fonte
3
@hippietrail Essa pergunta mais antiga é sobre encontrar e retornar apenas as duplicatas (eu também estava confuso!). Minha pergunta é mais sobre por que essa função falha quando uma matriz tem um zero nela.
Mottie
Você provavelmente também quer tornar o título da sua pergunta menos vago.
Hippietrail
Para futuros leitores, quando começar a descobrir que é necessário modificar o conteúdo da sua estrutura de dados o tempo todo (ordená-los, remover elementos repetidos etc.) ou procurar elementos dentro dela a cada iteração, é seguro supor que você está usando a estrutura de dados incorreta em primeiro lugar e comece a usar uma que seja mais apropriada para a tarefa em questão (nesse caso, um conjunto de hash em vez de matriz).
precisa
Copiei o código de algum outro lugar, um loooong atrás ... mas parece bem simples: o= object, a= array, i= indexe e= umm, algo: P
Mottie
Possível duplicata Como obter valores exclusivos em uma matriz
Adeel Imran

Respostas:

2661

Com o JavaScript 1.6 / ECMAScript 5, você pode usar o filtermétodo nativo de uma matriz da seguinte maneira para obter uma matriz com valores exclusivos:

function onlyUnique(value, index, self) { 
    return self.indexOf(value) === index;
}

// usage example:
var a = ['a', 1, 'a', 2, '1'];
var unique = a.filter( onlyUnique ); // returns ['a', 1, 2, '1']

O método nativo filterpercorrerá a matriz e deixará apenas as entradas que passam pela função de retorno de chamada fornecida onlyUnique.

onlyUniqueverifica se o valor fornecido é o primeiro a ocorrer. Caso contrário, deve ser uma duplicata e não será copiada.

Esta solução funciona sem nenhuma biblioteca extra como jQuery ou prototype.js.

Também funciona para matrizes com tipos de valor misto.

Para navegadores antigos (<IE9), que não suportam os métodos nativos filtere indexOfpode encontrar arounds trabalho na documentação MDN para filtro e indexOf .

Se você deseja manter a última ocorrência de um valor, substitua-o indexOfpor lastIndexOf.

Com o ES6, pode ser reduzido para isso:

// usage example:
var myArray = ['a', 1, 'a', 2, '1'];
var unique = myArray.filter((v, i, a) => a.indexOf(v) === i); 

// unique is ['a', 1, 2, '1']

Obrigado a Camilo Martin pela dica no comentário.

O ES6 possui um objeto nativo Setpara armazenar valores exclusivos. Para obter uma matriz com valores exclusivos, você pode fazer isso agora:

var myArray = ['a', 1, 'a', 2, '1'];

let unique = [...new Set(myArray)]; 

// unique is ['a', 1, 2, '1']

O construtor de Setpega um objeto iterável, como Matriz, e o operador de dispersão ...transforma o conjunto novamente em uma Matriz. Obrigado a Lukas Liese pela dica no comentário.

TLindig
fonte
54
Infelizmente, esta solução será muito mais lenta. Você está looping duas vezes, uma com filtro e uma vez com índice de
Jack Franzen
19
@JackFranzen Mais lento que o que? A solução do Rafael ? A solução Rafaels não funciona para matrizes de tipo misto. Para o meu exemplo, ['a', 1, 'a', 2, '1']você receberia ['a', 1, 2]. Mas não é isso que eu esperava. BTW, muito mais lento é muito relativo.
TLindig
25
No JS moderno: .filter((v,i,a)=>a.indexOf(v)==i)(notação de seta gorda).
Camilo Martin
164
let unique_values = [...new Set(random_array)]; developer.mozilla.org/pt-BR/docs/Web/JavaScript/Reference/…
Lukas Liesis
5
Para uma resposta mais detalhada, incluindo muitas possibilidades - como classificação em primeiro lugar, e lidar com diferentes tipos de dados - ver stackoverflow.com/a/9229821/368896
Dan Nissenbaum
876

Resposta atualizada para ES6 / ES2015 : Usando o conjunto , a solução de linha única é:

var items = [4,5,4,6,3,4,5,2,23,1,4,4,4]
var uniqueItems = Array.from(new Set(items))

Que retorna

[4, 5, 6, 3, 2, 23, 1]

Como le_m sugeriu, isso também pode ser reduzido usando o operador spread , como

var uniqueItems = [...new Set(items)]
AT
fonte
11
Observe, essa matriz interna não iria funcionarArray.from(new Set([[1,2],[1,2],[1,2,3]]))
Alexander Goncharov
3
Desempenho desta solução em comparação com myArray.filter((v, i, a) => a.indexOf(v) === i);?
Simoyw
43
Observe que, se você usar os Setobjetos e adicionar objetos em vez de valores primitivos, ele conterá referências exclusivas aos objetos. Assim, o set sin let s = new Set([{Foo:"Bar"}, {Foo:"Bar"}]);retornará this: Set { { Foo: 'Bar' }, { Foo: 'Bar' } }que é um Setobjeto com referências únicas a objetos que contêm os mesmos valores. Se você escrever let o = {Foo:"Bar"};e, em seguida, criar um conjunto com duas referências assim: let s2 = new Set([o,o]);, então s2 seráSet { { Foo: 'Bar' } }
mortb
2
Ambas as opções têm problemas no IE10, mesmo com uma camada polyfill.io presente #
Thymine
2
novo caso de teste jsperf.com/array-filter-unique-vs-new-set/1 parece new Set's troféu
shuk
167

Sei que esta pergunta já tem mais de 30 respostas. Mas li todas as respostas existentes primeiro e fiz minha própria pesquisa.

Dividi todas as respostas para 4 soluções possíveis:

  1. Use o novo recurso ES6: [...new Set( [1, 1, 2] )];
  2. Use objeto { }para evitar duplicatas
  3. Usar matriz auxiliar [ ]
  4. Usar filter + indexOf

Aqui estão exemplos de códigos encontrados nas respostas:

Use o novo recurso ES6: [...new Set( [1, 1, 2] )];

function uniqueArray0(array) {
  var result = Array.from(new Set(array));
  return result    
}

Use objeto { }para evitar duplicatas

function uniqueArray1( ar ) {
  var j = {};

  ar.forEach( function(v) {
    j[v+ '::' + typeof v] = v;
  });

  return Object.keys(j).map(function(v){
    return j[v];
  });
} 

Usar matriz auxiliar [ ]

function uniqueArray2(arr) {
    var a = [];
    for (var i=0, l=arr.length; i<l; i++)
        if (a.indexOf(arr[i]) === -1 && arr[i] !== '')
            a.push(arr[i]);
    return a;
}

Usar filter + indexOf

function uniqueArray3(a) {
  function onlyUnique(value, index, self) { 
      return self.indexOf(value) === index;
  }

  // usage
  var unique = a.filter( onlyUnique ); // returns ['a', 1, 2, '1']

  return unique;
}

E me perguntei qual é o mais rápido. Fiz uma amostra do Google Sheet para testar funções. Nota: O ECMA 6 não está disponível no Planilhas Google, então não posso testá-lo.

Aqui está o resultado dos testes: insira a descrição da imagem aqui

Eu esperava ver que o código usando o objeto { }vencerá porque ele usa hash. Estou feliz que os testes tenham mostrado melhores resultados para esse algoritmo no Chrome e no IE. Obrigado a @rab pelo código .

Max Makhrov
fonte
A opção "filter + indexOf" é extremamente lenta em matrizes com mais de 100.000 itens. Eu tive que usar a abordagem "mapa de objetos", no entanto, quebra a classificação original.
liberborn 14/09
O número mais alto é mais lento, não é mais rápido?
fletchsod
3
@ fletchsod, os números são o tempo em ms para executar o código.
Max Makhrov
1
Os números estão errados! O script do Google Apps é executado no servidor, não no navegador do cliente - e, portanto, o desempenho no Chrome / IE (ou praticamente em qualquer navegador) será o mesmo!
Jay Dadhania 11/04
1
Embora seja possível executar JS do lado do cliente no Google Script (via google.script.host ou google.script.run - não sei ao certo), a resposta afirma claramente que "o ECMA 6 não está disponível no Planilhas Google" - portanto podemos assumir com segurança que o pôster usou o Script do Google no servidor para isso - e, portanto, a resposta mostra o desempenho do servidor do Google, não do navegador !!
Jay Dadhania 11/04
134

Você também pode usar underscore.js .

console.log(_.uniq([1, 2, 1, 3, 1, 4]));
<script src="http://underscorejs.org/underscore-min.js"></script>

que retornará:

[1, 2, 3, 4]
kornfridge
fonte
22
Por favor, faça isso pessoal. Não coloque algo no protótipo Array. Por favor.
Jacob Dalton
5
@ JacobDalton - Isso não está estendendo o protótipo Array. É namespaced no objeto _.
superluminary
25
@ superluminary Eu sei que é por isso que eu disse, por favor, faça isso. A solução aceita sugere a modificação do protótipo Array. NÃO faça isso.
Jacob Dalton
21
@JacobDalton Por favor, não faça isso. Não há necessidade de adicionar uma biblioteca extra apenas para um pequeno trabalho que pode ser feito com #array = [...new Set(array)]
3
Isso anulou o propósito de usar os recursos do ES e adicionar mais bibliotecas apenas incha mais o site.
fletchsod
68

One Liner, JavaScript puro

Com sintaxe ES6

list = list.filter((x, i, a) => a.indexOf(x) == i)

x --> item in array
i --> index of item
a --> array reference, (in this case "list")

insira a descrição da imagem aqui

Com sintaxe ES5

list = list.filter(function (x, i, a) { 
    return a.indexOf(x) == i; 
});

Compatibilidade do Navegador : IE9 +

Vamsi
fonte
14
@Spets Tem custo quadrática, por isso não é a melhor resposta
Oriol
@ Spets como todos os únicos / distintos ... sim, seja inteligente, use o radix para classificar primeiro, seja mais lento que 0 (n2) na pesquisa rápida na maioria dos casos.
Doker 7/12
obrigado rapazes! Não percebi quando olhei o código pela primeira vez, mas agora é um pouco mais óbvio.
Spets
51

Desde então, encontrei um bom método que usa jQuery

arr = $.grep(arr, function(v, k){
    return $.inArray(v ,arr) === k;
});

Nota: Este código foi retirado do post de soco de pato de Paul Irish - eu esqueci de dar crédito: P

Mottie
fonte
8
Uma solução concisa, mas chamar inArray é muito menos eficiente do que chamar hasOwnProperty.
Sr. Smith
Isso também é O (N ^ 2), certo? Enquanto a abordagem dicionário ou hasOwnProperty provavelmente seria O (N * logN).
speedplane
Isso funcionou para mim, todas as outras soluções não eram suportadas pelo Internet Explorer.
precisa saber é
51

Solução mais curta com ES6: [...new Set( [1, 1, 2] )];

Ou se você deseja modificar o protótipo Array (como na pergunta original):

Array.prototype.getUnique = function() {
    return [...new Set( [this] )];
};

No momento, o EcmaScript 6 é implementado apenas parcialmente em navegadores modernos (agosto de 2015), mas o Babel se tornou muito popular ao transpilar o ES6 (e até o ES7) de volta para o ES5. Dessa forma, você pode escrever o código ES6 hoje!

Se você está se perguntando o que isso ...significa, é chamado de operador de propagação . No MDN : «O operador spread permite que uma expressão seja expandida em locais onde são esperados múltiplos argumentos (para chamadas de função) ou múltiplos elementos (para literais de matriz)». Como um conjunto é iterável (e pode ter apenas valores exclusivos), o operador de expansão expandirá o conjunto para preencher a matriz.

Recursos para aprender ES6:

Torsten Becker
fonte
3
você pode fazê-lo ainda mais curto, com a = [...Set(a)], mas, de qualquer forma, este é apenas o Firefox, por enquanto.
C69
@ C69, certo, não ficará mais curto que isso. Os usuários do SpiderMonkey também irão gostar.
Torsten Becker
Trabalha com Babel. Você só precisa incluir o Array.from shim também:require ( "core-js/fn/array/from" );
Vladislav Rastrusny
deve ser Array.prototype.getUnique = function () {return [... new Set ([this])]; }; ou, Array.prototype.getUnique = function () {return [... new Set (this)]; }; Eu o apliquei no seguinte- $ ('body'). Html (). ToLowerCase (). Match (/ ([a-zA-Z0-9 ._ + -] + @ [a-zA-Z0-9. _-] + \. [a-zA-Z0-9 ._-] +) / gi) .getUnique (); O primeiro me devolveu um valor único apenas onde o segundo ajustou todas as remoções duplicadas.
Ashique
[...Set(['a', 1, 'a', 2, '1'])]irá lançar um TypeError, por isso ainda é aconselhável manter o new:[...new Set(['a', 1, 'a', 2, '1'])]
Adam Katz
36

Solução mais simples:

var arr = [1, 3, 4, 1, 2, 1, 3, 3, 4, 1];
console.log([...new Set(arr)]);

Ou:

var arr = [1, 3, 4, 1, 2, 1, 3, 3, 4, 1];
console.log(Array.from(new Set(arr)));

Pedro L.
fonte
4
Observe que isso não é suportado no IE10 e abaixo. Note-se que IE11 + e Safari oferecer suporte limitado (nem mesmo certeza de que o exemplo obras acima) developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/...
oriadam
32

A maneira mais simples e rápida (no Chrome) de fazer isso:

Array.prototype.unique = function() {
    var a = [];
    for (var i=0, l=this.length; i<l; i++)
        if (a.indexOf(this[i]) === -1)
            a.push(this[i]);
    return a;
}

Simplesmente percorre todos os itens da matriz, testa se esse item já está na lista e, se não estiver, envie para a matriz retornada.

De acordo com o jsPerf, essa função é a mais rápida que encontrei em qualquer lugar - fique à vontade para adicionar a sua.

A versão sem protótipo:

function uniques(arr) {
    var a = [];
    for (var i=0, l=arr.length; i<l; i++)
        if (a.indexOf(arr[i]) === -1 && arr[i] !== '')
            a.push(arr[i]);
    return a;
}

Ordenação

Quando também é necessário classificar a matriz, o seguinte é o mais rápido:

Array.prototype.sortUnique = function() {
    this.sort();
    var last_i;
    for (var i=0;i<this.length;i++)
        if ((last_i = this.lastIndexOf(this[i])) !== i)
            this.splice(i+1, last_i-i);
    return this;
}

ou não protótipo:

function sortUnique(arr) {
    arr.sort();
    var last_i;
    for (var i=0;i<arr.length;i++)
        if ((last_i = arr.lastIndexOf(arr[i])) !== i)
            arr.splice(i+1, last_i-i);
    return arr;
}

Isso também é mais rápido que o método acima na maioria dos navegadores não-chrome.

Joeytje50
fonte
No Linux, o Chrome 55.0.2883 prefere o arr.unique () e o arrclone2.sortFilter () da swilliams é o mais lento (78% mais lento). No entanto, o Firefox 51.0.0 (com muitos addons) tem swilliams mais rápido (ainda mais lento em Ops / s do que qualquer outro resultado do Chrome), com o jQuery $ .grep (arr, jqFilter) da mottie sendo o mais lento (46% mais lento). Seu arr.uniq () foi 30% mais lento. Fiz cada teste duas vezes e obtive resultados consistentes. O arr.getUnique () de Rafael ficou em segundo lugar nos dois navegadores.
23417 Adam
JSPerf é de buggy no momento, por isso a minha edição para este teste não cometeu tudo, mas fiz resultado na adição de dois testes: de Cocco toUnique () bate de Vamsi list.filter ES6 () em ambos os navegadores, batendo sortFilter swilliams () para o número 1 no FF (sortFilter foi 16% mais lento) e superando o teste classificado (que foi mais lento em 2%) para o número 3 no Chrome.
Adam Katz
Ah, eu não percebi que esses testes eram trivialmente pequenos e realmente não importam. Um comentário à resposta aceita descreve esse problema e oferece uma correção em uma revisão do teste, na qual o código de Rafael é facilmente o mais rápido e o código arr.unique de Joetje50 é 98% mais lento. Também fiz outra revisão, conforme observado neste comentário .
Adam Katz
1
Bem, na verdade, o algoritmo que você implementou na uniquefunção tem complexidade O (n ^ 2), enquanto o algoritmo getUniqueé O (n). O primeiro pode ser mais rápido em pequenos conjuntos de dados, mas como você pode argumentar com a matemática :) Você pode garantir que o último seja mais rápido se você executá-lo em uma variedade de, por exemplo, 1e5 itens exclusivos
Mikhail Dudin
31

SOMENTE DESEMPENHO! esse código é provavelmente 10 vezes mais rápido que todos os códigos aqui * funciona em todos os navegadores e também tem o menor impacto na memória .... e mais

se você não precisar reutilizar o array antigo, faça as outras operações necessárias antes de convertê-lo em exclusivo aqui, provavelmente é a maneira mais rápida de fazer isso, também muito curta.

var array=[1,2,3,4,5,6,7,8,9,0,1,2,1];

então você pode tentar isso

var array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 1];

function toUnique(a, b, c) { //array,placeholder,placeholder
  b = a.length;
  while (c = --b)
    while (c--) a[b] !== a[c] || a.splice(c, 1);
  return a // not needed ;)
}
console.log(toUnique(array));
//[3, 4, 5, 6, 7, 8, 9, 0, 2, 1]

Eu vim com essa função lendo este artigo ...

http://www.shamasis.net/2009/09/fast-algorithm-to-find-unique-items-in-javascript-array/

Eu não gosto do loop for. ele tem muitos parâmetros. eu gosto do loop while--. enquanto é o loop mais rápido em todos os navegadores, exceto no que todos gostamos tanto ... chrome.

de qualquer maneira eu escrevi a primeira função que usa while.E sim, é um pouco mais rápido que a função encontrada no article.but não é suficiente.unique2()

próximo passo use js modernos. Object.keys Substituí o outro por loop com Object.keys do js1.7 ... um pouco mais rápido e mais curto (no chrome 2x mais rápido);). Insuficiente!. unique3().

Neste ponto, eu estava pensando sobre o que eu realmente preciso em MINHA função única. não preciso da matriz antiga, quero uma função rápida. então eu usei 2 enquanto loops + emenda.unique4()

Inútil dizer que fiquei impressionado.

chrome: as 150.000 operações usuais por segundo saltaram para 1.800.000 operações por segundo.

ou seja: 80.000 op / s vs 3.500.000 op / s

ios: 18.000 op / s vs 170.000 op / s

safari: 80.000 op / s vs 6.000.000 op / s

Prova http://jsperf.com/wgu ou melhor, use console.time ... microtime ... qualquer que seja

unique5() é apenas para mostrar o que acontece se você deseja manter a matriz antiga.

Não use Array.prototypese você não sabe o que está fazendo. Eu apenas fiz um monte de cópia e passado. Use Object.defineProperty(Array.prototype,...,writable:false,enumerable:false})se você deseja criar um protótipo nativo. Exemplo: https://stackoverflow.com/a/20463021/2450730

Demo http://jsfiddle.net/46S7g/

NOTA: sua matriz antiga é destruída / torna-se única após esta operação.

se você não consegue ler o código acima, pergunte, leia um livro javascript ou aqui estão algumas explicações sobre códigos mais curtos. https://stackoverflow.com/a/21353032/2450730

alguns estão usando indexOf ... não ... http://jsperf.com/dgfgghfghfghghgfhgfhfghfhgfh

para matrizes vazias

!array.length||toUnique(array); 
cocco
fonte
4
testado no node.js, com uma matriz de 100k de URLs (strings). O resultado foi 2x mais lento que o underscore.js _.uniq ... embora um jsperf separado concorde com você ( jsperf.com/uniq-performance/5 ), estou desapontado :(
xShirase
3
você não está testando corretamente no jsperf ... no seu exemplo, você define a função toda vez ... mas as funções underscore.js já estão definidas .. isso penaliza minha função. também teste 3 e 4. Outra coisa que devo dizer é que, se você usar variáveis ​​mistas (strings e números), deverá substituir a [b]! == a [c] por a [b]! = a [c]
cocco
2
Não tenho certeza se o jsPerf foi corrigido, mas parece que a alternativa Reduzir (que, para mim, é mais fácil de entender) é 94% mais rápida que a sua solução. jsperf.com/reduce-for-distinct Editar: o seu é mais lento no chrome, o mesmo no Edge e mais rápido no Firefox.
Victor Ivens
3
Utilizável apenas com matrizes pequenas / porcentagem baixa de duplicatas . Cada vez que um objeto não exclusivo for encontrado, o mecanismo será forçado a deslocar todos os elementos restantes para a esquerda: 1) iterando sobre eles 2) lendo-os 3) gravando-os em um novo local. E então, ele também cria uma nova matriz com esse elemento emendado e o retorna como resultado ... O algoritmo é degradado rapidamente com matrizes maiores / duplicatas mais frequentes. Para matrizes de tamanho 1000 -> 10000-> 50000 e ~ 40% de duplicatas, o tempo médio gasto será de 2 a 775 a 19113 ms. (o tamanho é alterado para 1-> x10-> x5, o tempo é 1-> x10-> x25) no Chrome.
Ankhzet 31/05/19
1
Obrigado. Boa abordagem. Mas e quanto à ordem correta dos itens?
liberborn 14/09
28

Muitas das respostas aqui podem não ser úteis para iniciantes. Se a remoção de duping de uma matriz for difícil, eles realmente saberão sobre a cadeia de protótipos ou mesmo sobre o jQuery?

Nos navegadores modernos, uma solução limpa e simples é armazenar dados em um conjunto , projetado para ser uma lista de valores exclusivos.

const cars = ['Volvo', 'Jeep', 'Volvo', 'Lincoln', 'Lincoln', 'Ford'];
const uniqueCars = Array.from(new Set(cars));

O Array.fromé útil para converter parte de trás Set para uma matriz para que você tenha acesso fácil a todos os métodos impressionantes (características) que as matrizes têm. Existem também outras maneiras de fazer a mesma coisa. Mas você pode não precisar Array.from, pois o Sets possui muitos recursos úteis, como o forEach .

Se você precisar dar suporte ao Internet Explorer antigo e, portanto, não puder usar o Set, uma técnica simples é copiar itens para uma nova matriz e verificar com antecedência se eles já estão na nova matriz.

// Create a list of cars, with duplicates.
var cars = ['Volvo', 'Jeep', 'Volvo', 'Lincoln', 'Lincoln', 'Ford'];
// Create a list of unique cars, to put a car in if we haven't already.
var uniqueCars = [];

// Go through each car, one at a time.
cars.forEach(function (car) {
    // The code within the following block runs only if the
    // current car does NOT exist in the uniqueCars list
    // - a.k.a. prevent duplicates
    if (uniqueCars.indexOf(car) === -1) {
        // Since we now know we haven't seen this car before,
        // copy it to the end of the uniqueCars list.
        uniqueCars.push(car);
    }
});

Para tornar isso instantaneamente reutilizável, vamos colocá-lo em uma função.

function deduplicate(data) {
    if (data.length > 0) {
        var result = [];

        data.forEach(function (elem) {
            if (result.indexOf(elem) === -1) {
                result.push(elem);
            }
        });

        return result;
    }
}

Então, para se livrar das duplicatas, faríamos agora isso.

var uniqueCars = deduplicate(cars);

A deduplicate(cars)parte se torna a coisa que chamamos de resultado quando a função é concluída.

Apenas passe o nome de qualquer array que você desejar.

Seth Holladay
fonte
A propósito, usei uma matriz cheia de strings para mostrar que minha técnica é flexível. Funcionará corretamente para números.
Seth Holladay
20
["Defects", "Total", "Days", "City", "Defects"].reduce(function(prev, cur) {
  return (prev.indexOf(cur) < 0) ? prev.concat([cur]) : prev;
 }, []);

[0,1,2,0,3,2,1,5].reduce(function(prev, cur) {
  return (prev.indexOf(cur) < 0) ? prev.concat([cur]) : prev;
 }, []);
sergeyz
fonte
Você pode explicar por que você simplesmente não inseriu pusho elemento na matriz em vez de usá-lo concat? Eu tentei usar push e falhou. Estou procurando uma explicação.
Makenova
1
O motivo está no valor de retorno. concat retorna a matriz modificada (exatamente o que precisa ser retornado dentro da função de redução), enquanto push retorna um índice no qual você pode acessar o valor empurrado. Isso responde à sua pergunta?
14115 sergeyz
Aparentemente, esta solução é bastante rápida no Chrome (e no nó). jsperf.com/reduce-for-distinct Essa é a versão do ES6: [0,1,2,0,3,2,1,5].reduce((prev, cur) => ~prev.indexOf(cur) ? prev : prev.concat([cur]), []);
Victor Ivens
nota: esta implementação não é NaN-amigável
Alireza
19

Podemos fazer isso usando conjuntos ES6:

var duplicatedArray = [1, 2, 3, 4, 5, 1, 1, 1, 2, 3, 4];
var uniqueArray = Array.from(new Set(duplicatedArray));

console.log(uniqueArray);

// A saída será

uniqueArray = [1,2,3,4,5];
chinmayan
fonte
17

Este protótipo getUniquenão está totalmente correto, porque se eu tiver um Array como: ["1",1,2,3,4,1,"foo"]ele retornará ["1","2","3","4"]e "1"é string e 1é um número inteiro; Eles são diferentes.

Aqui está uma solução correta:

Array.prototype.unique = function(a){
    return function(){ return this.filter(a) }
}(function(a,b,c){ return c.indexOf(a,b+1) < 0 });

usando:

var foo;
foo = ["1",1,2,3,4,1,"foo"];
foo.unique();

O acima irá produzir ["1",2,3,4,1,"foo"].

Gabriel Silveira
fonte
1
Observe que $foo = 'bar'é a maneira do PHP de declarar variáveis. Funcionará em javascript, mas criará um global implícito e geralmente não deve ser feito.
Camilo Martin
@CamiloMartin desculpe, mas você está errado, $ foo é global porque o exemplo não está em um fechamento e ele está ausente da palavra-chave var. Nada a ver com o dólar jsfiddle.net/robaldred/L2MRb
Rob
8
@ Rob é exatamente o que estou dizendo, as pessoas do PHP pensam que $fooé a maneira de declarar variáveis ​​em javascript enquanto na verdade var fooé.
Camilo Martin
13

Sem estender Array.prototype (é considerado uma prática ruim) ou usar jquery / underscore, você pode simplesmente filtera matriz.

Mantendo a última ocorrência:

    function arrayLastUnique(array) {
        return array.filter(function (a, b, c) {
            // keeps last occurrence
            return c.indexOf(a, b + 1) < 0;
        });
    },

ou primeira ocorrência:

    function arrayFirstUnique(array) {
        return array.filter(function (a, b, c) {
            // keeps first occurrence
            return c.indexOf(a) === b;
        });
    },

Bem, é apenas javascript ECMAScript 5+, o que significa apenas IE9 +, mas é bom para o desenvolvimento em HTML / JS nativo (Windows Store App, Firefox OS, Sencha, Phonegap, Titanium, ...).

Cœur
fonte
2
O fato de ser o js 1.6 não significa que você não possa usar filter. Na página MDN, eles têm uma implementação para o Internet Explorer, quero dizer, navegadores mais antigos. Também: JS 1.6 refere-se apenas ao motor js do Firefox, mas a coisa certa a dizer que é que é ECMAScript 5.
Camilo Martin
@CamiloMartin Alterei 1.6 para ECMAScript5. Obrigado.
Cœur
13

Magia

a.filter(e=>!(t[e]=e in t)) 

O (n) desempenho ; assumimos que sua matriz está em ae t={}. Explicação aqui (+ Jeppe impr.)

Kamil Kiełczewski
fonte
14
este olhar tão super legal, que, sem uma explicação sólida i caiu você está bitcoins meu Vou quando eu executar este
Ondřej Żelazko
3
o que eu quis dizer é que você deve expandir sua resposta com alguma explicação e comentar a desconstrução dela. não espere que as pessoas encontrem respostas úteis como essa. (embora ele realmente parece arrefecer um provavelmente funciona)
Ondřej Żelazko
1
@Jeppe, quando executo sua melhoria, experimento um efeito aha (antes que eu não saiba que posso usar o inoperador fora da outra construção que não o forloop: P) - Obrigado - agradeço e darei +2 às suas outras boas respostas .
Kamil Kiełczewski 14/01/19
2
isso não cria uma variável global tque permanece viva após a filtragem ... ??
philipp
1
Esta é realmente uma solução complicada. Infelizmente, você deve declarar t fora da expressão, a menos que queira definir uma variável global e / ou confiar no seu bundler de módulo para ocultar a global. Parece chique, mas faça um favor a si mesmo (e à sua equipe de desenvolvimento) e, em seguida, escreva uma linha dupla: let t = {}; a.filter (e =>! (t [e] = e em t))
ford04
13
[...new Set(duplicates)]

Este é o mais simples e mencionado no MDN Web Docs .

const numbers = [2,3,4,4,2,3,3,4,4,5,5,6,6,7,5,32,3,4,5]
console.log([...new Set(numbers)]) // [2, 3, 4, 5, 6, 7, 32]
ifelse.codes
fonte
Embora esse código possa resolver a questão, incluir uma explicação de como e por que isso resolve o problema realmente ajudaria a melhorar a qualidade da sua postagem e provavelmente resultaria em mais votos positivos. Lembre-se de que você está respondendo à pergunta dos leitores no futuro, não apenas à pessoa que está perguntando agora. Edite sua resposta para adicionar uma explicação e forneça uma indicação de quais limitações e suposições se aplicam.
Id 19/07
11

Se você estiver usando a estrutura Prototype, não há necessidade de fazer loops 'for', você pode usar http://www.prototypejs.org/api/array/uniq assim:

var a = Array.uniq();  

O que produzirá uma matriz duplicada sem duplicatas. Me deparei com sua pergunta procurando um método para contar registros de matriz distintos, para depois

uniq ()

eu usei

Tamanho()

e houve o meu resultado simples. ps Desculpe se eu digitei algo errado

editar: se você quiser escapar de registros indefinidos, poderá adicionar

compactar()

antes, assim:

var a = Array.compact().uniq();  
Decebal
fonte
14
porque eu encontrei uma resposta melhor, eu penso sobre tópicos são para todas as pessoas e não apenas para quem pediu
Decebal
11

Agora, usando conjuntos, você pode remover duplicatas e convertê-las novamente na matriz.

var names = ["Mike","Matt","Nancy", "Matt","Adam","Jenny","Nancy","Carl"];

console.log([...new Set(names)])

Outra solução é usar classificação e filtro

var names = ["Mike","Matt","Nancy", "Matt","Adam","Jenny","Nancy","Carl"];
var namesSorted = names.sort();
const result = namesSorted.filter((e, i) => namesSorted[i] != namesSorted[i+1]);
console.log(result);

Krishnadas PC
fonte
10

Isso 0ocorre porque é um valor falso no JavaScript.

this[i] será falso se o valor da matriz for 0 ou qualquer outro valor falso.

Luca Matteis
fonte
Ahhhh, ok eu vejo agora ... mas haveria uma solução fácil para fazê-lo funcionar?
Mottie
10
Array.prototype.getUnique = function() {
    var o = {}, a = []
    for (var i = 0; i < this.length; i++) o[this[i]] = 1
    for (var e in o) a.push(e)
    return a
}
efémero
fonte
Acho que isso não funcionará se a matriz contiver objetos / matrizes e não tenho certeza se preservará o tipo de escalares.
Camilo Martin #
Sim, tudo fica estrito. Isso pode ser corrigido armazenando o valor original em ovez de apenas a 1, embora a comparação de igualdade ainda seja sequencial (embora, dentre todas as possíveis igualdade de Javascript, isso não pareça irracional).
ephemient 23/05
O Array.prototype poderia ser estendido apenas com métodos não enumeráveis ​​... Object.defineProperty (Array.prototype, "getUnique", {}) ... mas a idéia de usar um objeto auxiliar é muito boa
bortunac
10

Eu tive um problema um pouco diferente, em que precisei remover objetos com propriedades de identificação duplicadas de uma matriz. isso funcionou.

let objArr = [{
  id: '123'
}, {
  id: '123'
}, {
  id: '456'
}];

objArr = objArr.reduce((acc, cur) => [
  ...acc.filter((obj) => obj.id !== cur.id), cur
], []);

console.log(objArr);

shunryu111
fonte
9

A resposta mais simples é:

const array = [1, 1, 2, 2, 3, 5, 5, 2];
const uniqueArray = [...new Set(array)];
console.log(uniqueArray); // [1, 2, 3, 5]
Sukanta Bala
fonte
Simples, mas não funciona com uma variedade de objetos.
Thanwa Ch.
7

Não sei por que Gabriel Silveira escreveu a função dessa maneira, mas uma forma mais simples que funciona para mim da mesma forma e sem a minificação é:

Array.prototype.unique = function() {
  return this.filter(function(value, index, array) {
    return array.indexOf(value, index + 1) < 0;
  });
};

ou no CoffeeScript:

Array.prototype.unique = ->
  this.filter( (value, index, array) ->
    array.indexOf(value, index + 1) < 0
  )
Dan Fox
fonte
7

Se você concorda com dependências extras ou já possui uma das bibliotecas da sua base de código, pode remover duplicatas de uma matriz no local usando LoDash (ou Sublinhado).

Uso

Se você ainda não o possui na sua base de código, instale-o usando o npm:

npm install lodash

Em seguida, use-o da seguinte maneira:

import _ from 'lodash';
let idArray = _.uniq ([
    1,
    2,
    3,
    3,
    3
]);
console.dir(idArray);

Fora:

[ 1, 2, 3 ]
NikeshPathania
fonte
7

Isso foi muito respondido, mas não atendeu minha necessidade particular.

Muitas respostas são assim:

a.filter((item, pos, self) => self.indexOf(item) === pos);

Mas isso não funciona para matrizes de objetos complexos.

Digamos que temos uma matriz como esta:

const a = [
 { age: 4, name: 'fluffy' },
 { age: 5, name: 'spot' },
 { age: 2, name: 'fluffy' },
 { age: 3, name: 'toby' },
];

Se queremos os objetos com nomes exclusivos, devemos usar em array.prototype.findIndexvez de array.prototype.indexOf:

a.filter((item, pos, self) => self.findIndex(v => v.name === item.name) === pos);
Dave
fonte
Ótima solução, cuidado para que uma nova matriz retorne de uma função. (não se modifica)
Thanwa Ch.
6

De Shamasis Bhattacharya blog do (complexidade O (2n) tempo):

Array.prototype.unique = function() {
    var o = {}, i, l = this.length, r = [];
    for(i=0; i<l;i+=1) o[this[i]] = this[i];
    for(i in o) r.push(o[i]);
    return r;
};

No blog de Paul Irish : aprimoramento no JQuery .unique():

(function($){

    var _old = $.unique;

    $.unique = function(arr){

        // do the default behavior only if we got an array of elements
        if (!!arr[0].nodeType){
            return _old.apply(this,arguments);
        } else {
            // reduce the array to contain no dupes via grep/inArray
            return $.grep(arr,function(v,k){
                return $.inArray(v,arr) === k;
            });
        }
    };
})(jQuery);

// in use..
var arr = ['first',7,true,2,7,true,'last','last'];
$.unique(arr); // ["first", 7, true, 2, "last"]

var arr = [1,2,3,4,5,4,3,2,1];
$.unique(arr); // [1, 2, 3, 4, 5]
Z gelado
fonte
6

Localizando valores exclusivos de matriz em método simples

function arrUnique(a){
  var t = [];
  for(var x = 0; x < a.length; x++){
    if(t.indexOf(a[x]) == -1)t.push(a[x]);
  }
  return t;
}
arrUnique([1,4,2,7,1,5,9,2,4,7,2]) // [1, 4, 2, 7, 5, 9]
Saravanan Rajaraman
fonte
6

Parece que perdemos a resposta de Rafael , que permaneceu como resposta aceita por alguns anos. Essa foi (pelo menos em 2017) a solução com melhor desempenho se você não tiver uma matriz de tipo misto :

Array.prototype.getUnique = function(){
    var u = {}, a = [];
    for (var i = 0, l = this.length; i < l; ++i) {
        if (u.hasOwnProperty(this[i])) {
            continue;
        }
        a.push(this[i]);
        u[this[i]] = 1;
    }
return a;
}

Se você fazer tem uma matriz de tipo misto, você pode serializar a chave de hash:

Array.prototype.getUnique = function() {
    var hash = {}, result = [], key; 
    for ( var i = 0, l = this.length; i < l; ++i ) {
        key = JSON.stringify(this[i]);
        if ( !hash.hasOwnProperty(key) ) {
            hash[key] = true;
            result.push(this[i]);
        }
    }
    return result;
}
Jason
fonte
5

Para resolver o problema de maneira inversa, pode ser útil não duplicar enquanto você carrega sua matriz, da maneira que o objeto Set faria, mas ainda não está disponível em todos os navegadores. Ele economiza memória e é mais eficiente se você precisar examinar o conteúdo várias vezes.

Array.prototype.add = function (elem) {
   if (this.indexOf(elem) == -1) {
      this.push(elem);
   }
}

Amostra:

set = [];
[1,3,4,1,2,1,3,3,4,1].forEach(function(x) { set.add(x); });

Da-te set = [1,3,4,2]

Le Droid
fonte