Knuth–Morris–Pratt Search

using System.Collections.Generic;

namespace Algorithms.Strings.PatternMatching;

public class KnuthMorrisPrattSearcher
{
    /// <summary>
    ///     An implementation of Knuth–Morris–Pratt Algorithm.
    ///     Worst case time complexity: O(n + k)
    ///     where n - text length, k - pattern length.
    /// </summary>
    /// <param name="str">The string to look in.</param>
    /// <param name="pat">The pattern to look for.</param>
    /// <returns>
    ///     The zero-based positions of all occurrences of <paramref name="pat" /> in <paramref name="str" />.
    /// </returns>
    public IEnumerable<int> FindIndexes(string str, string pat)
    {
        var lps = FindLongestPrefixSuffixValues(pat);

        for (int i = 0, j = 0; i < str.Length;)
        {
            if (pat[j] == str[i])
            {
                j++;
                i++;
            }

            if (j == pat.Length)
            {
                yield return i - j;
                j = lps[j - 1];
                continue;
            }

            if (i < str.Length && pat[j] != str[i])
            {
                if (j != 0)
                {
                    j = lps[j - 1];
                }
                else
                {
                    i += 1;
                }
            }
        }
    }

    /// <summary>
    ///     Return the longest prefix suffix values for pattern.
    /// </summary>
    /// <param name="pat">pattern to seek.</param>
    /// <returns>The longest prefix suffix values for <paramref name="pat" />.</returns>
    public int[] FindLongestPrefixSuffixValues(string pat)
    {
        var lps = new int[pat.Length];
        for (int i = 1, len = 0; i < pat.Length;)
        {
            if (pat[i] == pat[len])
            {
                len++;
                lps[i] = len;
                i++;
                continue;
            }

            if (len != 0)
            {
                len = lps[len - 1];
            }
            else
            {
                lps[i] = 0;
                i++;
            }
        }

        return lps;
    }
}