Бойер-Мур Практика в C#?

Бойер-Мур, пожалуй, самый быстрый неиндексированный алгоритм поиска текста. Так что я внедряю это в C# для моего сайта Black Belt Coder.

У меня было это работает, и он показал примерно ожидаемые улучшения производительности по сравнению с String.IndexOf(), Тем не менее, когда я добавил StringComparison.Ordinal аргумент IndexOf, он начал опережать мою реализацию Бойера-Мура. Иногда на значительную сумму.

Интересно, кто-нибудь может помочь мне понять, почему. Я понимаю почему StringComparision.Ordinal может ускорить процесс, но как это может быть быстрее, чем Бойер-Мур? Это из-за накладных расходов самой платформы.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.

3 ответа

Решение

Основываясь на моих собственных тестах и ​​комментариях, сделанных здесь, я пришел к выводу, что причина String.IndexOf() так хорошо работает с StringComparision.Ordinal потому что метод вызывает неуправляемый код, который, вероятно, использует оптимизированный вручную язык ассемблера.

Я провел ряд различных тестов и String.IndexOf() кажется, что это быстрее, чем что-либо, что я могу реализовать с помощью управляемого кода C#.

Если кому-то интересно, я написал все, что узнал об этом, и опубликовал несколько вариантов алгоритма Бойера-Мура на C# по адресу http://www.blackbeltcoder.com/Articles/algorithms/fast-text-search-with-boyer-moore.

Держу пари, что установка этого флага позволяет String.IndexOf использовать самого Бойера-Мура. И его реализация лучше вашей.

Без этого флага следует осторожно использовать Бойера-Мура (и, вероятно, нет) из-за потенциальных проблем с Unicode. В частности, возможность Unicode приводит к взрыву таблиц переходов, которые использует Бойер-Мур.

Я внес небольшие изменения в ваш код и сделал другую реализацию алгоритма Бойера-Мура и получил лучшие результаты. Я получил идею для этой реализации отсюда

Но, честно говоря, я ожидал бы достичь более высокой скорости по сравнению с 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;
    }
Другие вопросы по тегам