Pregunta Eliminar los valores duplicados de la matriz JS [duplicar]


Esta pregunta ya tiene una respuesta aquí:

Tengo una matriz de JavaScript muy simple que puede contener o no duplicados.

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

Necesito eliminar los duplicados y poner los valores únicos en una nueva matriz.

Podría señalar todos los códigos que he intentado, pero creo que es inútil porque no funcionan. Acepto las soluciones jQuery también.

Pregunta similar:


861
2018-02-10 14:53


origen


Respuestas:


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

362
2018-02-10 15:13



"Inteligente" pero ingenuamente

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

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

Utilizando el 3er parámetro ("this array") de la devolución de llamada del filtro podemos evitar el cierre de la variable de matriz:

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

Aunque conciso, este algoritmo no es particularmente eficiente para grandes matrices (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 generalmente se hace. La idea es colocar cada elemento en una tabla hash y luego verificar su presencia al instante. Esto nos da tiempo lineal, pero tiene al menos dos inconvenientes:

  • dado que las claves hash solo pueden ser cadenas en Javascript, este código no distingue números y "cadenas numéricas". Es decir, uniq([1,"1"]) volverá solo [1]
  • por la misma razón, todos los objetos serán considerados iguales: uniq([{foo:1},{foo:2}]) volverá solo [{foo:1}].

Dicho esto, si sus matrices contienen solo primitivos 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 primero la matriz y luego eliminar cada elemento igual al anterior:

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

De nuevo, esto no funciona con objetos (porque todos los objetos son iguales para sort) Además, cambiamos silenciosamente la matriz original como un efecto secundario, ¡no es bueno! Sin embargo, si su entrada ya está ordenada, este es el camino a seguir (solo elimine sort de arriba).

Único por ...

A veces se desea uniquify una lista basada en algunos criterios distintos de la igualdad, por ejemplo, para filtrar objetos que son diferentes, pero comparten alguna propiedad. Esto se puede hacer elegantemente pasando una devolución de llamada. Esta devolución de llamada "clave" se aplica a cada elemento, y los elementos con "claves" iguales se eliminan. Ya que key se espera que devuelva una tabla primitiva, hash 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);
    })
}

Una particularmente útil key() es JSON.stringify que eliminará objetos que son físicamente diferentes, pero que "se ven igual":

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

o usa el Set objeto en ES6:

function uniqBy(a, key) {
    var seen = new Set();
    return a.filter(item => {
        var k = key(item);
        return seen.has(k) ? false : seen.add(k);
    });
}

(Algunas personas prefieren !seen.has(k) && seen.add(k) en lugar de seen.has(k) ? false : seen.add(k))

Bibliotecas

Ambos guion bajo y Lo-Dash proporcionar uniq métodos. Sus algoritmos son básicamente similares al primer fragmento de arriba 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 extras adicionales, como envolver nativos indexOf, capacidad de uniqify por una clave (iteratee en su lenguaje) y optimizaciones para matrices ya ordenadas.

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

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

que es, nuevamente, una variación del primer fragmento.

Actuación

Las llamadas a funciones son caras en Javascript, por lo tanto, las soluciones anteriores, por más 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 # 3 anterior, pero un orden de magnitud más rápido (a partir de 2017, es solo el doble de rápido: ¡la gente de JS core 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('<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

ES6 proporciona el Conjunto objeto, lo que hace las cosas mucho más fáciles:

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

o

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

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

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

Generadores

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

  • tomar el siguiente valor de la discusión
  • si ya se ha visto, sáltelo
  • 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


2209
2018-02-10 15:05



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 mientras mantiene 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 la 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ó por ordenar los resultados según 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] || 0) + b.count
    return a
  }, {})

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

console.log(sorted)

266
2018-04-07 22:42



Vanilla JS: eliminar duplicados utilizando un objeto como un conjunto

Siempre puede tratar de ponerlo en un objeto, y luego iterar a través de sus teclas:

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: eliminar duplicados mediante el seguimiento de los valores ya vistos (seguro para pedidos)

O bien, para una versión segura para pedidos, utilice un objeto para almacenar todos los valores vistos anteriormente y compruebe los valores en su contra antes de agregarlos a una matriz.

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: utilice la nueva estructura de datos de Set (seguro para pedidos)

ECMAScript 6 agrega el nuevo Set Estructura de datos, que le permite almacenar valores de cualquier tipo. Set.values devuelve elementos en orden de inserción.

function remove_duplicates_es6(arr) {
    let s = new Set(arr);
    let it = s.values();
    return Array.from(it);
}

Ejemplo de uso:

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"]

73
2018-02-10 15:03



Utilizar Underscore.js

Es una biblioteca con una gran cantidad de funciones para manipular matrices.

Es el lazo para ir junto con el tux de jQuery, y Backbone.js   tirantes.

_.uniq

_.uniq(array, [isSorted], [iterator])  Alias:  único
  Produce una versión libre de duplicados del formación, usando === para probar el objeto   igualdad. Si sabes de antemano que el formación está ordenado, pasando    cierto para isSorted ejecutará un algoritmo mucho más rápido. Si quieres   calcular elementos únicos basados ​​en una transformación, pasar un iterador   función.

Ejemplo

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

alert(_.uniq(names, false));

Nota: Lo-Dash (un guion bajo competidor) también ofrece un .uniq implementación.


68
2018-06-30 03:07



Una versión de línea única que utiliza el filtro de matriz y las funciones indexOf:

arr = arr.filter (function (value, index, array) { 
    return array.indexOf (value) == index;
});

60
2018-02-11 21:18



Simplemente puede hacerlo en JavaScript, con la ayuda del segundo - índice - parámetro del filter método:

var a = [2,3,4,5,5,4];
a.filter(function(value, index){ return a.indexOf(value) == index });

o en pocas palabras

a.filter((v,i) => a.indexOf(v) == i)

46
2018-06-15 11:05



La forma más concisa de eliminar duplicados de una matriz usando funciones JavaScript nativas es usar una secuencia como la siguiente:

vals.sort().reduce(function(a, b){ if (b != a[0]) a.unshift(b); return a }, [])

no hay necesidad de slice ni indexOf dentro de la función reducir, como he visto en otros ejemplos! Sin embargo, tiene sentido usarlo junto con una función de filtro:

vals.filter(function(v, i, a){ return i == a.indexOf(v) })

Otra forma más de hacerlo de ES6 (2015) que ya funciona en algunos navegadores es:

Array.from(new Set(vals))

o incluso usando el operador de propagación:

[...new Set(vals)]

¡aclamaciones!


28
2017-09-11 23:44



Una línea:

let names = ['Mike','Matt','Nancy','Adam','Jenny','Nancy','Carl', 'Nancy'];
let dup = [...new Set(names)];
console.log(dup);

28
2017-08-01 01:39