Подтвердить что ты не робот

Карта с использованием кортежей или объектов

Я пытаюсь использовать новые объекты (ES6) Map, чтобы представлять карту между свойствами и значением.

У меня есть объекты в форме, подобной:

 {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 допускает произвольные объекты как ключи, но использует стандартный алгоритм равенства (===), поэтому объекты только равны по ссылке из того, что я могу сказать.

Как я могу выполнить подобную группировку с помощью карт ES6? Альтернативно, решение, использующее обычные объекты JS, если есть изящный способ, который я пропускал.

Я бы предпочел не использовать внешнюю коллекционную библиотеку, но если есть лучшее решение, использующее одно, мне тоже интересно узнать об этом.

4b9b3361

Ответ 1

Хорошо, теперь я поднял вопрос о esdiscuss, и я получил ответ от Mozilla Джейсон Орендорф:

  • Это проблема с картами ES6.
  • Решение будет представлено в виде ES7 объектов значений для ключей вместо объектов.
  • Раньше считалось, что люди указывают .equals и .hashCode, но они были отвергнуты в пользу объектов значений. (по уважительным причинам, на мой взгляд).
  • Единственным решением на данный момент является сворачивание собственной коллекции.

Базовая такая коллекция (концепция, не использующая в производственном коде) была предложена Брэдли в потоке 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, которое позволяет специфицировать функции hash/equals.

Здесь вы можете увидеть документы API .

Ответ 2

Это не представляется возможным. Что ты можешь сделать? Что-то ужасное, как всегда.

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

Ответ 3

Ответ Benjamin не работает для всех объектов, поскольку он полагается на JSON.stringify, который не может обрабатывать круглые объекты и может сопоставлять разные объекты с одной и той же строкой. Ответ Minitech может создавать огромные деревья вложенных карт, которые, как я подозреваю, являются как памятью, так и неэффективной CPU, особенно для длинных кортежей, поскольку она должна создавать карту для каждого элемента в кортеже.

Если вы знаете, что ваши кортежи содержат только цифры, лучшим решением является использование [x,y].join(',') в качестве ключа. Если вы хотите использовать кортежи, содержащие произвольные объекты в качестве ключей, вы все равно можете использовать этот метод, но сначала должны сопоставить объекты с уникальными идентификаторами. В приведенном ниже коде я генерирую эти идентификаторы лениво, используя get_object_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

Если вы уверены, что ваши кортежи будут содержать только объекты (т.е. не нуль, числа или строки), тогда вы можете использовать WeakMap в get_object_id, чтобы get_object_id и tuple не пропустили объекты которые передаются в качестве аргумента для них.

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

Ответ 4

В то время как этот вопрос довольно старый, объекты ценности по-прежнему не являются существующими в JavaScript (поэтому люди все равно могут быть заинтересованы), поэтому я решил написать простую библиотеку, чтобы выполнить аналогичное поведение для массивов в виде ключей на картах (здесь репо: https://github.com/Jamesernator/es6-array-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}