Conjunto de objetos en javascript

6 minutos de lectura

Me gustaría tener un conjunto de objetos en Javascript. Es decir, una estructura de datos que contiene solo objetos únicos.

Normalmente se recomienda el uso de propiedades, por ejemplo myset["key"] = true. Sin embargo, necesito que las llaves sean objetos. He leído que Javascript convierte nombres de propiedad en cadenas, así que supongo que no puedo usar myset[myobject] = true.

Podría usar una matriz, pero necesito algo mejor que el rendimiento de O(n) para agregar, buscar y eliminar elementos.

Necesita poder diferenciar los objetos solo por referencia, así que dado:

var a = {};
var b = {};

entonces ambos a y b deberían poder agregarse, porque son objetos separados.

Básicamente, busco algo como C++ std::set, que puede almacenar objetos Javascript. ¿Algunas ideas?

  • posible duplicado de la implementación de JavaScript de una estructura de datos establecida

    – Ciro Santilli OurBigBook.com

    15 de junio de 2014 a las 15:45

Avatar de usuario de Ry-
Ry-

ES6 proporciona un nativo Set:

let s = new Set();
let a = {};
let b = {};

s.add(a);

console.log(s.has(a));  // true
console.log(s.has(b));  // false

  • new Set([{one: "one"}, {one: "one"}]).size es 2. ¿No debería ser 1?

    – ndtreviv

    11 de diciembre de 2018 a las 12:35

  • @ndtreviv: No, no son el mismo objeto. {one: "one"} !== {one: "one"}

    – Ry-

    11/12/2018 a las 15:30

  • Estás bien. Terminé usando _.uniqWith y _.isEqual de lodash para obtener objetos únicos.

    – ndtreviv

    11 dic 2018 a las 20:34

  • ¡Gracias @ndtreviv! solía uniqueObjects = _.uniqWith(objects, _.isEqual)

    – Joerick

    3 de enero de 2020 a las 12:24

Aquí hay una sugerencia loca… clave en el resultado de JSON.stringify(object)

  • Esto es en lo que sigo pensando, pero realmente no quiero hacer las cosas de esta manera :/

    – Charlie L.

    22 de julio de 2017 a las 18:05

  • TypeError: valor de objeto cíclico

    – Pacerier

    14 de junio de 2020 a las 17:56

No es posible para todos los objetos, pero si su objeto tiene un .toString() método implementado, es:

var x = {toString: function(){ return 'foo'; }};
var y = {toString: function(){ return 'bar'; }};
var obj = {};
obj[x] = 'X';
obj[y] = 'Y';
console.log(obj);
// { foo: 'X', bar: 'Y' }

Si quieres hacer esto más fácil, hazlo una clase:

function myObj(name){
   this.name = name;
}
myObj.prototype.toString = function(){ return this.name; }

var obj = {};
obj[new myObj('foo')] = 'X';
obj[new myObj('bar')] = 'Y';

  • Todos los objetos tienen un Encadenar método heredado de Objeto.prototipo. Asignar el objeto a un nombre de propiedad llamará a su Encadenar método de todos modos, por lo que no es necesario hacerlo explícitamente. No puede garantizar que un objeto Encadenar será único para todos los objetos, por lo que el enfoque de Sophistifunk es mejor (es decir, almacenar objetos en una matriz y proporcionar un envoltorio con métodos para agregar, recuperar y eliminar miembros).

    – RobG

    14 de abril de 2011 a las 1:43


  • @RobG – Te estás perdiendo el punto. los toString() los métodos que agregué fueron para que pudiera devolver un hash único del objeto actual. en este caso solo regreso 'foo' y 'bar'pero fácilmente podría codificar cualquier otra cosa que identifique el objeto (de ahí el prototype ejemplo). En cuanto a su primera declaración, no… Asignación toString() como una propiedad llamará a esa función, NO a la que hereda Object.prototype

    usuario578895

    14 de abril de 2011 a las 1:46


  • Para lo que me importa, var guid=0; myObj.prototype.toString = function(){ return (this.guid || (this.guid=guid++)); } trabajaría.

    usuario578895

    14 de abril de 2011 a las 1:50


  • @cwolves: todos los objetos almacenados deberán tener nombres únicos, por lo que es mejor automatizarlo y mantenerlo privado.

    – RobG

    14 de abril de 2011 a las 2:10

  • @RobG: si todos los objetos son del mismo tipo, hacerlo así es mejor (consulte mi último comentario de guid si lo desea) ya que son búsquedas O (1) a diferencia de Array.indexOf

    usuario578895

    14 de abril de 2011 a las 2:14

Estoy respondiendo mi propia pregunta, pero se me ocurrió una solución alternativa que me pareció interesante y pensé que sería útil compartirla.

La respuesta de los lobos me dio una idea. Proporcionar un objeto toString() El método identifica de manera única la instancia, las propiedades de un objeto se pueden usar para almacenar un conjunto de objetos. Esencialmente, para almacenar objetos xpuedes usar items[x.toString()] = x;. Tenga en cuenta que el valor es el objeto en sí mismo, por lo que el conjunto de objetos se puede extraer mirando todos itemy volcando todos los valores en una matriz.

Aquí está la clase, a la que llamo ObjectSet, en su totalidad. Requiere que los objetos se identifiquen de forma única por su toString() método, que está bien para mis propósitos. add, remove y contains todos deberían ejecutarse mejor que el tiempo O (n), cualquiera que sea la eficiencia de acceso a la propiedad de javascript, que con suerte es O (1) u O (n log n).

// Set of objects.  Requires a .toString() overload to distinguish objects.
var ObjectSet = function ()
{
    this.items = {};
    this.item_count = 0;
};

ObjectSet.prototype.contains = function (x)
{
    return this.items.hasOwnProperty(x.toString());
};

ObjectSet.prototype.add = function (x)
{
    if (!this.contains(x))
    {
        this.items[x.toString()] = x;
        this.item_count++;
    }

    return this;
};

ObjectSet.prototype.remove = function (x)
{
    if (this.contains(x))
    {
        delete this.items[x.toString()];
        this.item_count--;
    }

    return this;
};

ObjectSet.prototype.clear = function ()
{
    this.items = {};
    this.item_count = 0;

    return this;
};

ObjectSet.prototype.isEmpty = function ()
{
    return this.item_count === 0;
};

ObjectSet.prototype.count = function ()
{
    return this.item_count;
};

ObjectSet.prototype.values = function ()
{
    var i, ret = [];

    for (i in this.items)
    {
        if (this.items.hasOwnProperty(i))
            ret.push(this.items[i]);
    }

    return ret;
};

Usé Map, resolví mi caso

const objectsMap = new Map();
const placesName = [
  { place: "here", name: "stuff" },
  { place: "there", name: "morestuff" },
  { place: "there", name: "morestuff" },
];
placesName.forEach((object) => {
  objectsMap.set(object.place, object);
});
console.log(objectsMap);

  • Solución perfecta.. Gracias

    – Yehya

    20 de septiembre a las 13:16

Para lo que está tratando de hacer (conjuntos de objetos), no hay una implementación nativa de Javascript. Tendrías que implementar esto por tu cuenta. Una forma de hacerlo sería implementar una función hash para sus objetos. El tipo de datos de respaldo del conjunto sería una matriz asociativa, donde la clave de la matriz es el valor que obtiene al llamar a la función hash del objeto, y el valor de la matriz es el objeto mismo.

Por supuesto, esto no aborda el problema que destacó, por lo que también deberá tener en cuenta la igualdad (quizás implementar una función de igualdad)?

En lugar de hacer que la función hash sea una propiedad del objeto en sí, puede tener una función hash independiente que tome un objeto como entrada y genere un valor hash (presumiblemente iterando sobre sus propiedades).

Con este método, debería poder obtener O(1) para insertar, buscar y eliminar (sin contar el orden de la función hash, que no debería ser peor que O(n)especialmente si está iterando sobre sus propiedades para crear su valor hash).

  • Solución perfecta.. Gracias

    – Yehya

    20 de septiembre a las 13:16

ECMAScript6 Set debería comportarse así:

Ejemplo de trabajo en Firefox 32 (pero no implementado en Chromium 37):

if (Set) {
  var s = new Set()
  var a = {}
  var b = {}
  var c = {}
  s.add(a)
  s.add(b)
  s.add(b)
  assert(s.size === 2)
  assert(s.has(a))
  assert(s.has(b))
  assert(!s.has(c))
}

Esto no es sorprendente ya que {} != {}: la igualdad compara las direcciones de los objetos por defecto.

Un módulo que lo implementa para navegadores sin soporte: https://github.com/medikoo/es6-set

¿Ha sido útil esta solución?