Skip to content

Instantly share code, notes, and snippets.

@phillippelevidad
Created November 3, 2023 10:03
Show Gist options
  • Save phillippelevidad/d44aef576dd9f97e046987302992dbf8 to your computer and use it in GitHub Desktop.
Save phillippelevidad/d44aef576dd9f97e046987302992dbf8 to your computer and use it in GitHub Desktop.
/**
* The UniquePropertySet class is designed to manage a collection of objects,
* ensuring that each object is unique based on its properties and values
* rather than its reference in memory.
*
* Beware, though, that this implementation is not optimized for performance,
* and that the JSON.stringify method does not handle non-serializable values
* consistently, such as functions, undefined, or circular references, which
* will cause objects containing such values to not be handled correctly.
*/
class UniquePropertySet {
constructor() {
this.map = new Map();
}
add(obj) {
const key = this._serialize(obj);
if (!this.map.has(key)) {
this.map.set(key, obj);
}
}
has(obj) {
const key = this._serialize(obj);
return this.map.has(key);
}
delete(obj) {
const key = this._serialize(obj);
return this.map.delete(key);
}
_serialize(obj) {
// Sort the object's keys to ensure consistent order
return JSON.stringify(obj, Object.keys(obj).sort());
}
clear() {
return this.map.clear();
}
get size() {
return this.map.size;
}
values() {
return this.map.values();
}
*[Symbol.iterator]() {
for (let value of this.map.values()) yield value;
}
}
// Usage
const assert = require("assert");
const uniqueObjects = new UniquePropertySet();
uniqueObjects.add({ a: 1, b: 2 });
uniqueObjects.add({ b: 2, a: 1 }); // Won't be added, as it's considered a duplicate
assert.strictEqual(uniqueObjects.size, 1);
uniqueObjects.add({ a: 1, b: 2, c: 3 }); // Will be added, as it's a different object
assert.strictEqual(uniqueObjects.size, 2);
uniqueObjects.delete({ a: 1, b: 2 }); // Correctly deletes the first entry
assert.strictEqual(uniqueObjects.size, 1);
// Nested objects are supported too
uniqueObjects.clear();
uniqueObjects.add({ a: 1, b: { c: 2 } });
uniqueObjects.add({ a: 1, b: { c: 2 } });
assert.strictEqual(uniqueObjects.size, 1);
// Even nested objects are supported
uniqueObjects.clear();
uniqueObjects.add({ a: 1, b: { d: 3, c: 2 } });
uniqueObjects.add({ a: 1, b: { c: 2, d: 3 } });
assert.strictEqual(uniqueObjects.size, 1);
@phillippelevidad
Copy link
Author

Vi meu comentário na aula de Set e WeakSet. Lá eu falei que essa solução que eu botei aqui no Gist tem um problema inerente de performance porque usa JSON.stringify. Daí, se eu entendi bem a sua dúvida aqui, é porque você está curioso se fazer um for..of resolveria, ou seja, se seria mais rápido.

Em tese, sim :) mas só tem um jeito da gente saber, e é fazendo um benchmark. O Erick ensina isso mais à frente no curso, mas bora rodar um benchmark aqui rapidão e ver o que acontece.


Rodei o código abaixo 3 vezes, pra gente ter uma análise melhor de 3, e olha só os tempos:

deepEqual x 941,275 ops/sec ±2.13% (85 runs sampled)
JSON.stringify x 94,887 ops/sec ±1.91% (88 runs sampled)
Fastest is deepEqual

deepEqual x 921,202 ops/sec ±2.98% (82 runs sampled)
JSON.stringify x 91,463 ops/sec ±1.53% (84 runs sampled)
Fastest is deepEqual

deepEqual x 944,692 ops/sec ±1.74% (85 runs sampled)
JSON.stringify x 88,985 ops/sec ±2.92% (77 runs sampled)
Fastest is deepEqual

Você pode ver que, no último teste, o deepEqual rodou 944.692 por segundo, contra apenas 88.985 do JSON.stringify. E os outros dois testes não ficam muito diferentes, então podemos que, na média, o deepEqual que mandei pra você no comentário acima é 10x mais rápido que o JSON.stringify!

Segue o código do teste, usando a lib https://www.npmjs.com/package/benchmark. Eu crio dois objetos idênticos e deixo rodar a comparação nas duas formas que estamos testando.

const deepEqual = require("./deepEqual.js");
const Benchmark = require("benchmark");

const obj1 = {
  firstName: "John",
  lastName: "Doe",
  dateOfBirth: new Date("1980-01-01"),
  address: {
    street: "North Pole",
    city: "Arctic Circle",
    country: "Earth",
  },
  orders: [
    {
      product: {
        name: "iPhone",
        price: 699,
      },
      quantity: 1,
    },
    {
      product: {
        name: "MacBook Pro",
        price: 1299,
      },
      quantity: 2,
    },
  ],
};

const obj2 = {
  firstName: "John",
  lastName: "Doe",
  dateOfBirth: new Date("1980-01-01"),
  address: {
    street: "North Pole",
    city: "Arctic Circle",
    country: "Earth",
  },
  orders: [
    {
      product: {
        name: "iPhone",
        price: 699,
      },
      quantity: 1,
    },
    {
      product: {
        name: "MacBook Pro",
        price: 1299,
      },
      quantity: 2,
    },
  ],
};

const suite = new Benchmark.Suite();

suite
  // add tests
  .add("deepEqual", function () {
    return deepEqual(obj1, obj2);
  })
  .add("JSON.stringify", function () {
    return JSON.stringify(obj1) === JSON.stringify(obj2);
  })
  // add listeners
  .on("cycle", function (event) {
    console.log(String(event.target));
  })
  .on("complete", function () {
    console.log("Fastest is " + this.filter("fastest").map("name"));
  })
  // run async
  .run({ async: true });

Massa, né :)

@ganes369
Copy link

ganes369 commented Dec 2, 2023

Opa, iae blz? era isso msm "Daí, se eu entendi bem a sua dúvida aqui, é porque você está curioso se fazer um for..of resolveria, ou seja, se seria mais rápido" eu ainda ñ chegai nesse módulo ainda "benchmark" msm valeuzão pela resposta...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment