如何实现流的 Boyer-Moore 搜索

How to Implement Boyer-Moore Search for Streams

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

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

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

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"这样的针字符串,它就可以正常工作,它会如预期的那样return 3。但是,如果输入 haystack 是一个分成 4 字节块的流怎么办。第一个块是 "barf",return 不匹配,第二个块是 "ooba",return 也不匹配,依此类推...

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

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

下面的代码效率低下,因为滑动 window 总是需要 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;
}