2016-02-09 2 views
7

Резюме: Есть ли более быстрый способ для хэш-объектов, чем JSON.stringify?Эффективное запоминание аргументов объекта

Подробности: У меня есть библиотека Ruby и JavaScript (NeatJSON), которая обеспечивает довольно-типографию значений JavaScript. Недавно я исправил проблему, когда глубоко вложенные объекты вызывают производительность O (n!) (n - уровень вложенности) с использованием memoization на основе сериализуемого объекта и суммы отступа.

В Ruby, the fix было очень легко, потому что вы можете индексировать хэшей массивов уникальных наборов объектов:

build = ->(object,indent) do 
    memoizer[[object,indent]] ||= <all the rest of the code> 
end 

В JavaScript, однако, я не могу индекс объекта другим объектом (в уникальный способ). Вслед за несколько статей, которые я нашел в Интернете, я решил fix the problem обобщенно, используя JSON.stringify на полном наборе аргументов функции, чтобы создать уникальный ключ для запоминания:

function memoize(f){ 
    var memo = {}; 
    var slice = Array.prototype.slice; 
    return function(){ 
    var args = slice.call(arguments); 
    var mkey = JSON.stringify(args); 
    if (!(mkey in memo)) memo[mkey] = f.apply(this,args); 
    return memo[mkey]; 
    } 
} 

function rawBuild(o,indent){ .. } 
var build = memoize(rawBuild); 

Это работает, но (а) это немного медленнее, чем мне хотелось бы, и (б) кажется дико неэффективным (и неэлегантным) выполнять (наивную) сериализацию каждого объекта и ценности, которые я собираюсь сериализовать. Акт сериализации большого объекта со многими значениями будет хранить строку и результат форматирования для КАЖДОГО уникального значения (а не только значений листа) всего объекта.

Есть ли современный трюк JavaScript, который позволил бы мне однозначно идентифицировать ценность? Например, какой-то способ доступа к внутреннему идентификатору или связывание сложных объектов с уникальными целыми числами, которые принимают O (1) раз, чтобы найти идентификатор для значения?

+0

Очень похоже (не совсем ДУП) из [JavaScript Id объекта] (http://stackoverflow.com/q/2020670/405017). Не совсем дуп, потому что мне нужно найти уникальное представление для большинства видов значений (string, boolean, number, array, object), а не только для объектов. – Phrogz

+0

Хотите ли вы мемуазировать по стоимости объекта или ссылке? Другими словами: 'var a = {b: 1}; var c = memoizedFn (a); a.b = 2; var d = memoizedFn (a); 'должен ли второй вызов использовать memoized значение? –

+0

@TamasHegedus По ссылке вполне достаточно. – Phrogz

ответ

3

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

+0

Это выглядит очень полезно. И да, в этом случае memoization по идентификатору достаточно, так как я просто сканирую вверх и вниз значения в одном и том же дереве. – Phrogz

+0

Поскольку мне нужно поддерживать примитивные значения, это также выглядит как ['Карта'] (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map) чем «WeakMap». – Phrogz

+0

@Phrogz: Да, зависит от варианта использования. «WeakMap» может иметь лучшую эффективность памяти, если часть объекта уже может быть собрана в мусор, пока вы все еще удерживаете (или запускаете) сохраненную память. – Bergi

1

Если вам просто нужно запоминать объекты, тогда имеет смысл назначить уникальный идентификатор вашим объектам.

var gID = 0; 

function createNode() { 
    var obj = ... 
    obj.id = (++gID).toString(); 
} 

и использовать эти obj.id «S в качестве ключей в вашей memo коллекции.

Это было бы самым быстрым и наименее жадным решением.

Update:

Если вы хотите, чтобы свойство идентификатора, чтобы не конфликтовать с существующими свойствами , то вы можете создать несчетное свойство, используя стандартные ES5.1 Object.createProperty() (с некоторым уникальным именем) или использовать ES6 symbols:

var gID = 0; 
var gUidSym = Symbol("uid"); 

function getUidOf(obj) { 
    return obj[gUidSym] 
     || (obj[gUidSym] = (++gID).toString()); 
} 
+0

Две проблемы: 1) Я не управляю создаваемыми объектами. Это сериализатор JSON, где люди передают мне свои объекты, и я их обрабатываю. 2) Поскольку я сериализую свойства объекта, свойство 'id', которое вы предлагаете здесь, будет включено в сериализацию (и, возможно, конфликт с существующим свойством). – Phrogz

+1

Проверьте часть «Обновить:». –

+0

Создание свойств (как строковых, так и именных символов) связано с тем, что он не работает с закрытыми объектами. – Bergi

2

Использование @ предложение BERĢI о более WeakMap я узнал о Map, что позволяет использовать любого типа значение в качестве ключа (а не только объекты).Потому что мне нужен ключ-однозначно memoizing сочетание значения, переданного в и отступы соединение струнной-я создал иерархическую структуру запоминанием:

function memoizedBuild(){ 
    var memo = new Map; 
    return function(value,indent){ 
    var byIndent=memo.get(value); 
    if (!byIndent) memo.set(value,byIndent={}); 
    if (!byIndent[indent]) byIndent[indent] = rawBuild(value,indent); 
    return byIndent[indent]; 
    } 
} 

Это оказалось около 4 × быстрее memoization code I had been using при сериализации большой объект JSON 270 КБ.

Обратите внимание, что в приведенном выше коде я могу использовать !byIndent[indent] только потому, что я знаю, что rawBuild никогда не будет возвращать значение falsey (null, undefined, false, NaN, 0, ""). Безопаснее строка кода будет выглядеть примерно так:

if (!(indent in byIndent)) byIndent[indent] = rawBuild(value,indent); 
Смежные вопросы