检查重复的Javascript对象

时间:2011-07-06 21:30:56

标签: javascript duplicates

TL; DR版本:我想避免将重复的Javascript对象添加到类似对象的数组中,其中一些可能非常大。什么是最好的方法?

我有一个应用程序,我将大量JSON数据加载到Javascript数据结构中。虽然它比这复杂一点,但假设我通过一系列AJAX请求将JSON加载到服务器的Javascript对象数组中,例如:

var myObjects = [];

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

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

使问题复杂化,JSON:

  • 处于未知架构
  • 具有任意长度(可能不是很大,但可能在100-200 kb范围内)
  • 可能包含不同请求的重复项

我最初的想法是有一个额外的对象来存储每个对象的哈希值(通过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 kb。所以我的问题是:

  • 对于这个问题,有没有比hashmap想法更好的方法?
  • 如果没有,是否有更好的方法为JSON.stringify的任意长度和架构的JSON对象创建哈希函数?
  • 对象中超长属性名称可能存在哪些问题?

2 个答案:

答案 0 :(得分:4)

我建议您创建JSON.stringify(o)的MD5哈希值,并将其存储在您的hashmap中,并将对存储对象的引用存储为哈希值。并且为了确保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/

答案 1 :(得分:2)

  1. 也许。例如,如果您知道什么类型的对象,您可以编写比JS对象的键更好的索引和搜索系统。但是你只能用JavaScript做到这一点,而对象键是用C语言编写的......
  2. 你的哈希是否必须无损?如果可以尝试失去压缩(MD5)。我猜你会失去一些速度并获得一些记忆。顺便说一句,JSON.stringify(o)保证相同的密钥排序。因为{foo: 1, bar: 2}{bar: 2, foo: 1}等于对象,而不是字符串。
  3. 成本记忆
  4. 一种可能的优化:

    而不是使用getJSON使用$.get并将"text"作为dataType param传递。比你可以使用结果作为你的哈希并在之后转换为对象。

    实际上,通过写下最后一句话,我会谈到另一个解决方案:

    • 使用$.get将所有结果收集到数组
    • 使用buildin(c speed)Array.sort
    • 对其进行排序
    • 现在,您可以使用一个for
    • 轻松识别和删除重复项

    同样,不同的JSON字符串可以构成相同的JavaScript对象。