Boyer-Moore可能是已知的最快的非索引文本搜索算法。所以我在C#中为我的Black Belt Coder网站实现了它。
我使用它,与String.IndexOf()
相比,它大致显示了预期的性能提升。但是,当我将StringComparison.Ordinal
参数添加到IndexOf
时,它开始优于我的Boyer-Moore实现。有时,相当多。
我想知道是否有人可以帮助我找出原因。我理解为什么StringComparision.Ordinal
可能会加快速度,但它怎么能比Boyer-Moore更快?是因为.NET平台本身的开销,可能是因为必须验证数组索引以确保它们在范围内,或者完全不同。有些算法在C#.NET中不实用吗?
以下是关键代码。
// Base for search classes
abstract class SearchBase
{
public const int InvalidIndex = -1;
protected string _pattern;
public SearchBase(string pattern) { _pattern = pattern; }
public abstract int Search(string text, int startIndex);
public int Search(string text) { return Search(text, 0); }
}
/// <summary>
/// A simplified Boyer-Moore implementation.
///
/// Note: Uses a single skip array, which uses more memory than needed and
/// may not be large enough. Will be replaced with multi-stage table.
/// </summary>
class BoyerMoore2 : SearchBase
{
private byte[] _skipArray;
public BoyerMoore2(string pattern)
: base(pattern)
{
// TODO: To be replaced with multi-stage table
_skipArray = new byte[0x10000];
for (int i = 0; i < _skipArray.Length; i++)
_skipArray[i] = (byte)_pattern.Length;
for (int i = 0; i < _pattern.Length - 1; i++)
_skipArray[_pattern[i]] = (byte)(_pattern.Length - i - 1);
}
public override int Search(string text, int startIndex)
{
int i = startIndex;
// Loop while there's still room for search term
while (i <= (text.Length - _pattern.Length))
{
// Look if we have a match at this position
int j = _pattern.Length - 1;
while (j >= 0 && _pattern[j] == text[i + j])
j--;
if (j < 0)
{
// Match found
return i;
}
// Advance to next comparision
i += Math.Max(_skipArray[text[i + j]] - _pattern.Length + 1 + j, 1);
}
// No match found
return InvalidIndex;
}
}
编辑:我已在http://www.blackbeltcoder.com/Articles/algorithms/fast-text-search-with-boyer-moore发布了有关此问题的所有测试代码和结论。
答案 0 :(得分:19)
基于我自己的测试和这里的评论,我得出结论String.IndexOf()
与StringComparision.Ordinal
表现良好的原因是因为该方法调用了可能采用手动优化组装的非托管代码语言。
我已经运行了许多不同的测试,String.IndexOf()
似乎比使用托管C#代码实现的任何速度都要快。
如果有人感兴趣,我已经写了我发现的所有内容,并在http://www.blackbeltcoder.com/Articles/algorithms/fast-text-search-with-boyer-moore的C#中发布了Boyer-Moore算法的几种变体。
答案 1 :(得分:4)
我敢打赌,设置该标志允许String.IndexOf使用Boyer-Moore本身。它的实施比你的更好。
如果没有那个标志,就必须小心使用Boyer-Moore(可能没有)因为围绕Unicode的潜在问题。特别是Unicode的可能性导致Boyer-Moore使用的转换表爆炸。
答案 2 :(得分:0)
我对您的代码进行了一些小的更改,并对 Boyer-Moore 算法进行了不同的实现并获得了更好的结果。 我从 here
那里得到了这个实现的想法但说实话,与 IndexOf
相比,我希望达到更高的速度。
class SearchResults
{
public int Matches { get; set; }
public long Ticks { get; set; }
}
abstract class SearchBase
{
public const int InvalidIndex = -1;
protected string _pattern;
protected string _text;
public SearchBase(string text, string pattern) { _text = text; _pattern = pattern; }
public abstract int Search(int startIndex);
}
internal class BoyerMoore3 : SearchBase
{
readonly byte[] textBytes;
readonly byte[] patternBytes;
readonly int valueLength;
readonly int patternLength;
private readonly int[] badCharacters = new int[256];
private readonly int lastPatternByte;
public BoyerMoore3(string text, string pattern) : base(text, pattern)
{
textBytes = Encoding.UTF8.GetBytes(text);
patternBytes = Encoding.UTF8.GetBytes(pattern);
valueLength = textBytes.Length;
patternLength = patternBytes.Length;
for (int i = 0; i < 256; ++i)
badCharacters[i] = patternLength;
lastPatternByte = patternLength - 1;
for (int i = 0; i < lastPatternByte; ++i)
badCharacters[patternBytes[i]] = lastPatternByte - i;
}
public override int Search(int startIndex)
{
int index = startIndex;
while (index <= (valueLength - patternLength))
{
for (int i = lastPatternByte; textBytes[index + i] == patternBytes[i]; --i)
{
if (i == 0)
return index;
}
index += badCharacters[textBytes[index + lastPatternByte]];
}
// Text not found
return InvalidIndex;
}
}
更改了 Form1
中的代码:
private void RunSearch(string pattern, SearchBase search, SearchResults results)
{
var timer = new Stopwatch();
// Start timer
timer.Start();
// Find all matches
int pos = search.Search(0);
while (pos != -1)
{
results.Matches++;
pos = search.Search(pos + pattern.Length);
}
// Stop timer
timer.Stop();
// Add to total Ticks
results.Ticks += timer.ElapsedTicks;
}