【问题标题】:How to Implement Boyer-Moore Search for Streams如何实现流的 Boyer-Moore 搜索
【发布时间】:2020-07-17 23:58:32
【问题描述】:

我将如何为流实现Boyer-Moore Search?我了解如何为给定的字符串实现这一点,我们知道整个字符串的长度。但是,如果我们不知道字符串的大小(即它是任意长度的字节流)怎么办。

我正在尝试在 PHP 中实现这一点,因此 PHP 中的任何代码都会有所帮助。

这是我在 PHP 中的 Boyer-Moore Search 的实现:

function BoyerMooreSearch($haystack, $needle) {
    $needleLen = strlen($needle);
    $haystackLen = strlen($haystack);
    $table = computeSkipTable($needle);

    for ($i = $needleLen - 1; $i < $haystackLen;) { 
        $t = $i;

        for ($j = $needleLen - 1; $needle[$j] == $haystack[$i]; $j--, $i--) { 
            if($j == 0) {
                return $i;
            }
        }

        $i = $t;

        if(array_key_exists($haystack[$i], $table)) {
            $i = $i + max($table[$haystack[$i]], 1);
        } else {
            $i += $needleLen;
        }
    }
    return false;
}

function computeSkipTable($string) {
    $len = strlen($string);
    $table = [];

    for ($i=0; $i < $len; $i++) { 
        $table[$string[$i]] = $len - $i - 1; 
    }

    return $table;
}

如果我们给它一个像"barfoobazquix" 这样的干草堆字符串和一个像"foo" 这样的针状字符串,它会正常工作,它将按预期返回3。但是,如果输入 haystack 是一个拆分为 4 字节块的流怎么办。第一个块是"barf",它将不返回匹配项,第二个块是"ooba",它也返回不匹配项,依此类推...

在这种情况下,我们永远无法在当前实现的流的任何缓冲块中找到子字符串"foo"。我正在努力调整当前的实现,即使搜索字符串被分成多个块,它也能正常工作。

【问题讨论】:

    标签: php algorithm search boyer-moore


    【解决方案1】:

    请注意,您只能使用流中的 $needleLen 最近的字符。因此,您可以维护一个由$needleLen 字符组成的滑动窗口,并根据需要将其推进。此外,$haystackLen 现在未知,应替换为 EOF 检查。

    下面的代码效率低,因为滑动窗口总是需要O(n),无论我们滑动n 个字符还是仅仅1 个字符。为了获得最佳的滑动复杂度,$window 应该实现为一个循环字符缓冲区。

    // sample call
    var_dump(BoyerMooreSearch(STDIN, 'foo'));
    
    function BoyerMooreSearch($resource, $needle) {
        $needleLen = strlen($needle);
        $table = computeSkipTable($needle);
    
        // build the first window
        if (($window = buildWindow($resource, $needleLen)) === false) {
            // special case: the text is shorter than the pattern
            return false;
        }
    
        $i = 0;
        while (true) {
            // check for a match
            $j = $needleLen - 1;
            while ($j >= 0 && $needle[$j] == $window[$j]) {
                $j--;
            }
            if ($j < 0) {
                return $i;
            }
    
            // determine slide distance
            $t = $i;
            $last = substr($window, -1);
            if (array_key_exists($last, $table)) {
                $i = $i + max($table[$last], 1);
            } else {
                $i += $needleLen;
            }
    
            // slide the window
            if (($window = slideWindow($window, $resource, $i - $t)) === false) {
                return false;
            }
        }
    
        return false;
    }
    
    /**
     * Initializes the sliding window to length $len.
     *
     * @return string A string of length $len or false if the $resource contains
     * less than $len characters.
     */
    function buildWindow ($resource, $len) {
        $result = '';
        while ($len--) {
            $result .= fgetc($resource);
        }
        return feof($resource) ? false : $result;
    }
    
    /**
     * Slides $window by $count positions into $resource.
     *
     * @return string The new window or false on EOF.
     */
    function slideWindow(&$window, $resource, $count) {
        $window = substr($window, $count) // discard the first $count chars
            . fread($resource, $count);   // and read $count new chars
        return feof($resource) ? false : $window;
    }
    

    【讨论】:

    • 这很好用。只需在循环while ($j &gt;= 0 &amp;&amp; $needle[$j] == $window[$j]) { 中快速注意一下,您必须考虑到$needle 实际上可以大于$window,因此您应该小心不要出现未定义的偏移错误。除此之外,您的解决方案正是我想要的。谢谢!
    • 如果$needle 可以大于$window,我的推理中有一个错误,应该修复它。 :-) 能给我举个例子吗?我的意图是 (a) 如果 buildWindow() 返回的窗口比 $needleLen 短,则尽早返回,然后 (b) 在 slideWindow() 未能保持窗口相同长度时返回。如果我的代码做了不同的事情,我们深表歉意。
    • 好吧,如果针在输入中部分找到然后在中间失败(例如输入 = "fo"),您最终会在第 19 行以某种方式出现未定义的偏移错误(这是那个while循环)。我没有深入研究它,但如果我在$needle[$j] == $window[$j] 之前添加条件&amp;&amp; isset($window[$j]),它将停止给出该错误。例如,当我输入 "fo" 并按 Enter 键时,就会发生这种情况。
    • 啊,不错。这似乎与fread 如何与STDIN 一起工作有关,因为fread 返回的字符少于预期。如果我将相同的输入放入文件a.txt 并使用a.php &lt; a.txt 进行管道传输,则错误不再发生。正确的解决方法是将调用fread 替换为$count 调用fgetc
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-10-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-06-21
    • 2014-06-29
    相关资源
    最近更新 更多