【问题标题】:Why does this function for searching strings using hashing not work?为什么这个使用散列搜索字符串的函数不起作用?
【发布时间】:2020-01-06 04:46:11
【问题描述】:

我写了一个函数,它有 2 个字符串,s 和 a,它应该返回 a 在 s 中第一次出现的位置。它可以将 a 作为一个字符正常工作,但如果第一次出现在 s 中的第三个字符之后,它将停止工作。

我已经检查了 mul 和 add 是否正确,a 的哈希是否正确,并且我已将基数减少到 10 和 100(这对于哈希不是很好,因为它们不是素数)并且它有效(在长度为 20 的字符串)。这可能意味着模数不能按预期工作。

function getIndex(s, a) {

    // 2 bases and 2 mods to reduce the number of collisions

    var base1 = 31337;
    var base2 = 31357;

    var mod1 = 1e9 + 7;
    var mod2 = 1e9 + 9;

    //suffix arrays

    var hs1 = new Uint32Array(s.length);
    var hs2 = new Uint32Array(s.length);

    // bases to the power of a.length

    var ba1 = 1;
    var ba2 = 1;

    // hashes for a

    var ha1 = 0;
    var ha2 = 0;

    //operators

    var mul = (x, y, mod) => (x * y) % mod;

    var add = (x, y, mod) => {
        x += y;
        if(x >= mod) x -= mod;
        if(x < 0) x += mod;
        return x;
    }

    //get hash of a and find value of ba1 and ba2

    for(var i = 0; i < a.length; i++) {
        ha1 = add(mul(ha1, base1, mod1), a.charCodeAt(i), mod1);
        ha2 = add(mul(ha2, base2, mod2), a.charCodeAt(i), mod2);

        ba1 = mul(ba1, base1, mod1);
        ba2 = mul(ba2, base2, mod2);
    }

    //make suffix array

    var h1 = 0;
    var h2 = 0;

    for(var i = 0; i < s.length; i++) {
        h1 = add(mul(h1, base1, mod1), s.charCodeAt(i), mod1);
        h2 = add(mul(h2, base2, mod2), s.charCodeAt(i), mod2);

        hs1[i] = h1;
        hs2[i] = h2;
    }

    //Compare hashes of substrings of s (by removing prefix from the current element) with hash of a

    for(var i = a.length - 1; i < s.length; i++) {
        var h1 = hs1[i];
        var h2 = hs2[i];
        if(i >= a.length) {
            console.log(i, i - a.length, h1);
            h1 = add(h1, -mul(hs1[i - a.length], ba1, mod1), mod1);
            h2 = add(h2, -mul(hs2[i - a.length], ba2, mod2), mod2);
        }
        if(h1 == ha1 && h2 == ha2) return i - a.length + 1;
    }

    return -1;
}
getIndex("abcdefgh", "f") //returns 5
getIndex("abcdefgh", "fg")//returns -1

【问题讨论】:

  • 你能解释一下你的哈希函数的目的是什么吗?它与检索另一个字符串中出现子字符串的第一个索引的任务有什么关系?如您所知,这可以通过s.indexOf(a) 轻松完成。
  • 因为在这个例子中, indexOf 的复杂度是 (n - m) * m (或简化的 O(n * m) ),但是使用散列(在这个代码中,它的目的是转换字符串到数字)和后缀数组我认为复杂度将是 O(n)(从 0 到 m 的循环为 1,从 0 到 n 的循环为 2)。
  • 我没有解释它的目的是什么,我使用散列创建了一个数字后缀数组,代表字符串的后缀。由于这个散列函数的属性(如果我从后缀数组中取(x - len)th elem,将它乘以len 的幂并从xth 元素中减去,我将从@ 得到子字符串的散列987654327@th 元素到xth 元素)我可以非常快速地获取每个子字符串的哈希值,并将其与我正在寻找的字符串的哈希值进行比较(这是比较数字,也应该很快)。
  • 好的,所以你想比较 hashed 字符串。我认为您的函数仍应比较 纯文本 字符串。
  • 您的“后缀数组”似乎存储了前缀的哈希值?

标签: javascript string debugging hash


【解决方案1】:

通过更多的日志记录,很明显您是floating point inaccuracies 的受害者。 JS 数字只能精确表示 52 位整数。

getIndex("abcdefgh", "fg") 中,搜索到的哈希ha13196477,您的基本乘数ba1982007569,在第六次迭代中,前缀哈希是hs1[6] = 73644174hs1[4] = 800389532。如果将它们放入算术函数中,则结果为 3196441,减 6。这是因为 800389532 * 982007569maximum safe integer 大两个数量级,并且在 JS 中确实计算为 785988578572367700,即明显错了。

您需要选择更小的模数和基数,或者使用 bigints 进行所有计算。

【讨论】:

  • 我已将第 28 行更改为 var mul = (x, y, mod) =&gt; Number((BigInt(x) * BigInt(y)) % BigInt(mod));,现在它可以工作了,但我不知道 BigInt 的速度有多快。我发现它的大小只受内存的限制,所以我不确定它有多快(数学运算和转换)。
  • 我也不认为减少基数和模组是明智的,因为它会为更多的冲突腾出空间,匹配不同的字符串哈希。
  • @Franx1024 无论如何,您都必须处理冲突,它们总是会发生。当使用哈希找到可能的解决方案时,您总是需要通过实际比较字符串来确认它。
  • @Franx1024 关于性能,bigints 可能相当慢。但是,它们将允许您仅使用一个大哈希而不是两个具有不同基数和模数的小哈希,这样您就可以摆脱所有代码重复。是的,那么您必须以indexOf 为基准。我很确定 JS 引擎确实实现了 a string search algorithm,它没有天真的二次运行时。
  • 我在这里stackoverflow.com/questions/12752274/… 发现了 indexof 的 O(nm) 复杂度,但是当我查看源代码 (chromium.googlesource.com/v8/v8/+/4.3.49/src/…) 时,似乎他们正在使用某种后缀数组也是。
猜你喜欢
  • 2013-09-10
  • 2012-11-30
  • 2020-07-04
  • 2017-07-07
  • 1970-01-01
  • 2020-07-07
  • 2022-01-26
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多