Pregunta

¿Cuál es la forma más concisa y eficiente de averiguar si una matriz de JavaScript contiene un objeto?

Esta es la única forma en que sé hacerlo:

function contains(a, obj) {
    for (var i = 0; i < a.length; i++) {
        if (a[i] === obj) {
            return true;
        }
    }
    return false;
}

¿Hay una manera mejor y más concisa de lograr esto?

Esto está muy relacionado con la pregunta de desbordamiento de pila ¿La mejor manera de encontrar un elemento en una matriz de JavaScript? que trata de encontrar objetos en una matriz utilizando indexOf .

¿Fue útil?

Solución

Los navegadores actuales tienen Array # incluye , que hace exactamente eso, es ampliamente compatible , y tiene un polyfill para navegadores más antiguos.

> ['joe', 'jane', 'mary'].includes('jane');
true 

También puede usar Array # indexOf , que es menos directo, pero no requiere Polyfills para navegadores desactualizados.

jQuery ofrece $ .inArray , que es funcionalmente equivalente a Array # indexOf .

underscore.js , una biblioteca de utilidades de JavaScript, ofrece _.contains (list, value) , alias _.include (list, value) , ambos de los cuales use indexOf internamente si se pasa una matriz de JavaScript.

Algunos otros marcos ofrecen métodos similares:

Observe que algunos marcos implementan esto como una función, mientras que otros agregan la función al prototipo de matriz.

Otros consejos

Actualización: Como @orip menciona en los comentarios, el punto de referencia vinculado se realizó en 2008, por lo que los resultados pueden no ser relevantes para los navegadores modernos. Sin embargo, probablemente necesites esto para admitir navegadores no modernos de todos modos y probablemente no se hayan actualizado desde entonces. Prueba siempre por ti mismo.

Como han dicho otros, la iteración a través de la matriz es probablemente la mejor manera, pero ha sido demostrado que un bucle decreciente de while es la forma más rápida de iterar en JavaScript. Así que es posible que desee volver a escribir su código de la siguiente manera:

function contains(a, obj) {
    var i = a.length;
    while (i--) {
       if (a[i] === obj) {
           return true;
       }
    }
    return false;
}

Por supuesto, también puede extender el prototipo Array:

Array.prototype.contains = function(obj) {
    var i = this.length;
    while (i--) {
        if (this[i] === obj) {
            return true;
        }
    }
    return false;
}

Y ahora puedes usar simplemente lo siguiente:

alert([1, 2, 3].contains(2)); // => true
alert([1, 2, 3].contains('2')); // => false

indexOf tal vez, pero es una extensión de " JavaScript al estándar ECMA-262; como tal, puede no estar presente en otras implementaciones de la norma. "

Ejemplo:

[1, 2, 3].indexOf(1) => 0
["foo", "bar", "baz"].indexOf("bar") => 1
[1, 2, 3].indexOf(4) => -1

AFAICS Microsoft hace no ofrezca algún tipo de alternativa a esto, pero puede agregar una funcionalidad similar a las matrices en Internet Explorer (y otros navegadores que no admiten indexOf ) si desea , como muestra una búsqueda rápida en Google (por ejemplo, this ).

ECMAScript 7 presenta Array. prototype.includes .

Se puede usar así:

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

También acepta un segundo argumento opcional fromIndex :

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

A diferencia de indexOf , que utiliza Strict Equality Comparison , incluye compara utilizando SameValueZero algoritmo de igualdad. Eso significa que puede detectar si una matriz incluye un NaN :

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

También a diferencia de indexOf , includes no omite los índices que faltan:

new Array(5).includes(undefined); // true

Actualmente, todavía es un borrador pero puede ser polyfilled para que funcione en todos los navegadores.

b es el valor, y a es la matriz. Devuelve true o false :

function(a, b) {
    return a.indexOf(b) != -1
}

Las respuestas principales asumen tipos primitivos, pero si desea averiguar si una matriz contiene un objeto con algún rasgo, Array.prototype.some () es una solución muy elegante:

const items = [ {a: '1'}, {a: '2'}, {a: '3'} ]

items.some(item => item.a === '3')  // returns true
items.some(item => item.a === '4')  // returns false

Lo bueno de esto es que la iteración se cancela una vez que se encuentra el elemento, por lo que se guardan ciclos de iteración innecesarios.

Además, encaja perfectamente en una declaración if , ya que devuelve un booleano:

if (items.some(item => item.a === '3')) {
  // do something
}

* Como señala Jamess en el comentario, a partir de hoy, septiembre de 2018, Array.prototype.some () es totalmente compatible: tabla de soporte de caniuse.com

Aquí hay una JavaScript 1.6 compatible implementación de Array.indexOf :

if (!Array.indexOf) {
    Array.indexOf = [].indexOf ?
        function(arr, obj, from) {
            return arr.indexOf(obj, from);
        } :
        function(arr, obj, from) { // (for IE6)
            var l = arr.length,
                i = from ? parseInt((1 * from) + (from < 0 ? l : 0), 10) : 0;
            i = i < 0 ? 0 : i;
            for (; i < l; i++) {
                if (i in arr && arr[i] === obj) {
                    return i;
                }
            }
            return -1;
        };
}

Uso:

function isInArray(array, search)
{
    return array.indexOf(search) >= 0;
}

// Usage
if(isInArray(my_array, "my_value"))
{
    //...
}

Extender el objeto Array de JavaScript es una muy mala idea porque introduce nuevas propiedades (sus métodos personalizados) en los bucles for-in que pueden romper los scripts existentes. Hace algunos años, los autores de la biblioteca Prototype tuvieron que volver a diseñar su implementación de biblioteca para eliminar solo este tipo de cosas.

Si no necesita preocuparse por la compatibilidad con otro JavaScript que se ejecuta en su página, hágalo, de lo contrario, le recomendaría la solución de función independiente más incómoda pero más segura.

Pensando fuera de la caja por un segundo, si está haciendo esta llamada muchas veces, es mucho más eficiente usar una matriz asociativa un Mapa para hacer búsquedas usando una función hash.

https://developer.mozilla.org/ en-US / docs / Web / JavaScript / Reference / Global_Objects / Map

Una sola línea:

function contains(arr, x) {
    return arr.filter(function(elem) { return elem == x }).length > 0;
}

Uso lo siguiente:

Array.prototype.contains = function (v) {
    return this.indexOf(v) > -1;
}

var a = [ 'foo', 'bar' ];

a.contains('foo'); // true
a.contains('fox'); // false
function contains(a, obj) {
    return a.some(function(element){return element == obj;})
}

Array.prototype.some () se agregó al estándar ECMA-262 en la quinta edición

Una alternativa indexOf / lastIndexOf bidireccional

2015

Aunque el nuevo método incluye es muy bueno, el soporte es básicamente cero por ahora.

Hace mucho tiempo que pensaba en la forma de reemplazar las funciones lentas indexOf / lastIndexOf.

Ya se ha encontrado una forma de rendimiento, mirando las respuestas principales. De entre los que elegí, la función contiene publicada por @Damir Zekic, que debería ser la más rápida. Pero también establece que los puntos de referencia son de 2008 y, por lo tanto, están desactualizados.

También prefiero while sobre para , pero no por una razón específica, terminé de escribir la función con un bucle for. También se puede hacer con un mientras que - .

Tenía curiosidad por saber si la iteración era mucho más lenta si revisaba ambos lados de la matriz mientras lo hacía. Aparentemente no, y esta función es aproximadamente dos veces más rápida que las más votadas. Obviamente también es más rápido que el nativo. Esto en un entorno del mundo real, donde nunca se sabe si el valor que está buscando está al principio o al final de la matriz.

Cuando sabe que acaba de presionar una matriz con un valor, el uso de lastIndexOf sigue siendo la mejor solución, pero si tiene que viajar a través de matrices grandes y el resultado podría estar en todas partes, esta podría ser una solución sólida para hacer las cosas más rápido.

Índice bidireccionalOf / lastIndexOf

function bidirectionalIndexOf(a, b, c, d, e){
  for(c=a.length,d=c*1; c--; ){
    if(a[c]==b) return c; //or this[c]===b
    if(a[e=d-1-c]==b) return e; //or a[e=d-1-c]===b
  }
  return -1
}

//Usage
bidirectionalIndexOf(array,'value');

Prueba de rendimiento

http://jsperf.com/bidirectionalindexof

Como prueba, creé una matriz con 100k entradas.

Tres consultas: al principio, en el medio y amp; al final de la matriz.

Espero que también encuentres esto interesante y que pruebes el rendimiento.

Nota: Como puede ver, modifiqué ligeramente la función contiene para reflejar el indexOf & amp; La salida de lastIndexOf (básicamente, true con el index y false con -1 ). Eso no debería dañarlo.

La variante del prototipo de array

Object.defineProperty(Array.prototype,'bidirectionalIndexOf',{value:function(b,c,d,e){
  for(c=this.length,d=c*1; c--; ){
    if(this[c]==b) return c; //or this[c]===b
    if(this[e=d-1-c] == b) return e; //or this[e=d-1-c]===b
  }
  return -1
},writable:false, enumerable:false});

// Usage
array.bidirectionalIndexOf('value');

La función también se puede modificar fácilmente para devolver verdadero o falso o incluso el objeto, la cadena o lo que sea.

Y aquí está la variante while :

function bidirectionalIndexOf(a, b, c, d){
  c=a.length; d=c-1;
  while(c--){
    if(b===a[c]) return c;
    if(b===a[d-c]) return d-c;
  }
  return c
}

// Usage
bidirectionalIndexOf(array,'value');

¿Cómo es esto posible?

Creo que el cálculo simple para obtener el índice reflejado en una matriz es tan simple que es dos veces más rápido que hacer una iteración de bucle real.

Este es un ejemplo complejo que realiza tres comprobaciones por iteración, pero esto solo es posible con un cálculo más largo que provoca la desaceleración del código.

http://jsperf.com/bidirectionalindexof/2

Si está utilizando JavaScript 1.6 o posterior (Firefox 1.5 o posterior) puede usar Array.indexOf . De lo contrario, creo que vas a terminar con algo similar a tu código original.

Si está comprobando repetidamente la existencia de un objeto en una matriz, tal vez debería examinar

  1. Mantener la matriz ordenada en todo momento haciendo orden de inserción en su matriz (ponga nuevos objetos en el lugar correcto)
  2. Realice la actualización de los objetos como eliminar + ordenó la operación de inserción y
  3. Use una búsqueda búsqueda binaria en su contiene (a, obj) .
function inArray(elem,array)
{
    var len = array.length;
    for(var i = 0 ; i < len;i++)
    {
        if(array[i] == elem){return i;}
    }
    return -1;
} 

Devuelve el índice de matriz si se encuentra, o -1 si no se encuentra

Usamos este fragmento de código (funciona con objetos, arrays, cadenas):

/*
 * @function
 * @name Object.prototype.inArray
 * @description Extend Object prototype within inArray function
 *
 * @param {mix}    needle       - Search-able needle
 * @param {bool}   searchInKey  - Search needle in keys?
 *
 */
Object.defineProperty(Object.prototype, 'inArray',{
    value: function(needle, searchInKey){

        var object = this;

        if( Object.prototype.toString.call(needle) === '[object Object]' || 
            Object.prototype.toString.call(needle) === '[object Array]'){
            needle = JSON.stringify(needle);
        }

        return Object.keys(object).some(function(key){

            var value = object[key];

            if( Object.prototype.toString.call(value) === '[object Object]' || 
                Object.prototype.toString.call(value) === '[object Array]'){
                value = JSON.stringify(value);
            }

            if(searchInKey){
                if(value === needle || key === needle){
                return true;
                }
            }else{
                if(value === needle){
                    return true;
                }
            }
        });
    },
    writable: true,
    configurable: true,
    enumerable: false
});

Uso :

var a = {one: "first", two: "second", foo: {three: "third"}};
a.inArray("first");          //true
a.inArray("foo");            //false
a.inArray("foo", true);      //true - search by keys
a.inArray({three: "third"}); //true

var b = ["one", "two", "three", "four", {foo: 'val'}];
b.inArray("one");         //true
b.inArray('foo');         //false
b.inArray({foo: 'val'})   //true
b.inArray("{foo: 'val'}") //false

var c = "String";
c.inArray("S");        //true
c.inArray("s");        //false
c.inArray("2", true);  //true
c.inArray("20", true); //false

Utilice la función alguna de lodash.

Es conciso, preciso y tiene un excelente soporte multiplataforma.

La respuesta aceptada ni siquiera cumple con los requisitos.

Requisitos: Recomienda la forma más concisa y eficiente de averiguar si una matriz de JavaScript contiene un objeto.

Respuesta aceptada:

$.inArray({'b': 2}, [{'a': 1}, {'b': 2}])
> -1

Mi recomendación:

_.some([{'a': 1}, {'b': 2}], {'b': 2})
> true

Notas:

$ .inArray funciona bien para determinar si existe un valor de escalar en una matriz de escalares ...

$.inArray(2, [1,2])
> 1

... pero la pregunta claramente pide una manera eficiente de determinar si un objeto está contenido en una matriz.

Para manejar tanto los escalares como los objetos, puedes hacer esto:

(_.isObject(item)) ? _.some(ary, item) : (_.indexOf(ary, item) > -1)

Solución que funciona en todos los navegadores modernos:

function contains(arr, obj) {
  const stringifiedObj = JSON.stringify(obj); // Cache our object to not call `JSON.stringify` on every iteration
  return arr.some(item => JSON.stringify(item) === stringifiedObj);
}

Uso:

contains([{a: 1}, {a: 2}], {a: 1}); // true

Solución IE6 +:

function contains(arr, obj) {
  var stringifiedObj = JSON.stringify(obj)
  return arr.some(function (item) {
    return JSON.stringify(item) === stringifiedObj;
  });
}

// .some polyfill, not needed for IE9+
if (!('some' in Array.prototype)) {
  Array.prototype.some = function (tester, that /*opt*/) {
    for (var i = 0, n = this.length; i < n; i++) {
      if (i in this && tester.call(that, this[i], i, this)) return true;
    } return false;
  };
}

Uso:

[{a: 1}, {a: 2}].includes({a: 1});
// false, because {a: 1} is a new object

¿Por qué usar JSON.stringify ?

Array.indexOf y Array.includes (así como la mayoría de las respuestas aquí) solo se comparan por referencia y no por valor.

[{a: 1}, {a: 2}].some(item => JSON.stringify(item) === JSON.stringify({a: 1));
// true

Bonus

One-liner de ES6 no optimizado:

<*>

Nota: La comparación de objetos por valor funcionará mejor si las claves están en el mismo orden, así que para estar seguro, primero debe ordenar las claves con un paquete como este: https://www.npmjs.com/package/sort-keys


Actualizado el contiene la función con una optimización de rendimiento. Gracias, itinance por señalarlo.

Si bien array.indexOf (x)! = - 1 es la forma más concisa de hacerlo (y ha sido compatible con navegadores que no son de Internet & nbsp; Explorer durante más de una década ...), no es O (1), sino O (N), lo cual es terrible. Si su matriz no cambiará, puede convertirla a una tabla hash, luego haga table [x]! == undefined o === undefined :

Array.prototype.toTable = function() {
    var t = {};
    this.forEach(function(x){t[x]=true});
    return t;
}

Demo:

var toRemove = [2,4].toTable();
[1,2,3,4,5].filter(function(x){return toRemove[x]===undefined})

(Desafortunadamente, aunque puedes crear un Array.prototype.contains para " congelar " una matriz y almacenar una tabla hash en this._cache en dos líneas, esto daría resultados incorrectos si eliges editar la matriz más adelante. JavaScript no tiene suficientes ganchos para permitirte mantener este estado, a diferencia de Python, por ejemplo.)

ECMAScript 6 tiene una propuesta elegante para encontrar.

  

El método de búsqueda ejecuta la función de devolución de llamada una vez para cada elemento   presente en la matriz hasta que encuentre uno en el que la devolución de llamada devuelva un verdadero   valor. Si se encuentra un elemento de este tipo, find devuelve inmediatamente el valor   de ese elemento. De lo contrario, encontrar devuelve indefinido. la devolución de llamada es   invocado solo para los índices de la matriz que tienen valores asignados; eso   no se invoca para los índices que se han eliminado o que nunca se han   Se le han asignado valores.

Aquí está la Documentación de MDN en eso.

La funcionalidad de búsqueda funciona así.

function isPrime(element, index, array) {
    var start = 2;
    while (start <= Math.sqrt(element)) {
        if (element % start++ < 1) return false;
    }
    return (element > 1);
}

console.log( [4, 6, 8, 12].find(isPrime) ); // Undefined, not found
console.log( [4, 5, 8, 12].find(isPrime) ); // 5

Puede usar esto en ECMAScript 5 y más abajo por definiendo la función .

if (!Array.prototype.find) {
  Object.defineProperty(Array.prototype, 'find', {
    enumerable: false,
    configurable: true,
    writable: true,
    value: function(predicate) {
      if (this == null) {
        throw new TypeError('Array.prototype.find called on null or undefined');
      }
      if (typeof predicate !== 'function') {
        throw new TypeError('predicate must be a function');
      }
      var list = Object(this);
      var length = list.length >>> 0;
      var thisArg = arguments[1];
      var value;

      for (var i = 0; i < length; i++) {
        if (i in list) {
          value = list[i];
          if (predicate.call(thisArg, value, i, list)) {
            return value;
          }
        }
      }
      return undefined;
    }
  });
}

Uso:

var myArray = ['yellow', 'orange', 'red'] ;

alert(!!~myArray.indexOf('red')); //true

Demostración

Para saber exactamente lo que hace tilde ~ en este momento, consulte esta pregunta ¿Qué hace una tilde cuando precede a una expresión? .

Así es como prototipo lo hace :

/**
 *  Array#indexOf(item[, offset = 0]) -> Number
 *  - item (?): A value that may or may not be in the array.
 *  - offset (Number): The number of initial items to skip before beginning the
 *      search.
 *
 *  Returns the position of the first occurrence of `item` within the array &mdash; or
 *  `-1` if `item` doesn't exist in the array.
**/
function indexOf(item, i) {
  i || (i = 0);
  var length = this.length;
  if (i < 0) i = length + i;
  for (; i < length; i++)
    if (this[i] === item) return i;
  return -1;
}

También vea

De acuerdo, ¡solo puede optimizar su código para obtener el resultado!

Hay muchas formas de hacer esto que son más limpias y mejores, pero solo quería obtener su patrón y aplicar a eso usando JSON.stringify , simplemente haga algo como esto en su caso:

function contains(a, obj) {
    for (var i = 0; i < a.length; i++) {
        if (JSON.stringify(a[i]) === JSON.stringify(obj)) {
            return true;
        }
    }
    return false;
}

Uso:

Array.prototype.contains = function(x){
  var retVal = -1;

  // x is a primitive type
  if(["string","number"].indexOf(typeof x)>=0 ){ retVal = this.indexOf(x);}

  // x is a function
  else if(typeof x =="function") for(var ix in this){
    if((this[ix]+"")==(x+"")) retVal = ix;
  }

  //x is an object...
  else {
    var sx=JSON.stringify(x);
    for(var ix in this){
      if(typeof this[ix] =="object" && JSON.stringify(this[ix])==sx) retVal = ix;
    }
  }

  //Return False if -1 else number if numeric otherwise string
  return (retVal === -1)?false : ( isNaN(+retVal) ? retVal : +retVal);
}

Sé que no es la mejor forma de hacerlo, pero como no hay una forma nativa de interacción entre objetos, supongo que esto es lo más parecido a comparar dos entidades en una matriz. Además, extender el objeto Array puede que no sea una buena idea, pero a veces está bien (si eres consciente de ello y de la disyuntiva).

Se puede usar Conjunto que tiene el El método " tiene () " ;:

function contains(arr, obj) {
  var proxy = new Set(arr);
  if (proxy.has(obj))
    return true;
  else
    return false;
}

var arr = ['Happy', 'New', 'Year'];
console.log(contains(arr, 'Happy'));

También puedes usar este truco:

var arrayContains = function(object) {
  return (serverList.filter(function(currentObject) {
    if (currentObject === object) {
      return currentObject
    }
    else {
      return false;
    }
  }).length > 0) ? true : false
}
  1. Utilice Array.indexOf (Object) .
  2. Con ECMA 7 se puede usar el Array.includes (Object) .
  3. Con ECMA 6 puede usar Array.find (FunctionName) donde FunctionName es un usuario Función definida para buscar el objeto en la matriz.

    Espero que esto ayude!

Cosa similar: encuentra el primer elemento por " buscar lambda " ;:

Array.prototype.find = function(search_lambda) {
  return this[this.map(search_lambda).indexOf(true)];
};

Uso:

[1,3,4,5,8,3,5].find(function(item) { return item % 2 == 0 })
=> 4

Lo mismo en coffeescript:

Array.prototype.find = (search_lambda) -> @[@map(search_lambda).indexOf(true)]
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top