如何实现流的 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;
}
我将如何为流实施 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;
}