I think you can make a few improvements that will make this class not only more useful but also easier to understand.
---
I'd start with naming the class according to its job, which is `ParenthesesValidator`. Then the main method becomes `ValidateParenthesesBalanced`. The next step is to throw away the constructor and the `Text` property. It doesn't have to be the state of the class. You can perfectly pass it to the validating method. The same with the `Stack`. What can be local to a method should be local. This way you can make less mistakes by overwriting the data and contribute to the thread-safety of the validation.
Then let's take care of the definition of the parentheses that currently is an array:
> Parentheses = new char[] { '{', '}', '(', ')', '[', ']' };
This requries some computations to find the matchin paretheses but there is another way. With the new tuples you can define a few helper variables that will allow you to greatly simplify the logic.
private static readonly IEnumerable<(char Opening, char Closing)> Parentheses = new (char, char)[] { ('{', '}'), ('(', ')'), ('[', ']') };
private static readonly ISet<char> OpeningParentheses = new HashSet<char>(Parentheses.Select(p => p.Opening));
private static readonly ISet<char> ClosingParentheses = new HashSet<char>(Parentheses.Select(p => p.Closing));
private static readonly ISet<char> AllParentheses = new HashSet<char>(OpeningParentheses.Concat(ClosingParentheses));
private static readonly IDictionary<char, char> ParenthesesMap = Parentheses.ToDictionary(p => p.Opening, p => p.Closing);
You first create a collection of all `Parentheses` naming them as `Opening` and `Closing.` You use it to build the other helper collections. Using hash-sets is usually faster then scanning arrays because it is an O(1) operation unlike the O(n) with an array. Lastly you create a dictionary that will map an opening paretheses to a closing one so you don't have to compute anything.
With this optimization your code becomes two one-liners:
private bool IsParentheses(char letter)
{
return AllParentheses.Contains(letter);
}
private bool IsCorrectClosingParenthese(char openingParenthese, char closingParenthese)
{
return ParenthesesMap[openingParenthese] == closingParenthese;
}
---
You can also remove this condition
> if (IsParentheses(letter))
by plugging this method into a `Where` _behind_ the `text`
foreach (char letter in text.Where(IsParentheses))
---
After putting everything together your code would look like this:
public class ParenthesesValidator
{
private static readonly IEnumerable<(char Opening, char Closing)> Parentheses = new (char, char)[] { ('{', '}'), ('(', ')'), ('[', ']') };
private static readonly ISet<char> OpeningParentheses = new HashSet<char>(Parentheses.Select(p => p.Opening));
private static readonly ISet<char> ClosingParentheses = new HashSet<char>(Parentheses.Select(p => p.Closing));
private static readonly ISet<char> AllParentheses = new HashSet<char>(OpeningParentheses.Concat(ClosingParentheses));
private static readonly IDictionary<char, char> ParenthesesMap = Parentheses.ToDictionary(p => p.Opening, p => p.Closing);
public bool ValidateParenthesesBalanced(string text)
{
var stack = new Stack<char>();
foreach (char letter in text.Where(IsParentheses))
{
if (OpeningParentheses.Contains(letter))
{
stack.Push(letter);
}
else if (stack.Count > 0)
{
// Stack contains opening parentheses so {,(,[
// We pop elements when we find closing parenthese.
var top = stack.Peek();
if (!IsCorrectClosingParenthese(top, letter))
{
return false;
}
stack.Pop();
}
else
{
// Stack should we should a opening parenthese otherwise if we
// Pop when stack is empty it will throw an error.
// This handles when user provide first letter as a closing parenthese
// rather then opening so ]()[
return false;
}
}
return stack.Count == 0;
}
private bool IsParentheses(char letter)
{
return AllParentheses.Contains(letter);
}
private bool IsCorrectClosingParenthese(char openingParenthese, char closingParenthese)
{
return ParenthesesMap[openingParenthese] == closingParenthese;
}
}
---
Additionally you can easily fix the quoted text and skip it by adding a small helper method to the class:
private static IEnumerable<char> SkipQuoted(IEnumerable<char> chars)
{
var escapeMode = false;
foreach (var c in chars)
{
if (c == '"' && !escapeMode)
{
escapeMode = true;
continue;
}
if (escapeMode)
{
if (c == '"')
{
escapeMode = false;
}
continue;
}
yield return c;
}
}
You then pass the `text` first to this method before grabbing the paretheses:
foreach (char letter in SkipQuoted(text).Where(IsParentheses))