web-dev-qa-db-ja.com

タプルまたはオブジェクトを使用したマップ

プロパティと値の間のマップを表すために、新しい(ES6) Map オブジェクトを使用しようとしています。

次のような形式のオブジェクトがあります。

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

bothkey1key2の値に基づいてグループ化します。

たとえば、以下を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オブジェクトを使用するソリューション。

私は外部のコレクションライブラリを使用したくないのですが、それを使用したより良い解決策があれば、それについても学びたいと思っています。

32

さて、私は esdiscuss で問題を提起し、Mozillaの Jason Orendorff から回答を得ました:

  1. これES6マップの問題です。
  2. ソリューションはES7 値オブジェクト の形式で提供され、オブジェクトではなくキーが使用されます。
  3. 以前は、人々に.equals.hashCodeを指定させることが検討されていましたが、値オブジェクトを優先して拒否されました。 (私の意見では正当な理由で)。
  4. 現在のところ唯一の解決策は、独自のコレクションをロールすることです。

基本的なそのようなコレクション(概念、製品コードでは使用しない)は、ESDiscussスレッドでBradleyによって提供され、次のようになります。

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ドキュメントを見ることができます ここ

17

それは都合の良いようには見えません。あなたは何ができますか?いつものように、何か恐ろしい。

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

ベンジャミンの答えは、JSON.stringifyに依存しているため、すべてのオブジェクトで機能するわけではありません。JSON.stringifyは、循環オブジェクトを処理できず、異なるオブジェクトを同じ文字列にマップできます。 Minitechの答えは、ネストされたマップの巨大なツリーを作成する可能性があります。これは、タプルの各要素のマップを作成する必要があるため、特に長いタプルの場合、メモリとCPUの両方で非効率的であると思われます。

タプルに含まれているのが数値のみであることがわかっている場合、最善の解決策は[x,y].join(',')をキーとして使用することです。任意のオブジェクトを含むタプルをキーとして使用する場合でも、このメソッドを使用できますが、最初にオブジェクトを一意の識別子にマップする必要があります。以下のコードでは、生成されたIDを内部マップに格納するget_object_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_object_idでWeakMapを使用して、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>');
1
bcmpinc

この質問はかなり古いですが、値オブジェクトはまだJavaScriptの既存のものではないので(人々はまだ興味があるかもしれません)、マップのキーとして配列に対して同様の動作を実行する単純なライブラリを書くことにしました(ここにレポ: https://github.com/Jamesernator/es6-array-map )。ライブラリは、配列がIDではなく要素ごとに比較されることを除いて、基本的にマップと同じように設計されています。

使用法:

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}

警告:ただし、ライブラリは重要な要素をIDで処理するため、以下は機能しません。

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}
0
Jamesernator

もう何年も経ちましたが、これはまだ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));
0