Javascript 使用元组或对象进行映射

Javascript 使用元组或对象进行映射,javascript,map,equality,ecmascript-harmony,Javascript,Map,Equality,Ecmascript Harmony,我试图使用新的(ES6)对象来表示属性和值之间的映射 我的对象的形式类似于: {key1:value1_1,key2:value2_1},..... {key1:value1_N,key2:value2_N} 我想根据它们的key1和key2值对它们进行分组 例如,我希望能够通过x和y对以下内容进行分组: [{x:3,y:5,z:3},{x:3,y:4,z:4},{x:3,y:4,z:7},{x:3,y:1,z:1},{x:3,y:5,z:4}] 并获取包含以下内容的地图: {x:3,y:

我试图使用新的(ES6)对象来表示属性和值之间的映射

我的对象的形式类似于:

 {key1:value1_1,key2:value2_1},..... {key1:value1_N,key2:value2_N}
我想根据它们的key1和key2值对它们进行分组

例如,我希望能够通过
x
y
对以下内容进行分组:

[{x:3,y:5,z:3},{x:3,y:4,z:4},{x:3,y:4,z:7},{x:3,y:1,z:1},{x:3,y:5,z:4}]
并获取包含以下内容的地图:

{x:3,y:5} ==>  {x:3,y:5,z:3},{x:3,y:5,z:4}
{x:3,y:4} ==>  {x:3,y:4,z:4},{x:3,y:4,z:7}
{x:3,y:1} ==>  {x:3,y:1,z:1}
在Python中,我使用元组作为字典键。ES6 map允许任意对象作为键,但使用标准的相等算法(
===
),因此根据我的判断,对象仅通过引用相等

如何使用ES6映射完成这种分组?或者,如果我忽略了一种优雅的方式,那么可以使用普通JS对象的解决方案


我不想使用外部收藏库,但如果有更好的解决方案,我也有兴趣了解它。

这似乎不太可能。你能做什么?像往常一样,有些可怕的事情

let tuple = (function() {
    let map = new Map();

    function tuple() {
        let current = map;
        let args = Object.freeze(Array.prototype.slice.call(arguments));

        for (let item of args) {
            if (current.has(item)) {
                current = current.get(item);
            } else {
                let next = new Map();
                current.set(item, next);
                current = next;
            }
        }

        if (!current.final) {
            current.final = args;
        }

        return current.final;
    }

    return tuple;
})();

let m = new Map();
m.set(tuple(3, 5), [tuple(3, 5, 3), tuple(3, 5, 4)]);
m.get(tuple(3, 5)); // [[3, 5, 3], [3, 5, 4]]

好的,我现在已经提出了这个问题,我从Mozilla得到了一个答案:

  • 这是ES6地图的一个问题
  • 解决方案将以ES7的形式出现,用于键而不是对象
  • 以前曾考虑让人们指定
    .equals
    .hashCode
    ,但后来被拒绝,取而代之的是值对象。(我认为有充分的理由)
  • 到目前为止,唯一的解决办法是推出自己的收藏
  • Bradley在ESDiscussion线程中提供了一个基本的此类集合(概念,不用于生产代码),可能看起来像这样:

    function HashMap(hash) {
      var map = new Map;
      var _set = map.set;
      var _get = map.get;
      var _has = map.has;
      var _delete = map.delete;
      map.set = function (k,v) {
        return _set.call(map, hash(k), v);
      }
      map.get = function (k) {
        return _get.call(map, hash(k));
      }
      map.has = function (k) {
        return _has.call(map, hash(k));
      }
      map.delete = function (k) {
        return _delete.call(map, hash(k));
      }
      return map;
    }
    
    function TupleMap() {
      return new HashMap(function (tuple) {
        var keys = Object.keys(tuple).sort();
        return keys.map(function (tupleKey) { // hash based on JSON stringification
                   return JSON.stringify(tupleKey) + JSON.stringify(tuple[tupleKey]);
        }).join('\n');
        return hashed;
      });
    }
    
    一个更好的解决方案是使用类似的东西,它允许指定hash/equals函数


    您可以看到API文档。

    Benjamin的答案并不适用于所有对象,因为它依赖于JSON.stringify,它不能处理循环对象,并且可以将不同的对象映射到同一个字符串。Minitech的答案可以创建巨大的嵌套映射树,我怀疑这会导致内存和CPU效率低下,特别是对于长元组,因为它必须为元组中的每个元素创建映射

    如果您知道元组只包含数字,那么最好的解决方案是使用
    [x,y].join(',')
    作为键。如果要使用包含任意对象的元组作为键,仍然可以使用此方法,但必须首先将对象映射到唯一标识符。在下面的代码中,我使用
    get\u object\u id
    惰性地生成这些标识符,它将生成的id存储在内部映射中。然后,我可以通过连接这些ID为元组生成键。(请参阅此答案底部的代码。)

    然后可以使用
    tuple
    方法将对象的元组散列为字符串,该字符串可以用作映射中的键。这使用了对象等价性:

    x={}; y={}; 
    tuple(x,y) == tuple(x,y) // yields true
    tuple(x,x) == tuple(y,y) // yields false
    tuple(x,y) == tuple(y,x) // yields false
    
    如果确定元组只包含对象(即不为null、数字或字符串),则可以在
    get\u object\u id
    中使用WeakMap,这样
    get\u object\u id
    tuple
    就不会泄漏作为参数传递给它们的对象

    var get\u object\u id=(函数(){
    生成的var_id=1;
    var map=newmap();
    返回get\u object\u id;
    函数获取对象id(obj){
    如果(映射has(obj)){
    返回map.get(obj);
    }否则{
    var r=生成的_id++;
    地图集(obj,r);
    返回r;
    }
    }
    })();
    函数元组(){
    返回Array.prototype.map.call(参数,get_object_id).join(',');
    }
    //试验
    var data=[{x:3,y:5,z:3},{x:3,y:4,z:4},{x:3,y:4,z:7},
    {x:3,y:1,z:1},{x:3,y:5,z:4}];
    var map=newmap();
    
    对于(var i=0;i虽然这个问题已经很老了,但值对象在JavaScript中仍然不是一个存在的东西(因此人们可能仍然感兴趣),所以我决定编写一个简单的库来实现数组作为映射中键的类似行为(repo here:)。库的设计基本上与map相同,只是数组按元素而不是按标识进行比较

    用法:

    var map = new ArrayMap();
    map.set([{x: 3, y: 5}], {x:3, y:5, z:10});
    map.get([{x: 3, y: 5}]); // undefined as objects within the list are
                             // treated by identity
    
    警告:但是,库按标识处理关键元素,因此以下操作无效:

    var serialize = function(point) {
        return [point.x, point.y];
    };
    var map = new ArrayMap(null, serialize);
    map.set({x: 10, y: 20}, {x: 10, y: 20, z: 30});
    map.get({x: 10, y: 20}); // {x: 10, y: 20, z: 30}
    
    但是,只要可以将数据序列化为基本体数组,就可以按如下方式使用ArrayMap:


    多年过去了,这仍然是JavaScript的一个问题。我改进了Jamesernator的方法并创建了该包。现在您可以得到您想要的:

    从“集合深度相等”导入{MapDeepEqual,SetDeepEqual};
    const object={name:“Leandro”,年龄:29};
    const deepEqualObject={name:“Leandro”,年龄:29};
    const mapDeepEqual=新的mapDeepEqual();
    mapDeepEqual.set(对象,“值”);
    断言(mapdepequal.get(object)=“value”);
    断言(mapDeepEqual.get(deepEqualObject)=“值”);
    const setDeepEqual=新setDeepEqual();
    setDeepEqual.add(对象);
    断言(setDeepEqual.has(object));
    断言(setDeepEqual.has(deepEqualObject));
    
    我不确定是否有可能滥用
    WeakMap
    来提高内存效率。可能没有。你刚才是不是在这里实现了一个元组flyweight:O.+1来提高创造力,但我真的认为这应该在语言层面上解决。请解释为什么你认为我关于WeakMap的句子是错误的。另外,如果你的answer可以不使用JSON.stringify,请更新您的答案以解释如何使用。使用WeakMap可确保元组中使用的对象仍有资格进行垃圾收集。即,如果WeakMap是唯一引用对象的对象,垃圾收集器可以销毁该对象(并将其从WeakMap中删除)。因此,它避免了内存泄漏。对象在WeakMap中用作键,因此如果它们符合垃圾收集的条件,则没有问题。映射用于使用唯一标识符扩展对象,而不实际更改对象。--要用另一个哈希函数替换答案中的JSON.stringify,还需要创建其他此类哈希函数函数,这很难。我的
    tuple()
    就是这样一个散列函数,
    var serialize = function(point) {
        return [point.x, point.y];
    };
    var map = new ArrayMap(null, serialize);
    map.set({x: 10, y: 20}, {x: 10, y: 20, z: 30});
    map.get({x: 10, y: 20}); // {x: 10, y: 20, z: 30}