Набор объектов в javascript
Я хотел бы иметь набор объектов в Javascript. То есть структура данных, содержащая только уникальные объекты.
Рекомендуется использовать обычно используемые свойства, например. myset["key"] = true
. Однако мне нужны ключи, чтобы быть объектами. Я прочитал, что Javascript передает имена свойств в строки, поэтому, я думаю, я не могу использовать myset[myobject] = true
.
Я мог бы использовать массив, но мне нужно что-то лучше, чем O (n) для добавления, поиска и удаления элементов.
Он должен иметь возможность указывать объекты отдельно только по ссылке, поэтому дано:
var a = {};
var b = {};
тогда должны быть добавлены как a
, так и b
, потому что они являются отдельными объектами.
В принципе, я за чем-то вроде С++ std::set
, который может хранить объекты Javascript. Любые идеи?
Ответы
Ответ 1
ES6 предоставляет нативный Map
:
let m = new Map();
let a = {};
let b = {};
m.set(a, b);
m.set(b, 10);
m.get(m.get(a)) // 10
Вот один из них, который я уже давно, примерно так же близок к естественной структуре, как вы можете получить:
function map() {
var keys = [];
var values = [];
function get(key) {
var i = keys.indexOf(key);
return i === -1 ? null : values[i];
}
get.set = function set(key, value) {
var i = keys.indexOf(key);
if (i === -1) {
keys.push(key);
values.push(value);
} else {
values[i] = value;
}
};
return get;
}
Используйте его так:
var m = map();
var a = {};
var b = {};
m.set(a, b);
m.set(b, 10);
m(m(a)); // 10
Чтобы удалить значение, просто используйте x.set(whateverKey, null)
, потому что он возвращает null, когда ключ не найден, поэтому нет разницы.
Ответ 2
Это невозможно для всех объектов, но если ваш объект имеет метод .toString()
, он:
var x = {toString: function(){ return 'foo'; }};
var y = {toString: function(){ return 'bar'; }};
var obj = {};
obj[x] = 'X';
obj[y] = 'Y';
console.log(obj);
// { foo: 'X', bar: 'Y' }
Если вы хотите сделать это проще, сделайте его классом:
function myObj(name){
this.name = name;
}
myObj.prototype.toString = function(){ return this.name; }
var obj = {};
obj[new myObj('foo')] = 'X';
obj[new myObj('bar')] = 'Y';
Ответ 3
Для того, что вы пытаетесь сделать (наборы объектов), нет встроенной реализации Javascript. Вам придется реализовать это самостоятельно. Один из способов сделать это - реализовать функцию хэширования для ваших объектов. Тип данных поддержки набора будет ассоциативным массивом, где ключом массива является значение, которое вы получаете от вызова хэш-функции объекта, а значение массива - это сам объект.
Конечно, это не относится к проблеме, которую вы выделили, поэтому вам также необходимо принять во внимание равенство (возможно, реализовать функцию равенства)?
Вместо того, чтобы сделать хэш-функцию собственностью самого объекта, вы можете иметь автономную хеш-функцию, которая принимает объект как входную и генерирует хеш-значение (предположительно путем итерации по его свойствам).
Используя этот метод, вы сможете получить O(1)
для вставки, поиска и удаления (не считая порядка хэш-функции, которая не должна быть хуже, чем O(n)
, особенно если вы повторяете его свойства, чтобы создать ваше хешированное значение).
Ответ 4
Здесь безумное предложение... введите его в результат JSON.stringify(object)
Ответ 5
ECMAScript6 Set
должен вести себя следующим образом:
Рабочий пример в Firefox 32 (но не реализованный в Chromium 37):
if (Set) {
var s = new Set()
var a = {}
var b = {}
var c = {}
s.add(a)
s.add(b)
s.add(b)
assert(s.size === 2)
assert(s.has(a))
assert(s.has(b))
assert(!s.has(c))
}
Это не удивительно, поскольку {} != {}
: равенство сравнивает адреса объектов по умолчанию.
Модуль, который реализует его для браузеров без поддержки: https://github.com/medikoo/es6-set
Ответ 6
Я отвечаю на свой вопрос, но я придумал альтернативное решение, которое я считал интересным, и подумал, что было бы полезно поделиться им.
Ответ cwolves дал мне идею. Предоставление объекта toString()
метод уникально идентифицирует экземпляр, свойства объекта могут использоваться для хранения набора объектов. По существу, для хранения объекта x
вы можете использовать items[x.toString()] = x;
. Обратите внимание, что это значение является самим объектом, поэтому набор объектов можно извлечь, просмотрев все свойства item
и сбросив все значения в массив.
Здесь класс, который я называю ObjectSet
, полностью. Это требует, чтобы объекты были уникально идентифицированы с помощью метода toString()
, который подходит для моих целей. add
, remove
и contains
должны выполняться лучше, чем время O (n) - независимо от эффективности доступа к ресурсу javascript, который, надеюсь, является либо O (1), либо O (n log n).
// Set of objects. Requires a .toString() overload to distinguish objects.
var ObjectSet = function ()
{
this.items = {};
this.item_count = 0;
};
ObjectSet.prototype.contains = function (x)
{
return this.items.hasOwnProperty(x.toString());
};
ObjectSet.prototype.add = function (x)
{
if (!this.contains(x))
{
this.items[x.toString()] = x;
this.item_count++;
}
return this;
};
ObjectSet.prototype.remove = function (x)
{
if (this.contains(x))
{
delete this.items[x.toString()];
this.item_count--;
}
return this;
};
ObjectSet.prototype.clear = function ()
{
this.items = {};
this.item_count = 0;
return this;
};
ObjectSet.prototype.isEmpty = function ()
{
return this.item_count === 0;
};
ObjectSet.prototype.count = function ()
{
return this.item_count;
};
ObjectSet.prototype.values = function ()
{
var i, ret = [];
for (i in this.items)
{
if (this.items.hasOwnProperty(i))
ret.push(this.items[i]);
}
return ret;
};
Ответ 7
Просто набрал это, он только кратко протестировал:
var Set = function Set()
{
var list = [];
var contains;
this.contains = contains = function(x) {
return list.indexOf(x) >= 0;
}
var put;
this.put = put = function(x) {
if (!contains(x))
list.push(x);
return this;
}
var remove;
this.remove = remove = function(x)
{
var idx = list.indexOf(x);
if (idx >= 0)
list.splice(idx,1);
return this;
}
var all;
this.all = all = function()
{
return list.concat();
}
return this;
}
Ответ 8
Кажется, что внутренний вызов функции работает, когда префикс с этим.
Exemple:
var put;
this.put = put = function(x) {
if (!this.contains(x))
list.push(x);
return this;
}