Saltar al contenido

Eliminar valores duplicados de JS array

Te damos la bienvenida a nuestra página web, ahora vas a encontrar la resolución a lo que estabas buscando.

Solución:

TL; DR

Usando el constructor Set y la sintaxis de propagación:

uniq = [...new Set(array)];

“Inteligente” pero ingenua

uniqueArray = a.filter(function(item, pos) 
    return a.indexOf(item) == pos;
)

Básicamente, iteramos sobre el array y, para cada elemento, comprobar si la primera posición de este elemento en el array es igual a la posición actual. Obviamente, estas dos posiciones son diferentes para elementos duplicados.

Usando el tercero (“este array”) parámetro de la devolución de llamada del filtro podemos evitar un cierre de la array variable:

uniqueArray = a.filter(function(item, pos, self) 
    return self.indexOf(item) == pos;
)

Aunque conciso, este algoritmo no es particularmente eficiente para arreglos grandes (tiempo cuadrático).

Hashtables al rescate

function uniq(a) 
    var seen = ;
    return a.filter(function(item) 
        return seen.hasOwnProperty(item) ? false : (seen[item] = true);
    );

Así es como suele hacerse. La idea es colocar cada elemento en una tabla hash y luego verificar su presencia al instante. Esto nos da un tiempo lineal, pero tiene al menos dos inconvenientes:

  • desde hash keys solo pueden ser cadenas o símbolos en JavaScript, este código no distingue números y “cadenas numéricas”. Es decir, uniq([1,"1"]) Regresará solo [1]
  • por la misma razón, todos los objetos se considerarán iguales: uniq([foo:1,foo:2]) Regresará solo [foo:1].

Dicho esto, si sus matrices contienen solo primitivas y no le importan los tipos (por ejemplo, siempre son números), esta solución es óptima.

Lo mejor de dos mundos

Una solución universal combina ambos enfoques: utiliza búsquedas hash para primitivas y búsqueda lineal 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);
    );

ordenar | uniq

Otra opción es ordenar los array primero, y luego elimine cada elemento igual al anterior:

function uniq(a) 
    return a.sort().filter(function(item, pos, ary)  item != ary[pos - 1];
    );

Nuevamente, esto no funciona con objetos (porque todos los objetos son iguales para sort). Además, cambiamos silenciosamente el original array como efecto secundario, ¡no es bueno! Sin embargo, si su entrada ya está ordenada, este es el camino a seguir (simplemente elimine sort de lo anterior).

Único por …

A veces se desea unificar una lista en función de algunos criterios distintos de la igualdad, por ejemplo, para filtrar objetos que son diferentes, pero comparten alguna propiedad. Esto se puede hacer con elegancia pasando una devolución de llamada. Esta “key”la devolución de llamada se aplica a cada elemento y a los elementos con igual”keys”se eliminan. Dado que key se espera que devuelva una tabla hash primitiva, funcionará bien aquí:

function uniqBy(a, key) 
    var seen = ;
    return a.filter(function(item) 
        var k = key(item);
        return seen.hasOwnProperty(k) ? false : (seen[k] = true);
    )

Particularmente útil key() es JSON.stringify que eliminará los objetos que son físicamente diferentes, pero “se ven” iguales:

a = [[1,2,3], [4,5,6], [1,2,3]]
b = uniqBy(a, JSON.stringify)
console.log(b) // [[1,2,3], [4,5,6]]

Si el key no es primitivo, hay que recurrir a la búsqueda lineal:

function uniqBy(a, key) 
    var index = [];
    return a.filter(function (item) 
        var k = key(item);
        return index.indexOf(k) >= 0 ? false : index.push(k);
    );

En ES6 puede utilizar un 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);
    );

o un Map:

function uniqBy(a, key) 
    return [
        ...new Map(
            a.map(x => [key(x), x])
        ).values()
    ]

que ambos también funcionan con no primitivos keys.

¿Primero o último?

Al retirar objetos con un key, es posible que desee conservar el primero de los objetos “iguales” o el último.

Utilizar el Set variante anterior para mantener la primera, y la Map para guardar lo ú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))

Bibliotecas

Tanto el subrayado como Lo-Dash proporcionan uniq métodos. Sus algoritmos son básicamente similares al primer fragmento anterior y se reducen a esto:

var result = [];
a.forEach(function(item) 
     if(result.indexOf(item) < 0) 
         result.push(item);
     
);

Esto es cuadrático, pero hay buenos beneficios adicionales, como envolver archivos nativos indexOf, capacidad de unificar por un key (iteratee en su lenguaje) y optimizaciones para matrices ya ordenadas.

Si está usando jQuery y no puede soportar nada sin un dólar antes, es así:

  $.uniqArray = function(a) 
        return $.grep(a, function(item, pos) 
            return $.inArray(item, a) === pos;
        );
  

que es, de nuevo, una variación del primer fragmento.

Rendimiento

Las llamadas a funciones son caras en JavaScript, por lo que las soluciones anteriores, por concisas que sean, no son particularmente eficientes. Para un rendimiento máximo, reemplace filter con un bucle y deshacerse de otras llamadas a funciones:

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 fragmento de código feo hace lo mismo que el fragmento n. ° 3 anterior, pero un orden de magnitud más rápido (A partir de 2017, es solo el doble de rápido: ¡la gente del núcleo de JS está haciendo un gran trabajo!)

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('
uniq, ms/loop: ' + (new Date() - d)/LOOPS) var d = new Date(); for(var i = 0; i < LOOPS; i++) uniq_fast(a); document.write('
uniq_fast, ms/loop: ' + (new Date() - d)/LOOPS)

ES6

ES6 proporciona el objeto Set, que facilita mucho las cosas:

function uniq(a) 
   return Array.from(new Set(a));

o

let uniq = a => [...new Set(a)];

Tenga en cuenta que, a diferencia de Python, los conjuntos de ES6 se iteran en orden de inserción, por lo que este código conserva el orden del original. array.

Sin embargo, si necesita un array con elementos únicos, ¿por qué no utilizar conjuntos desde el principio?

Generadores

Una versión "perezosa" basada en un generador de uniq se puede construir sobre la misma base:

  • tomar el siguiente valor del argumento
  • si ya se ha visto, omítalo
  • de lo contrario, ceda y agréguelo al conjunto de valores ya vistos
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 y sucio 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);
);

Me cansé de ver todos los malos ejemplos con for-loops o jQuery. Javascript tiene las herramientas perfectas para esto hoy en día: ordenar, mapear y reducir.

Uniq reduce manteniendo el orden existente

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

var uniq = names.reduce(function(a,b)
    if (a.indexOf(b) < 0 ) a.push(b);
    return a;
  ,[]);

console.log(uniq, names) // [ 'Mike', 'Matt', 'Nancy', 'Adam', 'Jenny', 'Carl' ]

// one liner
return names.reduce(function(a,b)if(a.indexOf(b)<0)a.push(b);return a;,[]);

Uniq más rápido con clasificación

Probablemente haya formas más rápidas, pero esta es bastante decente.

var uniq = names.slice() // slice makes copy of array before sorting it
  .sort(function(a,b)
    return a > b;
  )
  .reduce(function(a,b)
    if (a.slice(-1)[0] !== b) a.push(b); // slice(-1)[0] means last item in array without removing it (like .pop())
    return a;
  ,[]); // this empty array becomes the starting value for a

// one liner
return names.slice().sort(function(a,b)return a > b).reduce(function(a,b)if (a.slice(-1)[0] !== b) a.push(b);return a;,[]);

Actualización 2015: versión ES6:

En ES6 tiene Sets y Spread, lo que hace que sea muy fácil y eficaz eliminar todos los duplicados:

var uniq = [ ...new Set(names) ]; // [ 'Mike', 'Matt', 'Nancy', 'Adam', 'Jenny', 'Carl' ]

Ordenar según la ocurrencia:

Alguien preguntó acerca de ordenar los resultados en función de cuántos nombres únicos hay:

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

var uniq = names
  .map((name) => 
    return count: 1, name: name
  )
  .reduce((a, b) => 
    a[b.name] = (a[b.name] , )

var sorted = Object.keys(uniq).sort((a, b) => uniq[a] < uniq[b])

console.log(sorted)

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *