Cómo obtener valores únicos en una matriz

167

¿Cómo puedo obtener una lista de valores únicos en una matriz? ¿Siempre tengo que usar una segunda matriz o hay algo similar al hashmap de java en JavaScript?

Voy a usar JavaScript y jQuery solamente. No se pueden usar bibliotecas adicionales.

Astronauta
fuente
2
stackoverflow.com/questions/5381621/… - describe exactamente lo que quieres, ¿creo?
SpaceBison
1
¿Estás abierto a usar la underscore.jsbiblioteca?
jakee
un hashmap java es básicamente lo mismo que un objeto javascript. la sintaxis es {"clave": "valor", "clave2": "valor2"}
Ian
Las API de colección JavaScript / TypeScript son terribles en comparación con Scala,list.toSet
Jordan Stewart

Respuestas:

120

Como lo mencioné en los comentarios para la respuesta de @ Rocket, también puedo proporcionar un ejemplo que no use bibliotecas. Esto requiere dos nuevas funciones prototipo, containsyunique

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 mayor confiabilidad, puede reemplazar containscon la indexOfcuña de MDN y verificar si cada elemento indexOfes igual a -1: documentación

Jackwanders
fuente
1
~a.indexOf(b) === (a.indexOf(b) == -1)
Orwellophile
1
@Lexynux: ese era el tipo de respuesta geeky javascript-nerd que solo debería ser utilizado por alguien que entienda lo que significa, y posiblemente ni siquiera entonces. Lo que está diciendo es que escribir if (~a.indexOf(b)) ...es idéntico a escribir por más tiempo if (a.indexOf(b) == -1) ....
Orwellophile
44
esto tiene una alta complejidad de tiempo de ejecución (peor de los casos: O (n ^ 2))
Rahul Arora
1
Esta es una implementación realmente ineficiente. comprobar la matriz de resultados para ver si ya contiene un elemento es horrible. un mejor enfoque sería usar un objeto que rastree los recuentos, o si no desea usar el almacenamiento auxiliar, ordénelo primero en O (n log n) y luego a un barrido lineal y compare elementos uno al lado del otro
Isaiah Lee
1
¿Realmente necesitamos la función "contiene"?
Animesh Kumar
206

O para aquellos que buscan un one-liner (simple y funcional), compatible con los navegadores actuales :

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

Actualización 18-04-2017

Parece que 'Array.prototype.includes' ahora tiene un amplio soporte en las últimas versiones de los navegadores principales ( compatibilidad )

Actualización 29-07-2015:

Hay planes en proceso para que los navegadores admitan un método estandarizado 'Array.prototype.includes', que aunque no responde directamente a esta pregunta; A menudo está relacionado.

Uso:

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

Pollyfill ( soporte de navegador , fuente de 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
fuente
Es casi copiar y pegar desde Kennebec, pero admitir que pasar la matriz como parámetro en lugar de usar el cierre probablemente mejorará el rendimiento.
- Debo decir que no conecté los puntos, simplemente escaneé en busca de un trazador de líneas, parecía una publicación grande, así que salté, fui y encontré una fuente alternativa y volví a publicar para que otros la encontraran rápidamente. Dicho eso, tienes razón; más o menos lo mismo que Kennebec.
Josh Mc
Agradable: no me di cuenta de que el filtro enviado en la matriz como parámetro, y no quería trabajar en un objeto externo. Esto es exactamente lo que necesito: mi versión de javascript (versión anterior de xerces) no tendrá las nuevas ventajas por un tiempo.
Gerard ONeill
1
@GerardONeill sí, en algunas situaciones es muy vital, por ejemplo, si está encadenado funcionalmente y desea acceder a una matriz a la que no se le ha asignado una variable como .map (...). Filter (...)
Josh Mc
@Josh Mc, ¿podrías usar de alguna manera 'incluye' en el método 'único'?
vkelman el
143

Aquí hay una solución mucho más limpia para ES6 que veo que no está incluida aquí. Utiliza el conjunto y el operador de propagación :...

var a = [1, 1, 2];

[... new Set(a)]

Que vuelve [1, 2]

Charles Clayton
fuente
1
Eso es muy inteligente!
Josh Mc
1
Ahora, ¡ ESTO es una frase!
Mac
11
En Typecript tiene que usar Array.from(... new Set(a))ya que Set no se puede convertir implícitamente a un tipo de matriz. Sólo un aviso!
Zachscs
44
@Zachscs, recibí un error de compilación cuando lo intenté. ¿Quisiste decir solo Array.from(new Set(a))? Eso parece funcionar.
adam0101
72

One Liner, JavaScript puro

Con sintaxis 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")

ingrese la descripción de la imagen aquí

Con sintaxis ES5

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

Compatibilidad del navegador : IE9 +

Vamsi
fuente
44
No estoy seguro de por qué esto fue rechazado. Puede ser poco oscuro al principio, y tal vez clasificado como 'inteligente' y no pragmático de leer, pero es declarativo, no destructivo y conciso, donde faltan la mayoría de las otras respuestas.
Larry
1
@Larry, esto fue rechazado porque se proporcionó exactamente la misma respuesta años antes de esta.
Alex Okrushko
@AlexOkrushko bastante justo - perdí esa respuesta debido a la forma en que fue formateada
Larry
77
Todo es una frase si pones todo en una línea :-)
Gary McGill
Puede ser bueno ajustar la a.indexOf(x) === inota de igualdad de los tres signos iguales.
treejanitor
20

Usando EcmaScript 2016 simplemente puede hacerlo así.

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

Los conjuntos son siempre únicos y, al usarlos Array.from(), puede convertir un conjunto en una matriz. Como referencia, eche un vistazo a las documentaciones.

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
fuente
1
La es la respuesta que debes usar. indexOf()las respuestas son terribles porque son O (N ^ 2). Las respuestas extendidas están bien, pero no funcionarán para matrices grandes. Este es el mejor enfoque.
Timmmm
20

Ahora en ES6 podemos usar la función ES6 recientemente introducida

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

O por la sintaxis de propagación de matriz en iterables

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

Devolverá el resultado único.

[1, 3, 4, 5, 2, 23]
Rohit.007
fuente
1
¡La solución más limpia!
Dimitris Filippou
Este es el camino a seguir, cuando se encuentra en un entorno JS que admite de new Setesta manera (como Angular / TypeScript moderno)
Don Cheadle
1
Vale la pena señalar que también puede usar la sintaxis de propagación de matriz en iterables como Establecer y Mapa: let items = [1,1,1,1,3,4,5,2,23,1,4,4,4,2,2,2]; let uniqueItems = [...new Set(items)];
jacobedawson
¡Me encantan estas respuestas ES6!
Glen Thompson el
1
Esto es idéntico a la respuesta de @Adeel Imran. Por favor, no responda exactamente lo mismo que las respuestas existentes.
Timmmm
16

Si desea dejar la matriz original intacta,

necesita una segunda matriz para contener los elementos únicos de la primera

La mayoría de los navegadores tienen 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
fuente
14

En estos días, puede usar el tipo de datos Set de ES6 para convertir su matriz en un Set único. Luego, si necesita usar métodos de matriz, puede convertirlo nuevamente en una 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
fuente
1
Genial, sería bueno ver algunos números de rendimiento, creo que convertir estos conjuntos en particular si son muy dinámicos y grandes podría ser un aumento del rendimiento, la única forma de saberlo es probar :)
Astronauta
2
Si está utilizando un transpilador o está en un entorno que lo admite, puede hacer lo mismo de manera más concisa como:var uniqueArr = [...new Set(arr)]; // ["a", "b"]
Stenerson el
Hola @Astronaut, ¿has hecho algunas pruebas sobre el rendimiento como dijiste?
alexventuraio
8

No es nativo en Javascript, pero muchas bibliotecas tienen este método.

Underscore.js's _.uniq(array)( enlace ) funciona bastante bien ( fuente ).

Calvin
fuente
¡Gracias por compartir! Esta función toma el iterador y el contexto junto con la matriz como una lista de argumentos de v1.4.3.
Kunj
6

Usando jQuery, aquí hay una función única de Array que hice:

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]
Cohete Hazmat
fuente
55
si va a usar jQuery dentro del prototipo de un objeto core javascript, ¿no sería mejor escribir una función jQuery, como $.uniqueArray(arr)? Incrustar referencias a jQuery dentro Arraydel prototipo parece cuestionable
jackwanders
1
@jackwanders: ¿Qué es tan cuestionable al respecto? Si tienes jQuery en la página, usémoslo.
Rocket Hazmat
Solo que la nueva función única que escribió ahora depende de jQuery; no puede moverlo a un nuevo sitio o aplicación sin asegurarse de que jQuery esté en uso allí.
jackwanders
2
ese era mi punto; si va a usar jQuery, haga que la función en sí sea parte de jQuery. Si fuera a extender el prototipo de un objeto central, me quedaría con JavaScript central, solo para mantener las cosas reutilizables. Si alguien más está mirando su código, es obvio que $.uniqueArraydepende de jQuery; menos obvio que Array.prototype.uniquees así.
jackwanders
1
@jackwanders: supongo. Lo uso en mi código, ya que siempre uso jQuery, y me gusta extender prototypes. Pero, entiendo tu punto ahora. Dejaré esto aquí de todos modos.
Rocket Hazmat
6

Solución corta y dulce usando una 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
fuente
¿Corto? ¿y dulce? ¿Has mirado las mejores soluciones?
Alex Okrushko
5

¡Rápido, compacto, sin bucles anidados, funciona con cualquier objeto, no solo cadenas y números, toma un predicado y solo 5 líneas de código!

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

Ejemplo: para buscar elementos únicos 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'}
//  ] 

Si desea que encuentre el primer elemento único en lugar del último, agregue una marca found.hasOwnPropery () allí.

usuario1618323
fuente
4

Solo necesita Vanilla JS para encontrar elementos únicos con Array.some y Array.reduce. Con la sintaxis ES2015, solo tiene 62 caracteres.

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

Array.some y Array.reduce son compatibles con IE9 + y otros navegadores. Simplemente cambie las funciones de flecha gruesa para que las funciones regulares se admitan en navegadores que no admiten la sintaxis 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 / Array / Reducir

cachorros
fuente
4

La mayoría de las soluciones anteriores tienen una alta complejidad de tiempo de ejecución.

Aquí está la solución que usa reducey puede hacer el trabajo en O (n) tiempo.

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 solución no depende de reducir. La idea es crear un mapa de objetos e insertar unos únicos en la matriz.

Rahul Arora
fuente
1

Manera ES6:

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

puedes usar,

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

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

te dará elementos únicos,

**> pero hay una trampa,

para este "1" y 1 y son elementos diff, **

La segunda opción es utilizar el método de filtro en la matriz.

niranjan harpale
fuente
1

Puede ingresar una matriz con duplicados y el método siguiente devolverá la matriz con elementos únicos.

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
fuente
0

El único problema con las soluciones dadas hasta ahora es la eficiencia. Si le preocupa eso (y probablemente debería), debe evitar los bucles anidados: for * for, filter * indexOf, grep * inArray, todos iteran la matriz varias veces. Puede implementar un solo ciclo con soluciones como esta o esta

Jesús Carrera
fuente
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
fuente
0

He intentado este problema en JS puro. He seguido los siguientes pasos 1. Ordene la matriz dada, 2. recorra la matriz ordenada, 3. Verifique el valor anterior y el valor siguiente con el valor actual

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

Manifestación

Sanketh Nayak
fuente
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
fuente
0

Teniendo en cuenta que indexOfdevolverá la primera aparición de un elemento, puede hacer algo como esto:

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

Otro pensamiento de esta pregunta. Esto es lo que hice para lograr esto con 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
fuente
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);

usuario1739150
fuente
0

Aquí hay un enfoque con una equalsfunción personalizable que puede usarse para primitivas y también 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)
virus
fuente
0

Mi respuesta utiliza Array.filtery Array.indexOfmétodos para obtener los valores únicos

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

Vi este enfoque en un sitio web pero su código es diferente de lo que parece aquí. He simplificado el código a una línea y lo he publicado aquí para que alguien se beneficie de él.

Nota: Mi enfoque es similar o igual al del trazador de líneas publicado por Josh. Lo dejo aquí ya que los nombres de las variables se explican por sí mismos en mi código.

Anand Raj
fuente
-1

Estaba pensando si podemos usar la búsqueda lineal para eliminar los duplicados:

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
fuente
-1

Aquí está la solución única al problema:

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

Copie y pegue esto en la consola del navegador y obtenga los resultados, yo :-)

Manish Kumar
fuente
-2

He incorporado la función JQuery Unique .

uniqueValues= jQuery.unique( duplicateValues );

Para obtener más información, puede consultar las Documentaciones de la API de jquery.

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

Mitul Maheshwari
fuente
8
Tenga en cuenta que esto solo funciona en matrices de elementos DOM, no en cadenas o números. - Cita de la documentación.
mco