Skip to main content
Fixed code formatting
Source Link
Heslacher
  • 51k
  • 5
  • 83
  • 177
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; } }  
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; } } 
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; } }  
added 628 characters in body
Source Link
t3chb0t
  • 44.7k
  • 9
  • 84
  • 191

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)) 

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)) 
Source Link
t3chb0t
  • 44.7k
  • 9
  • 84
  • 191

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; } }