使用元组或对象映射

时间:2014-02-17 20:13:18

标签: javascript map equality ecmascript-harmony

我正在尝试使用新的(ES6)Map对象来表示属性和值之间的映射。

我的对象形式类似于:

 {key1:value1_1,key2:value2_1},..... {key1:value1_N,key2:value2_N}

我想根据 的key1 key2值对它们进行分组。

例如,我希望能够按xy对以下内容进行分组:

[{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映射允许任意对象作为键,但使用标准相等算法(===),因此对象只能通过引用来区分我所知道的。

如何使用ES6地图完成此类分组?或者,如果我忽略了一种优雅的方式,使用普通JS对象的解决方案。

我宁愿不使用外部集合库 - 但如果有更好的解决方案使用我也有兴趣了解它。

5 个答案:

答案 0 :(得分:15)

好的,我现在在esdiscuss上提出了这个问题,我从Mozilla的Jason Orendorff得到了答案:

  1. 是ES6地图的问题。
  2. 对于密钥而不是对象,解决方案将以ES7 value objects的形式出现。
  3. 之前我们考虑过让人们指定.equals.hashCode但是它被拒绝而不是有价值对象。 (我认为有充分理由)。
  4. 目前唯一的解决方案是推出自己的系列。
  5. Bradley在ESDiscuss主题上提供了一个基本的集合(概念,不要在生产代码中使用),可能看起来像这样:

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

    更好的解决方案是使用类似MontageJS/Collections的内容,允许指定哈希/等于函数。

    您可以看到API文档here

答案 1 :(得分:7)

似乎不太方便。你能做什么?像往常一样可怕的东西。

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]]

答案 2 :(得分:1)

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

如果您知道您的元组只包含数字,那么最好的解决方案是使用[x,y].join(',')作为密钥。如果要使用包含任意对象的元组作为键,您仍然可以使用此方法,但必须首先将对象映射到唯一标识符。在下面的代码中,我使用get_object_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

如果你确定你的元组只包含对象(即非空,数字或字符串),那么你可以在get_object_id中使用WeakMap,这样get_object_id和{{ 1}}不会泄漏作为参数传递给它们的对象。



tuple




答案 3 :(得分:0)

虽然这个问题很老,但是值对象仍然不是JavaScript中的现有东西(所以人们可能仍然感兴趣)所以我决定编写一个简单的库来实现数组的类似行为作为地图中的键(这里的repo: https://github.com/Jamesernator/es6-array-map)。该库的设计与使用中的map基本相同,只是数组是按元素而不是通过标识进行比较。

用法:

var map = new ArrayMap();
map.set([1,2,3], 12);
map.get([1,2,3]); // 12

map.set(['cats', 'hats'], {potatoes: 20});
map.get(['cats', 'hats']); // {potatoes: 20}

警告:但是,库会按标识处理关键元素,因此以下内容不起作用:

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

但只要您可以将数据序列化为基元数组,就可以按如下方式使用ArrayMap:

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}

答案 4 :(得分:0)

已经过去了很多年,这仍然是JavaScript的问题。我改进了Jamesernator的方法,并创建了软件包https://www.npmjs.com/package/collections-deep-equal。现在您可以得到想要的东西:

import { MapDeepEqual, SetDeepEqual } from "collections-deep-equal";

const object = { name: "Leandro", age: 29 };
const deepEqualObject = { name: "Leandro", age: 29 };

const mapDeepEqual = new MapDeepEqual();
mapDeepEqual.set(object, "value");
assert(mapDeepEqual.get(object) === "value");
assert(mapDeepEqual.get(deepEqualObject) === "value");

const setDeepEqual = new SetDeepEqual();
setDeepEqual.add(object);
assert(setDeepEqual.has(object));
assert(setDeepEqual.has(deepEqualObject));