javascript中的一组对象

Set of objects in javascript

本文关键字:一组 对象 javascript      更新时间:2023-09-26

我想在Javascript中有一组对象。即只包含唯一对象的数据结构。

建议正常使用属性,如myset["key"] = true。但是,我需要键是对象。我读到Javascript将属性名称转换为字符串,所以我想我不能使用myset[myobject] = true

我可以使用数组,但我需要比0 (n)性能更好的东西来添加、查找和删除项。

它需要能够仅通过引用来区分对象,因此给定:

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

那么ab应该都能加入,因为它们是分开的物体。

基本上,我之后的东西像c++的std::set,可以存储Javascript对象。什么好主意吗?

ES6提供原生Set:

let s = new Set();
let a = {};
let b = {};
s.add(a);
console.log(s.has(a));  // true
console.log(s.has(b));  // false

这是一个疯狂的建议…在JSON.stringify(object)的结果

我在回答我自己的问题,但我想到了一个我认为有趣的替代解决方案,并认为它将是有用的分享。

狼的回答给了我一个主意。提供对象的toString()方法唯一标识实例,对象的属性可用于存储一组对象。实际上,要存储对象x,可以使用items[x.toString()] = x;。请注意,该值就是对象本身,因此可以通过查看所有item的属性并将所有值转储到数组中来提取对象集。

这是类,我称之为ObjectSet,完整的。它要求对象由它们的toString()方法唯一标识,这对于我的目的来说是可以的。add, removecontains都应该运行在比O(n)时间更好的地方-无论javascript的属性访问效率是多少,希望是O(1)或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;
};

这是不可能的所有对象,但如果你的对象有.toString()方法实现,它是:

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' }

如果你想让这更容易,把它变成一个类:

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';

我使用Map,解决了我的案子

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

对于您正在尝试做的(对象集),没有原生Javascript实现。您必须自己实现它。一种方法是为对象实现散列函数。该集合的支持数据类型将是一个关联数组,其中数组的键是调用对象的哈希函数获得的值,数组的值是对象本身。

当然,这并不能解决你强调的问题,所以你也需要考虑到平等(也许实现一个equals函数)?

不需要让哈希函数成为对象本身的属性,你可以有一个独立的哈希函数,它接受一个对象作为输入,并生成一个哈希值(大概是通过迭代它的属性)。

使用此方法,您应该能够获得O(1)进行插入、搜索和删除(不计算散列函数的顺序,这应该不会比O(n)差,特别是如果您正在迭代其属性以创建散列值)。

ECMAScript6 Set 应该是这样的:

  • 标准:http://www.ecma-international.org/ecma-262/6.0/sec-set-o-p-v-throw
  • 非官方ES6备忘单:https://github.com/lukehoban/es6features#map--set--weakmap--weakset

Firefox 32上的工作示例(但未在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))
}

这并不奇怪,因为{} != {}: equal默认比较对象地址。

为不支持的浏览器实现的模块:https://github.com/medikoo/es6-set

Javascript Set's不做深度对象比较。

使用lodash,这是一个具有深度对象比较的唯一数组:

const objects = [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }, { 'x': 1, 'y': 2 }];
 
_.uniqWith(objects, _.isEqual);

给定一个以下类型的数组:

Array<{ foo: T1, bar: T2 }>

你可以建立一个类型为:

的对应字典
{ [foo: T1]: Set<T2> }

查找{ foo: fooValue, bar: barValue }可以按如下方式执行:

if (fooValue in dictionary && dictionary[fooValue].has(barValue))

这样我们就可以构建一个ObjectSet<T1, T2>.如果现在有三个元素,可以构建以下字典:

{ [foo: T1]: ObjectSet<T2, T3> }

并通过归纳将ObjectSet扩展为任意数量的属性。

假设您的类型可以用作索引签名。

刚刚输入了这个,只是简单地测试一下:

var Set = function Set()
{
    var list = [];
    var contains;
    this.contains = contains = function(x) {
        return list.indexOf(x) >= 0;
    }
    var put;
    this.put = put = function(x) {
        if (!contains(x))
            list.push(x);
        return this;
    }
    var remove;
    this.remove = remove = function(x)
    {
        var idx = list.indexOf(x);
        if (idx >= 0)
            list.splice(idx,1);
        return this;
    }
    var all;
    this.all = all = function()
    {
        return list.concat();
    }
    return this;
}

似乎当以这个为前缀时,函数的内部调用可以工作。例:

var put;
this.put = put = function(x) {
    if (!this.contains(x))
        list.push(x);
    return this;
}

不存在像std::set这样的原生ecmascript类,您可以在其中传递比较器函数。当然,您可以像在任何其他编程语言中一样实现此数据结构。但对于大多数用例,我建议使用更简单的方法:

  • 如果你的对象有某种键或id字段,并且你想要O(1)操作,如查找/添加/更新/删除,你应该使用使用这个id字段的映射。
  • 如果你没有键,只是想避免集合中的重复,你可以使用map甚至set,使用JSON.stringify(o, Object.keys(o).sort())作为id。如果你有嵌套的对象或数组,你也应该缩短它的项目(我没有写那部分)。

我写了一个类,可以匹配:

class ObjectSet {
    constructor(iterable) {
        this.map = new Map()
        if(!iterable) {
            return;
        }
        for(o of iterable) {
            this.map.set(this._getId(o), o);
        }
    }
    _getId(o) {
        return o.id ? o.id :
            JSON.stringify(o, Object.keys(o).sort());
    }
    get(id) {
        return this.map.get(id);
    }
    has(o) {
        return this.map.has(this._getId(o));
    }
    //add or update. Call "add" if you want to match ES6 Set interface
    set(o) {
        this.map.set(this._getId(o), o);
    }
    delete(o) {
        this.map.delete(this._getId(o));
    }
    values() {
        return this.map.values();
    }
    [Symbol.iterator] () {
        return this.values();
    }
    get size() {
        return this.map.size;
    }
}
//first use case example, fast find and update:
let objSet = new ObjectSet();
//add
objSet.set({
    id: 1,
    name: "John",
    age: 30
});
const eric = {
    id: 2,
    name: "Eric",
    age: 27
}
//add
objSet.set(eric)
console.log(objSet.size); //2
//update object of id 2
objSet.set({
    id: 2,
    name: "Eric",
    age: 28
})
console.log(objSet.size); // still 2

for(o of objSet) {
    console.log(o);
}
/*
prints:
{ id: 1, name: 'John Abbot', age: 30 }
{ id: 2, name: 'Eric', age: 28 }
*/
objSet.delete({id: 1});//deletes john
objSet.delete(eric);
console.log(objSet.size);//0

//second use case, lets remove duplicated objects using this class
let points = [
    {
        x: 10,
        y: 40
    },
    {
        x: 10,
        y: 40
    },
    {
        x: 10,
        y: 35
    }
]

//lets remove duplicated objects using ObjectSet:
objSet = new ObjectSet(points);
console.log(o.size);//2
for(o of objSet) {
    console.log(o);
}
//same with set method
objSet.set({
    x: 10,
    y: 35
});
console.log(objSet.size);//still 2

请使用此代码作为参考。

const fruits = [
  {name: 'apple', price: 100},
  {name: 'apple', price: 100},
  {name: 'orange', price: 200},
  {name: 'grapes', price: 300}
];
const hasFruitDuplicated = () => {
  const duplicatedDeleteFruits = fruits.filter((fruit, index) =>
    fruits.findIndex(item => item.name === fruit.name && item.price === fruit.price) === index
  );
  return duplicatedDeleteFruits;
};