Skip to content

Instantly share code, notes, and snippets.

@baratgabor
Last active April 25, 2019 12:00
Show Gist options
  • Save baratgabor/78c5c902cd130977daca7aa5058d598e to your computer and use it in GitHub Desktop.
Save baratgabor/78c5c902cd130977daca7aa5058d598e to your computer and use it in GitHub Desktop.
Manacher's algorithm for finding palindromes, in C#, refactored to separate the pre-processing, and to return IEnumerable<string> of palindromes of specified min and max length. Exposes static methods for instant queries, and instance methods for pre-processing a string only once and then querying it multiple times.
class ManachersPalindrome
{
private readonly char[] s2;
private readonly int[] p;
public static ManachersPalindrome Build(string s)
=> new ManachersPalindrome(s);
private ManachersPalindrome(string s)
{
if (string.IsNullOrEmpty(s))
throw new ArgumentException($"Parameter '{nameof(s)}' cannot be empty or null.");
s2 = AddBoundaries(s.ToArray());
p = PreProcess(s2);
}
public string LongestPalindrome()
=> LongestPalindrome(s2, p);
public IEnumerable<string> AllPalindromes(int minLength, int maxLength = int.MaxValue)
=> AllPalindromes(s2, p, minLength, maxLength);
public static string FindLongestPalindrome(string s)
{
if (string.IsNullOrEmpty(s))
throw new ArgumentException($"Parameter '{nameof(s)}' cannot be empty or null.");
var s2 = AddBoundaries(s.ToArray());
return LongestPalindrome(s2, PreProcess(s2));
}
public static IEnumerable<string> FindAllPalindromes(string s, int minLength, int maxLength = int.MaxValue)
{
if (string.IsNullOrEmpty(s))
throw new ArgumentException($"Parameter '{nameof(s)}' cannot be empty or null.");
var s2 = AddBoundaries(s.ToCharArray());
return AllPalindromes(s2, PreProcess(s2), minLength, maxLength);
}
private static string LongestPalindrome(char[] s2, int[]p)
{
int len = 0, c = 0, s2len = s2.Length;
for (int i = 1; i < s2len; i++)
{
if (len < p[i])
{
len = p[i];
c = i;
}
}
return GetSlice(s2, c - len, len * 2 + 1);
}
private static IEnumerable<string> AllPalindromes(char[] s2, int[] p, int minLength, int maxLength = int.MaxValue)
{
int s2len = s2.Length;
for (int i = 1; i < s2len; i++)
{
var len = p[i];
if (len >= minLength && len <= maxLength)
yield return GetSlice(s2, i - len, len * 2 + 1);
}
}
private static int[] PreProcess(char[] s2)
{
int s2len = s2.Length;
int[] p = new int[s2len];
int c = 0, r = 0;
int m = 0, n = 0;
for (int i = 1; i < s2len; i++)
{
if (i > r)
{
p[i] = 0; m = i - 1; n = i + 1;
}
else
{
int i2 = c * 2 - i;
if (p[i2] < (r - i - 1))
{
p[i] = p[i2];
m = -1; // This signals bypassing the while loop below.
}
else
{
p[i] = r - i;
n = r + 1; m = i * 2 - n;
}
}
while (m >= 0 && n < s2len && s2[m] == s2[n])
{
p[i]++; m--; n++;
}
if ((i + p[i]) > r)
{
c = i; r = i + p[i];
}
}
return p;
}
private static char[] AddBoundaries(char[] cs)
{
if (cs == null || cs.Length == 0)
return "||".ToCharArray();
char[] cs2 = new char[cs.Length * 2 + 1];
for (int i = 0; i < (cs2.Length - 1); i = i + 2)
{
cs2[i] = '|';
cs2[i + 1] = cs[i / 2];
}
cs2[cs2.Length - 1] = '|';
return cs2;
}
private static char[] RemoveBoundaries(char[] cs)
{
if (cs == null || cs.Length < 3)
return "".ToCharArray();
char[] cs2 = new char[(cs.Length - 1) / 2];
for (int i = 0; i < cs2.Length; i++)
cs2[i] = cs[i * 2 + 1];
return cs2;
}
private static string GetSlice(char[] s2, int start, int len)
{
char[] res = new char[len];
Array.Copy(s2, start, res, 0, len);
return new string(RemoveBoundaries(res));
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment