Como obter valores exclusivos em uma matriz

167

Como posso obter uma lista de valores exclusivos em uma matriz? Eu sempre tenho que usar uma segunda matriz ou há algo semelhante ao hashmap de java em JavaScript?

Vou usar apenas JavaScript e jQuery . Nenhuma biblioteca adicional pode ser usada.

Astronauta
fonte
2
stackoverflow.com/questions/5381621/… - descreve exatamente o que você quer, eu acho?
SpaceBison
1
você está aberto a usar a underscore.jsbiblioteca?
Jakee
um hashmap java é basicamente o mesmo que um objeto javascript. sintaxe é { "chave": "Valor", "key2": "value2"}
Ian
As APIs de coleção JavaScript / TypeScript são terríveis em comparação com o Scala,list.toSet
Jordan Stewart

Respostas:

120

Desde que eu falei sobre isso nos comentários da resposta do @ Rocket, também posso fornecer um exemplo que não usa bibliotecas. Isso requer duas novas funções de protótipo containseunique

Array.prototype.contains = function(v) {
  for (var i = 0; i < this.length; i++) {
    if (this[i] === v) return true;
  }
  return false;
};

Array.prototype.unique = function() {
  var arr = [];
  for (var i = 0; i < this.length; i++) {
    if (!arr.contains(this[i])) {
      arr.push(this[i]);
    }
  }
  return arr;
}

var duplicates = [1, 3, 4, 2, 1, 2, 3, 8];
var uniques = duplicates.unique(); // result = [1,3,4,2,8]

console.log(uniques);

Para obter mais confiabilidade, você pode substituir containspelo indexOfcalço do MDN e verificar se cada elemento indexOfé igual a -1: documentation

jackwanders
fonte
1
~a.indexOf(b) === (a.indexOf(b) == -1)
Orwellophile
1
@ Lexynux: esse foi o tipo de resposta nerd de javascript-nerd que só deve ser usada por alguém que entenda o que isso significa e, possivelmente, nem isso. O que está dizendo é que escrever if (~a.indexOf(b)) ...é idêntico a escrever por mais tempo if (a.indexOf(b) == -1) ....
Orwellophile 18/07/2016
4
isso tem uma alta complexidade de tempo de execução (na pior das hipóteses: O (n ^ 2))
Rahul Arora
1
Esta é uma implementação realmente ineficiente. verificar a matriz de resultados para ver se ele já contém um item é horrível. uma abordagem melhor seria usar um objeto que rastreia as contagens ou, se você não quiser usar o armazenamento auxiliar, classifique-o primeiro em O (n log n) e depois em uma varredura linear e compare os elementos lado a lado
Isaiah Lee
1
Realmente precisamos da função "contém"?
Animesh Kumar
206

Ou para quem procura uma linha (simples e funcional), compatível com os navegadores atuais :

let a = ["1", "1", "2", "3", "3", "1"];
let unique = a.filter((item, i, ar) => ar.indexOf(item) === i);
console.log(unique);

Atualização 18-04-2017

Parece que 'Array.prototype.includes' agora tem amplo suporte nas versões mais recentes dos navegadores principais ( compatibilidade )

Atualização 29-07-2015:

Existem planos em andamento para os navegadores oferecerem suporte a um método padronizado 'Array.prototype.includes', que, embora não responda diretamente a essa pergunta; é frequentemente relacionado.

Uso:

["1", "1", "2", "3", "3", "1"].includes("2");     // true

Pollyfill ( suporte ao navegador , fonte do mozilla ):

// https://tc39.github.io/ecma262/#sec-array.prototype.includes
if (!Array.prototype.includes) {
  Object.defineProperty(Array.prototype, 'includes', {
    value: function(searchElement, fromIndex) {

      // 1. Let O be ? ToObject(this value).
      if (this == null) {
        throw new TypeError('"this" is null or not defined');
      }

      var o = Object(this);

      // 2. Let len be ? ToLength(? Get(O, "length")).
      var len = o.length >>> 0;

      // 3. If len is 0, return false.
      if (len === 0) {
        return false;
      }

      // 4. Let n be ? ToInteger(fromIndex).
      //    (If fromIndex is undefined, this step produces the value 0.)
      var n = fromIndex | 0;

      // 5. If n ≥ 0, then
      //  a. Let k be n.
      // 6. Else n < 0,
      //  a. Let k be len + n.
      //  b. If k < 0, let k be 0.
      var k = Math.max(n >= 0 ? n : len - Math.abs(n), 0);

      // 7. Repeat, while k < len
      while (k < len) {
        // a. Let elementK be the result of ? Get(O, ! ToString(k)).
        // b. If SameValueZero(searchElement, elementK) is true, return true.
        // c. Increase k by 1.
        // NOTE: === provides the correct "SameValueZero" comparison needed here.
        if (o[k] === searchElement) {
          return true;
        }
        k++;
      }

      // 8. Return false
      return false;
    }
  });
}
Josh Mc
fonte
É quase copiar e colar do kennebec, mas admitir que passar o array como parâmetro em vez de usar o fechamento provavelmente melhorará o desempenho.
- devo dizer que não conectei os pontos, simplesmente procurei em um único forro, parecia um post grande, então pulei, encontrei uma fonte alternativa e re-publiquei para que outros o encontrassem rapidamente. Dito isto, você está certo; praticamente o mesmo que kennebec.
Josh Mc
Agradável - não percebeu o filtro enviado na matriz como parâmetro e não queria trabalhar em um objeto externo. É exatamente isso que eu preciso - minha versão do javascript (versão mais antiga do xerces) não terá as novidades por um tempo.
precisa saber é o seguinte
1
@GerardONeill sim, algumas situações são muito vitais, por exemplo, se estiver funcionalmente encadeado e você desejar acessar uma matriz à qual não foi atribuída uma variável como .map (...). Filter (...)
Josh Mc
@ Jos Mc, você poderia de alguma forma usar 'inclui' no método 'único'?
precisa saber é
143

Aqui está uma solução muito mais limpa para o ES6 que eu vejo que não está incluída aqui. Ele usa o conjunto e o operador de propagação :...

var a = [1, 1, 2];

[... new Set(a)]

Que retorna [1, 2]

Charles Clayton
fonte
1
Isso é tão inteligente!
Josh Mc
1
Agora, ESTA é um one-liner!
Mac
11
No TypeScript você deve usar, Array.from(... new Set(a))pois Set não pode ser convertido implicitamente em um tipo de matriz. Apenas um alerta!
Zachscs
4
@ Zachscs, recebi um erro de compilação quando tentei isso. Você quis dizer apenas Array.from(new Set(a))? Isso parece funcionar.
precisa saber é o seguinte
72

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
4
não sei por que isso foi rejeitado. Pode ser um pouco obscuro a princípio, e talvez classificado como 'inteligente' e não pragmático de ler, mas é declarativo, não destrutivo e conciso, onde falta a maioria das outras respostas.
Larry
1
@ Larry, isto foi rejeitado porque exatamente a mesma resposta foi fornecida anos antes desta.
Alex Okrushko
@AlexOkrushko bastante justo - perdeu essa resposta por causa da forma como foi formatado
Larry
7
Tudo é uma one-liner se você colocar tudo em uma linha :-)
Gary McGill
Pode ser bom reforçar a a.indexOf(x) === inota de igualdade dos três sinais de igualdade.
treejanitor
20

Usando o EcmaScript 2016, você pode simplesmente fazer assim.

 var arr = ["a", "a", "b"];
 var uniqueArray = Array.from(new Set(arr)); // Unique Array ['a', 'b'];

Os conjuntos são sempre exclusivos e, com Array.from()o uso, você pode converter um conjunto em uma matriz. Para referência, dê uma olhada nas documentações.

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects /Conjunto

Adeel Imran
fonte
1
A é a resposta que você deve usar. indexOf()as respostas são terríveis porque são O (N ^ 2). As respostas de propagação estão ok, mas não funcionam para matrizes grandes. Essa é a melhor abordagem.
Timmmm
20

Agora no ES6, podemos usar a recém-introduzida função ES6

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

OR by Array espalhar sintaxe em iterables

let items = [1,1,1,1,3,4,5,2,23,1,4,4,4,2,2,2]; 
let uniqueItems = [...new Set(items)];

Retornará o resultado exclusivo.

[1, 3, 4, 5, 2, 23]
Rohit.007
fonte
1
Solução mais limpa!
Dimitris Filippou
Este é o caminho a percorrer, quando em um ambiente JS que suportes new Setcomo este (como moderno angular / typescript)
Don Cheadle
1
Vale a pena notar que você também pode usar a sintaxe propagação matriz em iterables como Set e Map: let items = [1,1,1,1,3,4,5,2,23,1,4,4,4,2,2,2]; let uniqueItems = [...new Set(items)];
jacobedawson
Eu amo essas respostas ES6!
Glen Thompson
1
Isso é idêntico à resposta de @Adeel Imran. Por favor, não faça respostas exatamente iguais às respostas existentes.
Timmmm
16

Se você quiser deixar a matriz original intacta,

você precisa de uma segunda matriz para conter os elementos uniqe do primeiro

A maioria dos navegadores possui Array.prototype.filter:

var unique= array1.filter(function(itm, i){
    return array1.indexOf(itm)== i; 
    // returns true for only the first instance of itm
});


//if you need a 'shim':
Array.prototype.filter= Array.prototype.filter || function(fun, scope){
    var T= this, A= [], i= 0, itm, L= T.length;
    if(typeof fun== 'function'){
        while(i<L){
            if(i in T){
                itm= T[i];
                if(fun.call(scope, itm, i, T)) A[A.length]= itm;
            }
            ++i;
        }
    }
    return A;
}
 Array.prototype.indexOf= Array.prototype.indexOf || function(what, i){
        if(!i || typeof i!= 'number') i= 0;
        var L= this.length;
        while(i<L){
            if(this[i]=== what) return i;
            ++i;
        }
        return -1;
    }
Kennebec
fonte
14

Hoje em dia, você pode usar o tipo de dados Set do ES6 para converter sua matriz em um conjunto exclusivo. Então, se você precisar usar métodos de matriz, poderá transformá-lo novamente em uma matriz:

var arr = ["a", "a", "b"];
var uniqueSet = new Set(arr); // {"a", "b"}
var uniqueArr = Array.from(uniqueSet); // ["a", "b"]
//Then continue to use array methods:
uniqueArr.join(", "); // "a, b"
Fawntasia
fonte
1
Neat, seria bom para ver alguns números de perfomance, acho conversão thosesets em particular se eles são muito dinâmico e grande poderia ser uma hickup desempenho, única maneira de saber é testar :)
Astronauta
2
Se você estiver usando uma transpiler ou estão em um ambiente que suporta, você pode fazer a mesma coisa de forma mais concisa como:var uniqueArr = [...new Set(arr)]; // ["a", "b"]
Stenerson
Hey @Astronaut fez alguns testes sobre desempenho, como você disse?
Alexventuraio
8

Não é nativo em Javascript, mas muitas bibliotecas têm esse método.

Das Underscore.js _.uniq(array)( ligação ) funciona muito bem ( fonte ).

Calvin
fonte
Obrigado por compartilhar! Essa função leva o iterador e o contexto junto com o array como uma lista de argumentos da v1.4.3.
Kunj
6

Usando jQuery, aqui está uma função exclusiva de matriz que eu criei:

Array.prototype.unique = function () {
    var arr = this;
    return $.grep(arr, function (v, i) {
        return $.inArray(v, arr) === i;
    });
}

console.log([1,2,3,1,2,3].unique()); // [1,2,3]
Foguete Hazmat
fonte
5
se você usar o jQuery dentro do protótipo de um objeto javascript principal, não seria melhor escrever uma função jQuery, como $.uniqueArray(arr)? Incorporação referências a jQuery dentro Arrayprotótipo 's parece questionável
jackwanders
1
@jackwanders: O que há de tão questionável nisso? Se você tiver o jQuery na página, vamos usá-lo.
Foguete Hazmat
Apenas que a nova função exclusiva que você escreveu agora depende do jQuery; você não pode movê-lo para um novo site ou aplicativo sem garantir que o jQuery esteja em uso lá.
jackwanders
2
esse era o meu ponto; se você usar o jQuery, faça com que a função faça parte do jQuery. Se eu pretendesse estender o protótipo de um objeto principal, manteria o javascript central, apenas para manter as coisas reutilizáveis. Se alguém estiver olhando seu código, é óbvio que $.uniqueArraydepende do jQuery; menos óbvio que Array.prototype.uniqueé também.
jackwanders
1
@jackwanders: eu acho. Eu uso isso no meu código, como sempre uso jQuery, e gosto de estender prototypes. Mas, eu entendo o seu ponto agora. Vou deixar isso aqui de qualquer maneira.
Foguete Hazmat
6

Solução curta e doce usando segunda matriz;

var axes2=[1,4,5,2,3,1,2,3,4,5,1,3,4];

    var distinct_axes2=[];

    for(var i=0;i<axes2.length;i++)
        {
        var str=axes2[i];
        if(distinct_axes2.indexOf(str)==-1)
            {
            distinct_axes2.push(str);
            }
        }
    console.log("distinct_axes2 : "+distinct_axes2); // distinct_axes2 : 1,4,5,2,3
Pradip Shenolkar
fonte
Curto? e doce? Você já olhou para as principais soluções?
Alex Okrushko
5

Rápido, compacto, sem loops aninhados, funciona com qualquer objeto, não apenas com strings e números, assume um predicado e apenas 5 linhas de código !!

function findUnique(arr, predicate) {
  var found = {};
  arr.forEach(d => {
    found[predicate(d)] = d;
  });
  return Object.keys(found).map(key => found[key]); 
}

Exemplo: para encontrar itens exclusivos por tipo:

var things = [
  { name: 'charm', type: 'quark'},
  { name: 'strange', type: 'quark'},
  { name: 'proton', type: 'boson'},
];

var result = findUnique(things, d => d.type);
//  [
//    { name: 'charm', type: 'quark'},
//    { name: 'proton', type: 'boson'}
//  ] 

Se você deseja que ele encontre o primeiro item exclusivo em vez do último, adicione um check found.hasOwnPropery () lá.

user1618323
fonte
4

Você só precisa do vanilla JS para encontrar itens únicos com Array.some e Array.reduce. Com a sintaxe do ES2015, são apenas 62 caracteres.

a.reduce((c, v) => b.some(w => w === v) ? c : c.concat(v)), b)

Array.some e Array.reduce são suportados no IE9 + e em outros navegadores. Basta alterar as funções de seta gorda para obter funções regulares para suporte em navegadores que não suportam a sintaxe do ES2015.

var a = [1,2,3];
var b = [4,5,6];
// .reduce can return a subset or superset
var uniques = a.reduce(function(c, v){
    // .some stops on the first time the function returns true                
    return (b.some(function(w){ return w === v; }) ?  
      // if there's a match, return the array "c"
      c :     
      // if there's no match, then add to the end and return the entire array                                        
      c.concat(v)}),                                  
  // the second param in .reduce is the starting variable. This is will be "c" the first time it runs.
  b);                                                 

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/some https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects / Matriz / Reduzir

filhotes
fonte
4

A maioria das soluções acima tem uma alta complexidade de tempo de execução.

Aqui está a solução que usa reducee pode fazer o trabalho em O (n) tempo.

Array.prototype.unique = Array.prototype.unique || function() {
        var arr = [];
	this.reduce(function (hash, num) {
		if(typeof hash[num] === 'undefined') {
			hash[num] = 1; 
			arr.push(num);
		}
		return hash;
	}, {});
	return arr;
}
    
var myArr = [3,1,2,3,3,3];
console.log(myArr.unique()); //[3,1,2];

Nota:

Esta solução não depende de redução. A idéia é criar um mapa de objetos e inserir mapas exclusivos na matriz.

Rahul Arora
fonte
1

ES6 maneira:

const uniq = (arr) => (arr.filter((item, index, arry) => (arry.indexOf(item) === index)));
Ashwin Aggarwal
fonte
1

você pode usar,

let arr1 = [1,2,1,3];
let arr2 = [2,3,4,5,1,2,3];

let arr3 = [...new Set([...arr1,...arr2])];

isso lhe dará elementos únicos,

**> mas há um problema,

para este "1" e 1 e são elementos diff, **

A segunda opção é usar o método de filtro na matriz.

niranjan harpale
fonte
1

Você pode inserir a matriz com duplicatas e o método abaixo retornará a matriz com elementos exclusivos.

function getUniqueArray(array){
    var uniqueArray = [];
    if (array.length > 0) {
       uniqueArray[0] = array[0];
    }
    for(var i = 0; i < array.length; i++){
        var isExist = false;
        for(var j = 0; j < uniqueArray.length; j++){
            if(array[i] == uniqueArray[j]){
                isExist = true;
                break;
            }
            else{
                isExist = false;
            }
        }
        if(isExist == false){
            uniqueArray[uniqueArray.length] = array[i];
        }
    }
    return uniqueArray;
}
Muhammad Haroon Iqbal
fonte
0

O único problema com as soluções dadas até agora é a eficiência. Se você está preocupado com isso (e provavelmente deveria), precisa evitar loops aninhados: para * for, filter * indexOf, grep * inArray, todos iteram a matriz várias vezes. Você pode implementar um único loop com soluções como esta ou esta

Jesús Carrera
fonte
0
Array.prototype.unique = function () {
    var dictionary = {};
    var uniqueValues = [];
    for (var i = 0; i < this.length; i++) {
        if (dictionary[this[i]] == undefined){
            dictionary[this[i]] = i;
            uniqueValues.push(this[i]);
        }
    }
    return uniqueValues; 
}
Hexer338
fonte
0

Eu tentei esse problema em JS puro. Eu segui as etapas a seguir 1. Classifique a matriz fornecida, 2. faça um loop na matriz classificada, 3. Verifique o valor anterior e o próximo valor com o valor atual

// JS
var inpArr = [1, 5, 5, 4, 3, 3, 2, 2, 2,2, 100, 100, -1];

//sort the given array
inpArr.sort(function(a, b){
    return a-b;
});

var finalArr = [];
//loop through the inpArr
for(var i=0; i<inpArr.length; i++){
    //check previous and next value 
  if(inpArr[i-1]!=inpArr[i] && inpArr[i] != inpArr[i+1]){
        finalArr.push(inpArr[i]);
  }
}
console.log(finalArr);

Demo

Sanketh Nayak
fonte
0
function findUniques(arr){
  let uniques = []
  arr.forEach(n => {
    if(!uniques.includes(n)){
      uniques.push(n)
    }       
  })
  return uniques
}

let arr = ["3", "3", "4", "4", "4", "5", "7", "9", "b", "d", "e", "f", "h", "q", "r", "t", "t"]

findUniques(arr)
// ["3", "4", "5", "7", "9", "b", "d", "e", "f", "h", "q", "r", "t"]
smithWEBtek
fonte
0

Tendo em mente que indexOfretornará a primeira ocorrência de um elemento, você pode fazer algo assim:

Array.prototype.unique = function(){
        var self = this;
        return this.filter(function(elem, index){
            return self.indexOf(elem) === index;
        })
    }
Nikksan
fonte
0

Outro pensamento sobre esta questão. Aqui está o que eu fiz para conseguir isso com menos código.

var distinctMap = {};
var testArray = ['John', 'John', 'Jason', 'Jason'];
for (var i = 0; i < testArray.length; i++) {
  var value = testArray[i];
  distinctMap[value] = '';
};
var unique_values = Object.keys(distinctMap);

console.log(unique_values);

Xing-Wei Lin
fonte
0

function findUnique(arr) {
  var result = [];
  arr.forEach(function(d) {
    if (result.indexOf(d) === -1)
      result.push(d);
  });
  return result;
}

var unique = findUnique([1, 2, 3, 1, 2, 1, 4]); // [1,2,3,4]
console.log(unique);

user1739150
fonte
0

Aqui está uma abordagem com equalsfunção personalizável que pode ser usada para primitivos e também para objetos personalizados:

Array.prototype.pushUnique = function(element, equalsPredicate = (l, r) => l == r) {
    let res = !this.find(item => equalsPredicate(item, element))
    if(res){
        this.push(element)
    }
    return res
}

uso:

//with custom equals for objects
myArrayWithObjects.pushUnique(myObject, (left, right) => left.id == right.id)

//with default equals for primitives
myArrayWithPrimitives.pushUnique(somePrimitive)
vírus
fonte
0

Minha resposta usa Array.filtere Array.indexOfmétodos para obter valores únicos

array.filter((value, index, self)=>self.indexOf(value)===index)

Eu vi essa abordagem em um site, mas o código deles é diferente do que parece aqui. Simplifiquei o código para uma linha e publiquei aqui para que alguém se beneficie dele

Nota: Minha abordagem é semelhante ou igual à liner postada por Josh. Estou deixando aqui, pois os nomes das variáveis ​​são auto-explicativos no meu código.

Anand Raj
fonte
-1

Eu só estava pensando se podemos usar a pesquisa linear para eliminar as duplicatas:

JavaScript:
function getUniqueRadios() {

var x=document.getElementById("QnA");
var ansArray = new Array();
var prev;


for (var i=0;i<x.length;i++)
  {
    // Check for unique radio button group
    if (x.elements[i].type == "radio")
    {
            // For the first element prev will be null, hence push it into array and set the prev var.
            if (prev == null)
            {
                prev = x.elements[i].name;
                ansArray.push(x.elements[i].name);
            } else {
                   // We will only push the next radio element if its not identical to previous.
                   if (prev != x.elements[i].name)
                   {
                       prev = x.elements[i].name;
                       ansArray.push(x.elements[i].name);
                   }
            }
    }

  }

   alert(ansArray);

}

HTML:

<body>

<form name="QnA" action="" method='post' ">

<input type="radio"  name="g1" value="ANSTYPE1"> good </input>
<input type="radio" name="g1" value="ANSTYPE2"> avg </input>

<input type="radio"  name="g2" value="ANSTYPE3"> Type1 </input>
<input type="radio" name="g2" value="ANSTYPE2"> Type2 </input>


<input type="submit" value='SUBMIT' onClick="javascript:getUniqueRadios()"></input>


</form>
</body>
Suresh
fonte
-1

Aqui está a solução única para o problema:

var seriesValues = [120, 120, 120, 120];
seriesValues = seriesValues.filter((value, index, seriesValues) => (seriesValues.slice(0, index)).indexOf(value) === -1);
console.log(seriesValues);

Copie e cole isso no console do navegador e obtenha os resultados, yo :-)

Manish Kumar
fonte
-2

Eu tenho a função JQuery Unique embutida.

uniqueValues= jQuery.unique( duplicateValues );

Para mais, consulte a documentação da API do jquery.

http://api.jquery.com/jquery.unique/

Mitul Maheshwari
fonte
8
Observe que isso funciona apenas em matrizes de elementos DOM, não em cadeias ou números. - citação da documentação.
Mc