41

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

我有类似于以下形式的对象:

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

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

例如,我希望能够通过xand对以下内容进行分组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 映射允许任意对象作为键,但使用标准的相等算法 ( ===),因此对象只能通过引用来判断。

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

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

4

6 回答 6

24

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

  1. ES6 映射的问题。
  2. 解决方案将以 ES7值对象的形式出现,而不是对象。
  3. 之前考虑过让人们指定.equals.hashCode但它被拒绝支持价值对象。(我认为有充分的理由)。
  4. 目前唯一的解决方案是推出自己的收藏。

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 文档。

于 2014-02-18T06:49:27.647 回答
9

这似乎不太可能。你能做什么?一如既往的可怕。

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]]
于 2014-02-17T21:08:17.487 回答
1

本杰明的答案并不适用于所有对象,因为它依赖于 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

如果您确定您的元组将只包含对象(即不为空、数字或字符串),那么您可以使用 Wea​​kMap in get_object_id,这样get_object_idtuple不会泄漏作为参数传递给它们的对象。

var get_object_id = (function() {
  var generated_ids = 1;
  var map = new Map();
  return get_object_id;
  function get_object_id(obj) {
    if (map.has(obj)) {
      return map.get(obj);
    } else {
      var r = generated_ids++;
      map.set(obj, r);
      return r;
    }
  }
})();

function tuple() {
  return Array.prototype.map.call(arguments, get_object_id).join(',');
}

// Test
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 = new Map();
for (var i=0; i<data.length; i++) {
  var p = data[i];
  var t = tuple(p.x,p.y);
  if (!map.has(t)) map.set(t,[]);
  map.get(t).push(p);
}

function test(p) {
  document.writeln((JSON.stringify(p)+' ==> ' + 
    JSON.stringify(map.get(tuple(p.x,p.y)))).replace(/"/g,''));
}

document.writeln('<pre>');
test({x:3,y:5});
test({x:3,y:4});
test({x:3,y:1});
document.writeln('</pre>');

于 2015-09-01T09:53:59.580 回答
1

多年过去了,这仍然是 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));
于 2020-03-10T18:30:56.133 回答
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}
于 2016-03-07T11:43:27.970 回答
0

元组的另一个代码。

const tuple = (() => {
  const tpls = [];
  return (...args) => {
    let val = tpls.find(tpl => tpl.length === args.length && tpl.every((v,i) => v === args[i]));
    if(val == null) {
      val = Object.freeze([...args]);
      tpls.push(val);
    }
    return val;
  };
})();

//Usage
console.assert(tuple(1, 2, 3, foo) === tuple(1, 2, 3, foo));
//But as bcmpinc says, different objects are not equal.
console.assert(tuple({}) !== tuple({}));

function foo() {}

const map = new Map();
map.set(tuple(1, 2, 3, foo), 'abc');
map.set(tuple(1, 2, 3, foo), 'zzz');
console.log(map.get(tuple(1, 2, 3, foo)));  // --> 'zzz'
于 2021-11-29T02:21:08.553 回答