【问题标题】:Checking for duplicate Javascript objects检查重复的 Javascript 对象
【发布时间】:2011-07-06 21:30:55
【问题描述】:

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 对象创建哈希函数?
  • 对象中的超长属性名称可能存在哪些问题?

【问题讨论】:

  • 你控制服务器吗?有什么方法可以为你的对象添加一个唯一的 ID,你可以关闭?
  • 我同意 SB。每个对象的某种唯一键会使这变得微不足道。是否可以在数据源处重新考虑问题以创建这样的密钥?如果这不容易做到,您能否识别对象的属性的一个小主题,这些属性可以唯一地识别它,如果它们相同,那么您可以认为该对象是相同的,并仅使用该属性子集进行散列?
  • @SB, @jfriend00 - 唯一的 ID 会使这更容易,但由于各种原因,它是不可行的。假设我不控制服务器并且对象的模式完全是黑盒的(同样,它有点复杂,但本质上就是这种情况)。

标签: javascript duplicates


【解决方案1】:

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

【讨论】:

  • 你知道有没有好的、快速的 Javascript MD5 实现?
  • 有一个jQuery插件:plugins.jquery.com/project/md5。谷歌显示了许多其他选项。我不知道哪个更好。
  • 抱歉,这有点像“请帮我用 Google 搜索”的问题。这看起来是个不错的选择 - 我喜欢两层重复检查。
  • 我刚刚意识到这个例程在发生哈希冲突时存在缺陷。我会更新的。
【解决方案2】:
  1. 也许吧。例如,如果您知道对象的类型,您可以编写比 JS 对象的键更好的索引和搜索系统。但是你只能用 JavaScript 来做到这一点,并且对象键是用 C 编写的......
  2. 您的散列必须是无损的吗?如果可以尝试失去压缩(MD5)。我猜你会失去一些速度并获得一些记忆。顺便说一句,JSON.stringify(o) 保证相同的键顺序。因为{foo: 1, bar: 2}{bar: 2, foo: 1} 作为对象是相等的,而不是作为字符串。
  3. 成本记忆

一种可能的优化:

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

实际上,通过写最后一句话,我想到了另一种解决方案:

  • 将带有$.get 的所有结果收集到数组中
  • 使用内置排序(c 速度)Array.sort
  • 现在您可以使用for 轻松发现和删除重复项

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

【讨论】:

  • 关于密钥顺序的要点。JSON.stringify 不保证密钥顺序,但我很确定密钥顺序对于相同的 JSON 字符串是相同的 - 只要服务器提供重复数据的相同字符串,我认为是这样,我应该没问题。
猜你喜欢
  • 1970-01-01
  • 2017-08-06
  • 2018-01-07
  • 2020-05-27
  • 2015-04-11
  • 2016-02-23
  • 2021-11-06
  • 2014-05-08
  • 2014-05-30
相关资源
最近更新 更多