Eu tenho uma matriz JavaScript muito simples que pode ou não conter duplicatas.
var names = ["Mike","Matt","Nancy","Adam","Jenny","Nancy","Carl"];
Eu preciso remover as duplicatas e colocar os valores únicos em uma nova matriz.
Eu poderia apontar para todos os códigos que eu'tentei mas acho que'é inútil porque eles não'não funcionam. Eu também aceito soluções jQuery.
Usando o Conjunto construtor e a sintaxe de propagação:
uniq = [...new Set(array)];
uniqueArray = a.filter(function(item, pos) {
return a.indexOf(item) == pos;
})
Basicamente, nós iteramos sobre a matriz e, para cada elemento, verificamos se a primeira posição deste elemento na matriz é igual à posição atual. Obviamente, estas duas posições são diferentes para elementos duplicados. Usando o 3º (" este array") parâmetro do filtro de retorno podemos evitar o fechamento da variável do array:
uniqueArray = a.filter(function(item, pos, self) {
return self.indexOf(item) == pos;
})
function uniq(a) {
var seen = {};
return a.filter(function(item) {
return seen.hasOwnProperty(item) ? false : (seen[item] = true);
});
}
É assim que se faz normalmente's. A idéia é colocar cada elemento em uma hashtable e depois verificar sua presença instantaneamente. Isto nos dá tempo linear, mas tem pelo menos dois inconvenientes:
uniq([1,"1"])
irá retornar apenas [1]
uniq([{foo:1},{foo:2}])
irá retornar apenas [{foo:1}]
.
Dito isto, se seus arrays contêm apenas primitivos e você não't se importa com os tipos (por exemplo it's sempre números), esta solução é ótima.
O melhor de dois mundosUma solução universal combina ambas as abordagens: usa pesquisas de hash para primitivas e busca linear de objetos.
function uniq(a) {
var prims = {"boolean":{}, "number":{}, "string":{}}, objs = [];
return a.filter(function(item) {
var type = typeof item;
if(type in prims)
return prims[type].hasOwnProperty(item) ? false : (prims[type][item] = true);
else
return objs.indexOf(item) >= 0 ? false : objs.push(item);
});
}
Outra opção é ordenar primeiro o array, e depois remover cada elemento igual ao anterior:
function uniq(a) {
return a.sort().filter(function(item, pos, ary) {
return !pos || item != ary[pos - 1];
})
}
sort
). Adicionalmente, nós silenciosamente mudamos o array original como um efeito colateral - não é bom! Entretanto, se a sua entrada já estiver ordenada, este é o caminho a seguir (basta remover a sort
do acima).
Único por...Às vezes, ele's deseja unificar uma lista baseada em alguns critérios que não apenas igualdade, por exemplo, para filtrar objetos que são diferentes, mas compartilham algumas propriedades. Isto pode ser feito elegantemente, passando uma chamada de retorno. Este "key" callback é aplicado a cada elemento, e elementos com "keys" são removidos. Como se espera que a key
devolva uma tabela de hash primitiva, a tabela de hash funcionará bem aqui:
function uniqBy(a, key) {
var seen = {};
return a.filter(function(item) {
var k = key(item);
return seen.hasOwnProperty(k) ? false : (seen[k] = true);
})
}
Uma key()
particularmente útil é `JSON.stringify' que removerá objetos que são fisicamente diferentes, mas "look" o mesmo:
a = [[1,2,3], [4,5,6], [1,2,3]]
b = uniqBy(a, JSON.stringify)
console.log(b) // [[1,2,3], [4,5,6]]
Se a "chave" não for primitiva, você tem que recorrer à busca linear:
function uniqBy(a, key) {
var index = [];
return a.filter(function (item) {
var k = key(item);
return index.indexOf(k) >= 0 ? false : index.push(k);
});
}
No ES6 você pode utilizar um Set
:
function uniqBy(a, key) {
let seen = new Set();
return a.filter(item => {
let k = key(item);
return seen.has(k) ? false : seen.add(k);
});
}
ou um "Mapa":
function uniqBy(a, key) {
return [
...new Map(
a.map(x => [key(x), x])
).values()
]
}
Ao remover objetos por uma chave, você pode querer manter o primeiro do "igual" objetos ou o último.
Utilize a variante Set
acima para manter o primeiro, e o Map
para manter o último:
function uniqByKeepFirst(a, key) {
let seen = new Set();
return a.filter(item => {
let k = key(item);
return seen.has(k) ? false : seen.add(k);
});
}
function uniqByKeepLast(a, key) {
return [
...new Map(
a.map(x => [key(x), x])
).values()
]
}
//
data = [
{a:1, u:1},
{a:2, u:2},
{a:3, u:3},
{a:4, u:1},
{a:5, u:2},
{a:6, u:3},
];
console.log(uniqByKeepFirst(data, it => it.u))
console.log(uniqByKeepLast(data, it => it.u))
Ambos underscore e Lo-Dash fornecem métodos uniq
. Seus algoritmos são basicamente similares ao primeiro trecho acima e se resumem a isso:
var result = [];
a.forEach(function(item) {
if(result.indexOf(item) < 0) {
result.push(item);
}
});
Isso é quadrático, mas há bons acessórios adicionais, como o embrulho de "indexOf" nativo, a capacidade de unificar por uma chave ("alfabetizado" em sua linguagem), e otimizações para matrizes já classificadas. Se você'está usando jQuery e can'não suporta nada sem um dólar antes, é assim:
$.uniqArray = function(a) {
return $.grep(a, function(item, pos) {
return $.inArray(item, a) === pos;
});
}
As chamadas de funções são caras em JavaScript, portanto as soluções acima, por mais concisas que sejam, não são particularmente eficientes. Para um desempenho máximo, substitua o filtro
por um laço e livre-se de outras chamadas de função:
function uniq_fast(a) {
var seen = {};
var out = [];
var len = a.length;
var j = 0;
for(var i = 0; i < len; i++) {
var item = a[i];
if(seen[item] !== 1) {
seen[item] = 1;
out[j++] = item;
}
}
return out;
}
Este pedaço de código feio faz o mesmo que o snippet #3 acima, mas uma ordem de magnitude mais rápida (a partir de 2017 it's apenas duas vezes mais rápido - as pessoas do núcleo do JS estão fazendo um ótimo trabalho!)
function uniq(a) {
var seen = {};
return a.filter(function(item) {
return seen.hasOwnProperty(item) ? false : (seen[item] = true);
});
}
function uniq_fast(a) {
var seen = {};
var out = [];
var len = a.length;
var j = 0;
for(var i = 0; i < len; i++) {
var item = a[i];
if(seen[item] !== 1) {
seen[item] = 1;
out[j++] = item;
}
}
return out;
}
/////
var r = [0,1,2,3,4,5,6,7,8,9],
a = [],
LEN = 1000,
LOOPS = 1000;
while(LEN--)
a = a.concat(r);
var d = new Date();
for(var i = 0; i < LOOPS; i++)
uniq(a);
document.write('<br>uniq, ms/loop: ' + (new Date() - d)/LOOPS)
var d = new Date();
for(var i = 0; i < LOOPS; i++)
uniq_fast(a);
document.write('<br>uniq_fast, ms/loop: ' + (new Date() - d)/LOOPS)
ES6 fornece o objeto Set, o que torna as coisas muito mais fáceis:
function uniq(a) {
return Array.from(new Set(a));
}
ou
let uniq = a => [...new Set(a)];
A "lazy", versão baseada em gerador do uniq
pode ser construída na mesma base:
function* uniqIter(a) {
let seen = new Set();
for (let x of a) {
if (!seen.has(x)) {
seen.add(x);
yield x;
}
}
}
// example:
function* randomsBelow(limit) {
while (1)
yield Math.floor(Math.random() * limit);
}
// note that randomsBelow is endless
count = 20;
limit = 30;
for (let r of uniqIter(randomsBelow(limit))) {
console.log(r);
if (--count === 0)
break
}
// exercise for the reader: what happens if we set `limit` less than `count` and why
Rápido e sujo usando jQuery:
var names = ["Mike","Matt","Nancy","Adam","Jenny","Nancy","Carl"];
var uniqueNames = [];
$.each(names, function(i, el){
if($.inArray(el, uniqueNames) === -1) uniqueNames.push(el);
});
**Vanilla JS: Remover duplicados usando um Objecto como um Set***
Você pode sempre tentar colocá-lo em um objeto, e depois fazer iterações através de suas chaves:
function remove_duplicates(arr) {
var obj = {};
var ret_arr = [];
for (var i = 0; i < arr.length; i++) {
obj[arr[i]] = true;
}
for (var key in obj) {
ret_arr.push(key);
}
return ret_arr;
}
Vanilla JS: Remover duplicados rastreando valores já vistos (order-safe)
Ou, para uma versão segura, use um objeto para armazenar todos os valores anteriormente vistos, e verifique os valores em relação a ele antes de adicionar a um array.
function remove_duplicates_safe(arr) {
var seen = {};
var ret_arr = [];
for (var i = 0; i < arr.length; i++) {
if (!(arr[i] in seen)) {
ret_arr.push(arr[i]);
seen[arr[i]] = true;
}
}
return ret_arr;
}
ECMAScript 6: Use a nova estrutura de dados Set (order-safe)
ECMAScript 6 adiciona a nova estrutura de dados Set
, que lhe permite armazenar valores de qualquer tipo. O `Set.values' retorna elementos em ordem de inserção.
function remove_duplicates_es6(arr) {
let s = new Set(arr);
let it = s.values();
return Array.from(it);
}
Exemplo de utilização:
a = ["Mike","Matt","Nancy","Adam","Jenny","Nancy","Carl"];
b = remove_duplicates(a);
// b:
// ["Adam", "Carl", "Jenny", "Matt", "Mike", "Nancy"]
c = remove_duplicates_safe(a);
// c:
// ["Mike", "Matt", "Nancy", "Adam", "Jenny", "Carl"]
d = remove_duplicates_es6(a);
// d:
// ["Mike", "Matt", "Nancy", "Adam", "Jenny", "Carl"]