Creating a Score Method for an Array - c#

My goal is to create a score method for a simple game based in an array. The game consists of putting 'R's or 'B's in one of 11 positions in an array. Once the array is full, the score method will execute as follows:
Any single 'R' or 'B' is worth 0 points
Any pair of 'R's or 'B's is worth 4 points
Any triple of 'R's or 'B's is worth 6 points
... and so on.
I'm having trouble calculating the score and I feel like I am missing something obvious so I'm coming here. The code I have looks for pairs and adds 2 to the score, but I end up missing 2 points (since the first pair is worth 4 and each additional "pair" is worth another 2).
public int score(char color)
{
int score = 0;
for (int i = 0; i < gameBoard.Length - 1; i++)
{
if (gameBoard[i] == color && gameBoard[i + 1] == color)
score += 2;
else
score += 0;
}
return score;
}

Your problem can be solved by regular expressions.
public int CalculateScore(char color)
{
var boardStr = String.Join("", gameBoard);
return GetCharacterSequences(color, boardStr)
.Sum(str => str.Length * 2);
}
//Returns same character sequences of length more than two.
private IEnumerable<string> GetCharacterSequences(char color, string boardStr)
{
return Regex.Matches(boardStr, $#"({color})\1+").OfType<Match>()
.SelectMany(match => match.Groups.OfType<Group>())
.Select(#group => #group.Value)
.Where(str => str.Length > 1);
}
Idea of sum function: Each pair costs 4 points, each triple costs 6 points hence each single char costs 2 points.
GetCharacterSequences looks little bit complex but it makes CalculateScore method very simple.

Well you could create a boolean variable and use that to determine the number of points added.
Note: This code doesn't account for triple matches as you've mentioned in your question.
public int score(char color)
{
int score = 0;
bool firstMatch = true;
for (int i = 0; i < gameBoard.Length - 1; i++)
{
if (gameBoard[i] == color && gameBoard[i + 1] == color) {
if (firstMatch == true) {
score += 4;
firstMatch = false;
} else {
score += 2;
}
}
else
{
score += 0;
}
}
return score;
}

Here's another approach, based on a pair being 4 points, 3 being 6 points, and assuming 4 being 8 points and so forth (basically number of adjacent colors * 2 if there is at least a pair).
public int score(char color)
{
int adj = 0;
int score = 0;
for (int i = 0; i < gameBoard.Length; i++)
{
if (gameBoard[i] == color)
{
adj++;
}
else
{
if (adj > 1)
{
score += adj * 2;
}
adj = 0;
}
}
}
This code loops through the array, and keeps a count of the number of adjacent spots marked by the specified color. When it comes to an element of the array that is not the right color, it checks to see how many adjacent elements had the correct color.
If it's greater than 1, then it multiplies the number of adjacent elements by 2 and resets the adjacent elements counter to 0 and continues through the array.
Given an array of 'R', 'R', 'B', 'B', 'B', 'R', 'R', 'R', 'B', 'R', 'B', it produces a score of 10 for 'R' (4 + 6) and 6 for 'B' (6), as follows:
'R', 'R' = 4
'R', 'R', 'R' = 6
'R' = 0
'B', 'B', 'B' = 6
'B' = 0

Here is what i came up with
namespace TestApp1
{
class Program
{
static void Main(string[] args)
{
string[] test = GetValues();
string testView = String.Join(String.Empty, test);
string score = GetScore(test).ToString();
Console.WriteLine(testView);
Console.WriteLine(score);
Console.ReadLine();
}
public static int GetScore(string[] test)
{
int score = 0;
int occurence = 0;
string LastChar = string.Empty;
for (int i = 0; i < test.Length; i++)
{
if(LastChar == string.Empty)
{
LastChar = test[i];
occurence += 1;
continue;
}
if(LastChar == test[i])
{
occurence += 1;
if(i == test.Length - 1)
{
if (occurence > 1)
{
score += occurence * 2;
}
}
}
else
{
if(occurence > 1)
{
score += occurence * 2;
}
LastChar = test[i];
occurence = 1;
}
}
return score;
}
public static string[] GetValues()
{
List<string> values = new List<string>();
for (int i = 0; i < 12; i++)
{
var rnd = new Random(DateTime.Now.Millisecond);
int ticks = rnd.Next(0, 2);
values.Add(ticks == 1 ? "R" : "B");
System.Threading.Thread.Sleep(2);
}
return values.ToArray();
}
}
}
To calculate each score independently
namespace TestApp1
{
class Program
{
static void Main(string[] args)
{
string[] test = GetValues();
string testView = String.Join(String.Empty, test);
int rScore = 0;
int bScore = 0;
GetScore(test,out rScore, out bScore);
string score = "R: " + rScore.ToString() + " B: " + bScore.ToString();
Console.WriteLine(testView);
Console.WriteLine(score);
Console.ReadLine();
}
public static void GetScore(string[] test, out int rScore, out int bScore)
{
int occurence = 0;
string LastChar = string.Empty;
rScore = 0;
bScore = 0;
for (int i = 0; i < test.Length; i++)
{
if(LastChar == string.Empty)
{
LastChar = test[i];
occurence += 1;
continue;
}
if(LastChar == test[i])
{
occurence += 1;
if(i == test.Length - 1)
{
if (occurence > 1)
{
if(LastChar == "R")
{
rScore += occurence * 2;
}
else
{
bScore += occurence * 2;
}
}
}
}
else
{
if(occurence > 1)
{
if (LastChar == "R")
{
rScore += occurence * 2;
}
else
{
bScore += occurence * 2;
}
}
LastChar = test[i];
occurence = 1;
}
}
}
public static string[] GetValues()
{
List<string> values = new List<string>();
for (int i = 0; i < 12; i++)
{
var rnd = new Random(DateTime.Now.Millisecond);
int ticks = rnd.Next(0, 2);
values.Add(ticks == 1 ? "R" : "B");
System.Threading.Thread.Sleep(2);
}
return values.ToArray();
}
}
}

Related

Determine the winner on a 3x3 TicTacToe game

I have to find the winner of a 3x3 TicTacToe game player vs player. Firsty, I have to user input N (number of moves) and then N moves like (1 2) (0 1) etc. each on new line, X has the first move, 0 second move and so on. First number from bracket is representing the row index and second number representing column index. The program needs to return the winner X or 0, or draw if there is not a winner.
For example for the following input:
7
1 1
2 1
2 0
0 2
1 0
1 2
0 0
The output is : X
I've tried this code, but nothing is shown in console. Any hint would be really apreciated.
using System;
​
namespace ConsoleApp20
{
public class Program
{
public static string Tictactoe(int[,] moves)
{
int[] rowsValues = new int[3];
int[] columnsValues = new int[3];
int diagonalSum = 0;
int antiDiagonalSum = 0;
const int c1 = 3;
bool isFirstPlayer = true;
for (int i = 0; i < c1; i++)
{
int rowIndex = moves[i, 0];
int columnIndex = moves[i, 1];
int value = isFirstPlayer ? 1 : -1;
​
if (rowIndex == columnIndex)
{
diagonalSum += value;
}
​
if (columnIndex == c1 - rowIndex - 1)
{
antiDiagonalSum += value;
}
​
rowsValues[rowIndex] += value;
columnsValues[columnIndex] += value;
if (Math.Abs(diagonalSum) == c1 ||
Math.Abs(antiDiagonalSum) == c1 ||
Math.Abs(rowsValues[rowIndex]) == c1 ||
Math.Abs(columnsValues[columnIndex]) == c1)
{
return isFirstPlayer ? "X" : "0";
}
​
isFirstPlayer = !isFirstPlayer;
}
​
return "Draw";
}
​
public static void Main()
{
Tictactoe(ReadValues());
}
​
public static int[,] ReadValues()
{
int n = Convert.ToInt32(Console.ReadLine());
int[,] movesArr = new int[n, 2];
for (int i = 0; i < n; i++)
{
string[] moves = Console.ReadLine().Split();
int a = int.Parse(moves[0]);
int b = int.Parse(moves[1]);
movesArr[i, 0] = a;
movesArr[i, 1] = b;
}
​
return movesArr;
}
}
}

I wrote a program to get the count of matching adjacent alphabets in a string

For the same 1 of the test cases have passed while all the other had failed. The failed ones test cases were of very long strings. but could not understand where did I go wrong.
The number of test cases and string is been read in the main function, and string gets passed to this function.
public static int getMaxScore(string jewels)
{
string temp=jewels;
int count=0;
for(int i=0;i<temp.Length-1;i++)
{
for(int j=i;j<temp.Length-1;j++)
{
if(jewels[i]==jewels[j+1])
{
temp=jewels.Remove(i,2);
count++;
}
else
{
continue;
}
}
}
return count;
}
for the passed 1, there were 2 test cases. In that, one being jewels="abcddcbd" and the other being "abcd". Expected Output was 3 for the first string and 0 for the second. however, i got the expected output for this test case. but failed all other ones(those are very long strings)
jewels="edmamjboxwzfjsgnmycuutvkhzerdiabcvzlnoazreuavyemxqwgyzdvrzyohamwamziqvdduequyyspfipvigooyqmwllvp"
Can somebody help me in knowing what is wrong in my code or how can I obtain the result I want?
Thanks in Advance!!!
Sounds a Jewel Quest puzzle. Checking a string for adjacent equal characters, remove them and increase the counter by 1. Removing the two characters from the string could produce a new one with adjacent equal characters so it must be checked again from the beginning to remove them, increase the counter, and do it all over again until no more.
public static int getMaxScore(string jewels)
{
var count = 0;
var max = jewels.Length;
var i = 0;
var chars = jewels.ToList();
var adj = 2; //<- Change to increase the number of adjacent chars.
while (i < max)
{
if (chars.Count >= adj && i <= chars.Count - adj &&
chars.Skip(i).Take(adj).Distinct().Count() == 1)
{
count++;
chars.RemoveRange(i, adj);
max = chars.Count;
i = 0;
}
else
i++;
}
return count;
}
Testing:
void TheCaller()
{
Console.WriteLine(getMaxScore("abcddcbd"));
Console.WriteLine(getMaxScore("abcd"));
Console.WriteLine(getMaxScore("edmamjboxwzfjsgnmycuutvkhzerdiabcvzlnoazreuavyemxqwgyzdvrzyohamwamziqvdduequyyspfipvigooyqmwllvp"));
}
Writes 3, 0, and 5 respectively.
public static int CountThings(string s)
{
if(s.Length < 2) { return 0; }
int n = 0;
for (int i = 0; i < s.Length - 1; i++)
{
if (s[i] == s[i + 1])
{
int start = i;
int end = i + 1;
while (s[start] == s[end] && start >= 0 && end <= s.Length - 1)
{
n++;
start--;
end++;
}
}
}
return n;
}
For grits and shins, here's a compact recursive version:
static void Main(string[] args)
{
string jewels = "edmamjboxwzfjsgnmycuutvkhzerdiabcvzlnoazreuavyemxqwgyzdvrzyohamwamziqvdduequyyspfipvigooyqmwllvp";
int score = getMaxScore(new StringBuilder(jewels));
Console.WriteLine($"jewels = {jewels}");
Console.WriteLine($"score = {score}");
Console.Write("Press Enter to Quit.");
Console.ReadLine();
}
static int getMaxScore(StringBuilder jewels)
{
for(int i=0; i<(jewels.Length-1); i++)
if (jewels[i]==jewels[i+1])
return 1 + getMaxScore(jewels.Remove(i, 2));
return 0;
}

Sunday Algorithm - Index was outside the bounds of the array [duplicate]

This question already has answers here:
What is an IndexOutOfRangeException / ArgumentOutOfRangeException and how do I fix it?
(5 answers)
Closed 3 years ago.
I have this Sunday Algorithm, which checks some occurrences in some text.
class Program
{
static int alphabet = 512;
static int[] table = new int[alphabet];
static int[] occurence(string pattern)
{
for(char a = (char)0; a<(char)alphabet;a++)
{
table[(int)a] = -1;
}
for(int i = 0; i< pattern.Length;i++)
{
char a = pattern[i];
table[(int)a] = i;
}
return table;
}
public static int Sunday(string text, string pattern)
{
Stopwatch timer = new Stopwatch();
timer.Start();
int k = 0;
int i = 0;
int[] table = new int[pattern.Length];
table = occurence(pattern);
while(i <= text.Length - pattern.Length)
{
int j = 0;
while(j<pattern.Length && text[i+j] == pattern[j])
{
j++;
}
if(j==pattern.Length)
{
k++;
}
i += pattern.Length;
if(i<text.Length)
{
i -= table[(int)text[i]];
}
}
timer.Stop();
Console.WriteLine(timer.Elapsed);
return k;
}
static void Main(string[] args)
{
string text = File.ReadAllText(#"C:\Users\Bacho\Desktop\Studies\Advanced Algorithms\Test Patterns\Book1.txt");
string pattern = "frankenstein";
Console.WriteLine(Sunday(text, pattern));
}
}
This is my algorithm, which works well on small input of text. In the code, I try to read a text file which consists of roughly 80 000 words and 450 000 characters. I get this exception:
Unhandled Exception: System.IndexOutOfRangeException: Index was outside the bounds of the array.
at Sunday1.Program.Sunday(String text, String pattern) in C:\Users\Bacho\Desktop\Studies\Advanced Algorithms\Sunday1\Sunday1\Program.cs:line 55
at Sunday1.Program.Main(String[] args) in C:\Users\Bacho\Desktop\Studies\Advanced Algorithms\Sunday1\Sunday1\Program.cs:line 68
..at the following line:
i -= table[(int)text[i]];
Is it because it can not fit in string or is it something else?
Changing the algorithm to read and check line by line may be a solution, but is there a way to avoid it?
A char has a length of two bytes or 16 bits not 9. So I guess your alphabet should therefore probably be 65336 or simply char.MaxValue instead of 512.
Also there's no need of the static fields. You can make them local to occurence(). And in Sunday, you don't need to initialize table with new int[pattern.Length], you can directly use occurence(pattern).
class Program
{
static int[] occurence(string pattern)
{
int[] table = new int[char.MaxValue + 1];
for (int a = 0; a < char.MaxValue + 1; a++)
{
table[a] = -1;
}
for (int i = 0; i < pattern.Length; i++)
{
char a = pattern[i];
table[(int)a] = i;
}
return table;
}
public static int Sunday(string text, string pattern)
{
Stopwatch timer = new Stopwatch();
timer.Start();
int k = 0;
int i = 0;
int[] table = occurence(pattern);
while (i <= text.Length - pattern.Length)
{
int j = 0;
while (j < pattern.Length && text[i + j] == pattern[j])
{
j++;
}
if (j == pattern.Length)
{
k++;
}
i += pattern.Length;
if (i < text.Length)
{
i -= table[(int)text[i]];
}
}
timer.Stop();
Console.WriteLine(timer.Elapsed);
return k;
}
static void Main(string[] args)
{
string text = File.ReadAllText(#"C:\Users\Bacho\Desktop\Studies\Advanced Algorithms\Test Patterns\Book1.txt");
string pattern = "frankenstein";
Console.WriteLine(Sunday(text, pattern));
}
}

How to increment mixed Alphanumeric in C#?

I have requirements in a project to generate sequential rows and columns which are alphanumeric values.
The end user will pass the start value of row and column he would like to start from, and how many rows and columns he wants to generate.
For letters the max value is Z
For numbers the max values is 9
If the end user passed these parameters:
StartRow = 0A
StartColumn = A9Z
rowsCount = 2
columnsCount = 5
I would like to get this result:
You might want to reconsider your approach. Rather than maintaining an alphanumeric value and trying to increment it, maintain the value as a class containing Row and Column values, and then use ToString to convert it to the alphanumeric representation. Like this:
class RowCol
{
private int _row;
private int _col;
public int Row
{
get { return _row; }
set
{
// Row is of the form <digit><letter
// giving you 260 possible values.
if (value < 0 || value > 259)
throw new ArgumentOutOfRangeException();
_row = value;
}
}
public int Col
{
get { return _col; }
set
{
// Col is <letter><digit><letter>,
// giving you 6,760 possible values
if (value < 0 || value > 6759)
throw new ArgumentOutOfRangeException();
_col = value;
}
}
public string RowString
{
get
{
// convert Row value to string
int r, c;
r = Math.DivMod(_row, 26, out c);
r += '0';
c += 'A';
return string.Concat((char)r, (char)c);
}
set
{
// convert string to number.
// String is of the form <letter><digit>
if (string.IsNullOrEmpty(value) || value.Length != 2
|| !Char.IsDigit(value[0] || !Char.IsUpper(value[1]))
throw new ArgumentException();
_row = 26*(value[0]-'0') + (value[1]-'A');
}
}
public string ColString
{
get
{
int left, middle, right remainder;
left = Math.DivRem(_col, 260, out remainder);
middle = Math.DivRem(remainder, 26, out right);
left += 'A';
middle += '0';
right += 'A';
return string.Concat((char)left, (char)middle, (char)right);
}
set
{
// Do standard checking here to make sure it's in the right form.
if (string.IsNullOrEmpty(value) || value.Length != 3
|| !Char.IsUpper(value[0] || !Char.IsDigit(value[1]) || !Char.IsUpper(value[2]))
throw new ArgumentException();
_col = 260*(value[0] - 'A');
_col += 26*(value[1] - '0');
_col += value[2] - 'A';
}
}
public override string ToString()
{
return RowString + '-' + ColString;
}
public RowCol(int row, int col)
{
Row = _row;
Col = _col;
}
public RowCol(string row, string col)
{
RowString = row;
RowString = col;
}
}
(Code not yet tested, but that's the general idea.)
That's a bit more code than you have, it hides the complexity in the RowCol class rather than forcing you to deal with it in your main program logic. The point here is that you just want to increment the row or column; you don't want to have to think about how that's done. It makes your main program logic easier to understand. For example:
string startRow = "0A";
string startCol = "B0A";
RowCol rc = new RowCol("0A", "B0A");
for (int r = 0; r < rowsCount; r++)
{
rc.ColString = "B0A";
for (int c = 0; c < columnsCount; c++)
{
Console.WriteLine(rc);
rc.Row = rc.Row + 1;
}
rc.Col = rc.Col + 1;
}
By casting this as a simple conversion problem and encapsulating it in a class, I've made the code more robust and flexible, and easier to test, understand, and use.
I have come up with very simple solution to implement that and I would like to share this Console application :
class Program
{
static void Main(string[] args)
{
var row = "0A";
var column = "A9Z";
var rowsCount = 2;
var columnsCount = 5;
var rowCharArray =row.ToArray().Reverse().ToArray();
var columnCharArray = column.ToArray().Reverse().ToArray();
for (int i = 0; i < rowsCount; i++)
{
for (int j = 0; j < columnsCount; j++)
{
columnCharArray = incrementChar(columnCharArray);
var currentColumn = string.Join("", columnCharArray.Reverse().ToArray());
var currentRow= string.Join("", rowCharArray.Reverse().ToArray());
Console.WriteLine(currentRow + "-" + currentColumn);
}
columnCharArray = column.ToArray().Reverse().ToArray();
rowCharArray= incrementChar(rowCharArray);
Console.WriteLine("-------------------------------");
}
Console.ReadLine();
}
static char[] incrementChar(char[] charArray,int currentIndex=0)
{
char temp = charArray[currentIndex];
if (charArray.Length -1 == currentIndex && (temp == '9' || temp == 'Z'))
throw new Exception();
temp++;
if(Regex.IsMatch(temp.ToString(),"[A-Z]"))
{
charArray[currentIndex] = temp;
}
else
{
if (Regex.IsMatch(temp.ToString(), "[0-9]"))
{
charArray[currentIndex] = temp;
}
else
{
currentIndex++;
incrementChar(charArray, currentIndex);
}
}
if (currentIndex != 0)
charArray = resetChar(charArray, currentIndex);
return charArray;
}
static char[] resetChar(char[] charArray,int currentIndex)
{
for (int i = 0; i < currentIndex; i++)
{
if (charArray[i] == 'Z')
charArray[i] = 'A';
else if (charArray[i] == '9')
charArray[i] = '0';
}
return charArray;
}
}

how can i find lcs length between two large strings

I've written the following code in C# for obtaining the length of longest common subsequence of two texts given by use, but it doesn't work with large strings. Could you please help me. I'm really confused.
public Form1()
{
InitializeComponent();
}
public int lcs(char[] s1, char[] s2, int s1size, int s2size)
{
if (s1size == 0 || s2size == 0)
{
return 0;
}
else
{
if (s1[s1size - 1] == s2[s2size - 1])
{
return (lcs(s1, s2, s1size - 1, s2size - 1) + 1);
}
else
{
int x = lcs(s1, s2, s1size, s2size - 1);
int y = lcs(s1, s2, s1size - 1, s2size);
if (x > y)
{
return x;
}
else
return y;
}
}
}
private void button1_Click(object sender, EventArgs e)
{
string st1 = textBox2.Text.Trim(' ');
string st2 = textBox3.Text.Trim(' ');
char[] a = st1.ToCharArray();
char[] b = st2.ToCharArray();
int s1 = a.Length;
int s2 = b.Length;
textBox1.Text = lcs(a, b, s1, s2).ToString();
}
Here you are using the Recursion method. So it leads the program to occur performance problems as you mentioned.
Instead of recursion, use the dynamic programming approach.
Here is the C# Code.
public static void LCS(char[] str1, char[] str2)
{
int[,] l = new int[str1.Length, str2.Length];
int lcs = -1;
string substr = string.Empty;
int end = -1;
for (int i = 0; i < str1.Length; i++)
{
for (int j = 0; j < str2.Length; j++)
{
if (str1[i] == str2[j])
{
if (i == 0 || j == 0)
{
l[i, j] = 1;
}
else
l[i, j] = l[i - 1, j - 1] + 1;
if (l[i, j] > lcs)
{
lcs = l[i, j];
end = i;
}
}
else
l[i, j] = 0;
}
}
for (int i = end - lcs + 1; i <= end; i++)
{
substr += str1[i];
}
Console.WriteLine("Longest Common SubString Length = {0}, Longest Common Substring = {1}", lcs, substr);
}
Here is a solution how to find the longest common substring in C#:
public static string GetLongestCommonSubstring(params string[] strings)
{
var commonSubstrings = new HashSet<string>(strings[0].GetSubstrings());
foreach (string str in strings.Skip(1))
{
commonSubstrings.IntersectWith(str.GetSubstrings());
if (commonSubstrings.Count == 0)
return string.Empty;
}
return commonSubstrings.OrderByDescending(s => s.Length).DefaultIfEmpty(string.Empty).First();
}
private static IEnumerable<string> GetSubstrings(this string str)
{
for (int c = 0; c < str.Length - 1; c++)
{
for (int cc = 1; c + cc <= str.Length; cc++)
{
yield return str.Substring(c, cc);
}
}
}
I found it here: http://www.snippetsource.net/Snippet/75/longest-common-substring
Just for fun, here is one example using Queue<T>:
string LongestCommonSubstring(params string[] strings)
{
return LongestCommonSubstring(strings[0], new Queue<string>(strings.Skip(1)));
}
string LongestCommonSubstring(string x, Queue<string> strings)
{
if (!strings.TryDequeue(out var y))
{
return x;
}
var output = string.Empty;
for (int i = 0; i < x.Length; i++)
{
for (int j = x.Length - i; j > -1; j--)
{
string common = x.Substring(i, j);
if (y.IndexOf(common) > -1 && common.Length > output.Length) output = common;
}
}
return LongestCommonSubstring(output, strings);
}
It's still using recursion though, but it's a nice example of Queue<T>.
I refactored the C++ code from Ashutosh Singh at https://iq.opengenus.org/longest-common-substring-using-rolling-hash/ to create a rolling hash approach in C# - this will find the substring in O(N * log(N)^2) time and O(N) space
using System;
using System.Collections.Generic;
public class RollingHash
{
private class RollingHashPowers
{
// _mod = prime modulus of polynomial hashing
// any prime number over a billion should suffice
internal const int _mod = (int)1e9 + 123;
// _hashBase = base (point of hashing)
// this should be a prime number larger than the number of characters used
// in my use case I am only interested in ASCII (256) characters
// for strings in languages using non-latin characters, this should be much larger
internal const long _hashBase = 257;
// _pow1 = powers of base modulo mod
internal readonly List<int> _pow1 = new List<int> { 1 };
// _pow2 = powers of base modulo 2^64
internal readonly List<long> _pow2 = new List<long> { 1L };
internal void EnsureLength(int length)
{
if (_pow1.Capacity < length)
{
_pow1.Capacity = _pow2.Capacity = length;
}
for (int currentIndx = _pow1.Count - 1; currentIndx < length; ++currentIndx)
{
_pow1.Add((int)(_pow1[currentIndx] * _hashBase % _mod));
_pow2.Add(_pow2[currentIndx] * _hashBase);
}
}
}
private class RollingHashedString
{
readonly RollingHashPowers _pows;
readonly int[] _pref1; // Hash on prefix modulo mod
readonly long[] _pref2; // Hash on prefix modulo 2^64
// Constructor from string:
internal RollingHashedString(RollingHashPowers pows, string s, bool caseInsensitive = false)
{
_pows = pows;
_pref1 = new int[s.Length + 1];
_pref2 = new long[s.Length + 1];
const long capAVal = 'A';
const long capZVal = 'Z';
const long aADif = 'a' - 'A';
unsafe
{
fixed (char* c = s)
{
// Fill arrays with polynomial hashes on prefix
for (int i = 0; i < s.Length; ++i)
{
long v = c[i];
if (caseInsensitive && capAVal <= v && v <= capZVal)
{
v += aADif;
}
_pref1[i + 1] = (int)((_pref1[i] + v * _pows._pow1[i]) % RollingHashPowers._mod);
_pref2[i + 1] = _pref2[i] + v * _pows._pow2[i];
}
}
}
}
// Rollingnomial hash of subsequence [pos, pos+len)
// If mxPow != 0, value automatically multiply on base in needed power.
// Finally base ^ mxPow
internal Tuple<int, long> Apply(int pos, int len, int mxPow = 0)
{
int hash1 = _pref1[pos + len] - _pref1[pos];
long hash2 = _pref2[pos + len] - _pref2[pos];
if (hash1 < 0)
{
hash1 += RollingHashPowers._mod;
}
if (mxPow != 0)
{
hash1 = (int)((long)hash1 * _pows._pow1[mxPow - (pos + len - 1)] % RollingHashPowers._mod);
hash2 *= _pows._pow2[mxPow - (pos + len - 1)];
}
return Tuple.Create(hash1, hash2);
}
}
private readonly RollingHashPowers _rhp;
public RollingHash(int longestLength = 0)
{
_rhp = new RollingHashPowers();
if (longestLength > 0)
{
_rhp.EnsureLength(longestLength);
}
}
public string FindCommonSubstring(string a, string b, bool caseInsensitive = false)
{
// Calculate max neede power of base:
int mxPow = Math.Max(a.Length, b.Length);
_rhp.EnsureLength(mxPow);
// Create hashing objects from strings:
RollingHashedString hash_a = new RollingHashedString(_rhp, a, caseInsensitive);
RollingHashedString hash_b = new RollingHashedString(_rhp, b, caseInsensitive);
// Binary search by length of same subsequence:
int pos = -1;
int low = 0;
int minLen = Math.Min(a.Length, b.Length);
int high = minLen + 1;
var tupleCompare = Comparer<Tuple<int, long>>.Default;
while (high - low > 1)
{
int mid = (low + high) / 2;
List<Tuple<int, long>> hashes = new List<Tuple<int, long>>(a.Length - mid + 1);
for (int i = 0; i + mid <= a.Length; ++i)
{
hashes.Add(hash_a.Apply(i, mid, mxPow));
}
hashes.Sort(tupleCompare);
int p = -1;
for (int i = 0; i + mid <= b.Length; ++i)
{
if (hashes.BinarySearch(hash_b.Apply(i, mid, mxPow), tupleCompare) >= 0)
{
p = i;
break;
}
}
if (p >= 0)
{
low = mid;
pos = p;
}
else
{
high = mid;
}
}
// Output answer:
return pos >= 0
? b.Substring(pos, low)
: string.Empty;
}
}

Categories