Проверка на наличие дублированных объектов Javascript - PullRequest
3 голосов
/ 07 июля 2011

TL; DR версия: Я хочу не добавлять дубликаты объектов Javascript в массив похожих объектов, некоторые из которых могут быть очень большими.Какой лучший подход?

У меня есть приложение, в котором я загружаю большие объемы данных JSON в структуру данных Javascript.Хотя это немного сложнее, чем это, предположим, что я загружаю JSON в массив объектов Javascript с сервера через серию запросов AJAX, что-то вроде:

var myObjects = [];

function processObject(o) {
    myObjects.push(o);
}

for (var x=0; x<1000; x++) {
    $.getJSON('/new_object.json', processObject);
}

Чтобы усложнить ситуацию, JSON:

  • находится в неизвестной схеме
  • имеет произвольную длину (вероятно, не огромную, но может быть в диапазоне 100-200 КБ)
  • может содержать дубликатыпо разным запросам

Моя первоначальная мысль - создать дополнительный объект для хранения хеша каждого объекта (через JSON.stringify?) и проверять его при каждой загрузке, например:

var myHashMap = {};

function processObject(o) {
    var hash = JSON.stringify(o);
    // is it in the hashmap?
    if (!(myHashMap[hash])) {
        myObjects.push(o);
        // set the hashmap key for future checks
        myHashMap[hash] = true;
    }
    // else ignore this object
}

но меня беспокоит наличие имен свойств в myHashMap, длина которых может составлять 200 КБ.Итак, мои вопросы:

  • Есть ли лучший подход к этой проблеме, чем идея hashmap?
  • Если нет, есть ли лучший способ сделать хеш-функцию для объекта JSONпроизвольной длины и схемы, отличной от JSON.stringify?
  • Каковы возможные проблемы со сверхдлинными именами свойств в объекте?

Ответы [ 2 ]

4 голосов
/ 07 июля 2011

Я бы посоветовал вам создать MD5-хеш JSON.stringify (o) и сохранить его в своей хэш-карте со ссылкой на ваш сохраненный объект в качестве данных для хеш-функции.И чтобы убедиться, что в JSON.stringify() нет различий в порядке следования ключей объекта, необходимо создать копию объекта, который упорядочивает ключи.

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

Вот код для создания канонического строкового представления объекта (включая вложенные объекты или объекты в массивах), который обрабатывает ключи объектов, которые могутбыть в другом порядке, если вы только что вызвали JSON.stringify ().

// Code to do a canonical JSON.stringify() that puts object properties 
// in a consistent order
// Does not allow circular references (child containing reference to parent)
JSON.stringifyCanonical = function(obj) {
    // compatible with either browser or node.js
    var Set = typeof window === "object" ? window.Set : global.Set;

    // poor man's Set polyfill
    if (typeof Set !== "function") {
        Set = function(s) {
            if (s) {
                this.data = s.data.slice();
            } else {
                this.data = [];
            }
        };
        Set.prototype = {
            add: function(item) {
                this.data.push(item);
            },
            has: function(item) {
                return this.data.indexOf(item) !== -1;
            }
        };
    }

    function orderKeys(obj, parents) {
        if (typeof obj !== "object") {
            throw new Error("orderKeys() expects object type");
        }
        var set = new Set(parents);
        if (set.has(obj)) {
            throw new Error("circular object in stringifyCanonical()");
        }
        set.add(obj);
        var tempObj, item, i;
        if (Array.isArray(obj)) {
            // no need to re-order an array
            // but need to check it for embedded objects that need to be ordered
            tempObj = [];
            for (i = 0; i < obj.length; i++) {
                item = obj[i];
                if (typeof item === "object") {
                    tempObj[i] = orderKeys(item, set);
                } else {
                    tempObj[i] = item;
                }
            }
        } else {
            tempObj = {};
            // get keys, sort them and build new object
            Object.keys(obj).sort().forEach(function(item) {
                if (typeof obj[item] === "object") {
                    tempObj[item] = orderKeys(obj[item], set);
                } else {
                    tempObj[item] = obj[item];
                }
            });
        }
        return tempObj;
    }

    return JSON.stringify(orderKeys(obj));
}

И алгоритм

var myHashMap = {};

function processObject(o) {
    var stringifiedCandidate = JSON.stringifyCanonical(o);
    var hash = CreateMD5(stringifiedCandidate);
    var list = [], found = false;
    // is it in the hashmap?
    if (!myHashMap[hash] {
        // not in the hash table, so it's a unique object
        myObjects.push(o);
        list.push(myObjects.length - 1);    // put a reference to the object with this hash value in the list
        myHashMap[hash] = list;             // store the list in the hash table for future comparisons
    } else {
        // the hash does exist in the hash table, check for an exact object match to see if it's really a duplicate
        list = myHashMap[hash];             // get the list of other object indexes with this hash value
        // loop through the list
        for (var i = 0; i < list.length; i++) {
            if (stringifiedCandidate === JSON.stringifyCanonical(myObjects[list[i]])) {
                found = true;       // found an exact object match
                break;
            }
        }
        // if not found, it's not an exact duplicate, even though there was a hash match
        if (!found) {
            myObjects.push(o);
            myHashMap[hash].push(myObjects.length - 1);
        }
    }
}

Тестовый пример для jsonStringifyCanonical() находится здесь: https://jsfiddle.net/jfriend00/zfrtpqcL/

2 голосов
/ 07 июля 2011
  1. Может быть.Например, если Вы знаете, какой тип объекта проходит, Вы могли бы написать лучшую систему индексации и поиска, чем ключи объектов JS.Но вы могли бы сделать это только с помощью JavaScript, а ключи объектов написаны на C ...
  2. Должен ли ваш хеширование быть без потерь или нет?Если можете, то попытайтесь потерять сжатие (MD5).Я предполагаю, что Вы потеряете некоторую скорость и приобретете немного памяти.Кстати, do JSON.stringify(o) гарантирует такой же порядок клавиш.Потому что {foo: 1, bar: 2} и {bar: 2, foo: 1} равны как объекты, но не как строки.
  3. Стоимость памяти

Одна из возможных оптимизаций:

Вместо использования getJSON используйте $.get и передайте "text" как dataType param.Чем вы можете использовать результат в качестве хэша и впоследствии преобразовать его в объект.

На самом деле, написав последнее предложение, я подумал о другом решении:

  • Собрать все результаты с $.get в массив
  • Сортировка с помощью buildin (скорость c) Array.sort
  • Теперь Вы можете легко находить и удалять дубликаты с помощью одного for

Снова можно создавать различные строки JSONтот же объект JavaScript.

...