Showing posts with label Snippet. Show all posts
Showing posts with label Snippet. Show all posts

Thursday, December 24, 2015

Infix Notation Parser via Shunting-Yard Algorithm





Infix notation is the typical notation for writing equations in algebra.
An example would be: 7 - (2 * 5)

Parsing such an equation is not a trivial task, but I wanted one for my EquationFinder project, as I wanted to respect order of operations.

Strategies include substitution/replacement algorithms, recursion to parse into a tree and then tree traversal, or converting the infix notation to reverse polish notation (RPN), also known as post-fix notation, then using a stack based postfix notation evaluator. I choose the latter, as such algorithms are well defined in many places on the web.

My code consists of 3 classes, all static:
(Links go to the .cs file on GitHub)
  1. InfixNotation - this simply holds a few public variables and calls the public methods on the below two classes.
  2. ShuntingYardAlgorithm - this converts an equation in infix notation into postfix notation (aka RPN).
  3. PostfixNotation - this evaluates the equation in postfix notation and returns a numerical result value.

In order to implement the shunting-yard algorithm and the postfix evaluator, I simply wrote the steps to the algorithms as written on Wikipedia:
(Links go to the Wikipedia article)
Link to the Shunting-Yard Algorithm to convert Infix notation to Postfix notation.
Link to the Postfix Notation Evaluation Algorithm.


The code for this is pretty extensive, but I will prettify it and present it below. Alternatively, you can view and download the code from the MathNotationConverter project on my GitHub.


InfixNotationParser:


public static class InfixNotation
{
   public static string Numbers = "0123456789";
   public static string Operators = "+-*/^";

   public static bool IsNumeric(string text)
   {
      return string.IsNullOrWhiteSpace(text) ? false : text.All(c => Numbers.Contains(c));
   }

  public static int Evaluate(string infixNotationString)
  {
    string postFixNotationString = ShuntingYardConverter.Convert(infixNotationString);
    int result = PostfixNotation.Evaluate(postFixNotationString);
    return result;
  }
}



ShuntingYardConverter 

(converts an equation from infix notation into postfix notation):

public static class ShuntingYardAlgorithm
{
   private static string AllowedCharacters = InfixNotation.Numbers + InfixNotation.Operators + "()";

   private enum Associativity
   {
      Left, Right
   }
   private static Dictionary<char, int> PrecedenceDictionary = new Dictionary<char, int>()
   {
      {'(', 0}, {')', 0},
      {'+', 1}, {'-', 1},
      {'*', 2}, {'/', 2},
      {'^', 3}
   };
   private static Dictionary<char, Associativity> AssociativityDictionary = new Dictionary<char, Associativity>()
   {
      {'+', Associativity.Left},
      {'-', Associativity.Left},
      {'*', Associativity.Left},
      {'/', Associativity.Left},
      {'^', Associativity.Right}
   };

   private static void AddToOutput(List<char> output, params char[] chars)
   {
      if (chars != null && chars.Length > 0)
      {
         foreach (char c in chars)
         {
            output.Add(c);
         }
         output.Add(' ');
      }
   }
   
   public static string Convert(string infixNotationString)
   {
      if (string.IsNullOrWhiteSpace(infixNotationString))
      {
         throw new ArgumentException("Argument infixNotationString must not be null, empty or whitespace.", "infixNotationString");
      }

      List<char> output = new List<char>();
      Stack<char> operatorStack = new Stack<char>();
      string sanitizedString = new string(infixNotationString.Where(c => AllowedCharacters.Contains(c)).ToArray());

      string number = string.Empty;
      List<string> enumerableInfixTokens = new List<string>();
      foreach (char c in sanitizedString)
      {
         if (InfixNotation.Operators.Contains(c) || "()".Contains(c))
         {
            if (number.Length > 0)
            {
               enumerableInfixTokens.Add(number);
               number = string.Empty;
            }
            enumerableInfixTokens.Add(c.ToString());
         }
         else if (InfixNotation.Numbers.Contains(c))
         {
            number += c.ToString();
         }
         else
         {
            throw new Exception(string.Format("Unexpected character '{0}'.", c));
         }
      }

      if (number.Length > 0)
      {
         enumerableInfixTokens.Add(number);
         number = string.Empty;
      }

      foreach (string token in enumerableInfixTokens)
      {
         if (InfixNotation.IsNumeric(token))
         {
            AddToOutput(output, token.ToArray());
         }
         else if (token.Length == 1)
         {
            char c = token[0];

            if (InfixNotation.Numbers.Contains(c)) // Numbers (operands)
            {
               AddToOutput(output, c);
            }
            else if (InfixNotation.Operators.Contains(c)) // Operators
               if (operatorStack.Count > 0)
               {
                  char o = operatorStack.Peek();
                  if ((AssociativityDictionary[c] == Associativity.Left &&
                     PrecedenceDictionary[c] <= PrecedenceDictionary[o])
                        ||
                     (AssociativityDictionary[c] == Associativity.Right &&
                     PrecedenceDictionary[c] < PrecedenceDictionary[o]))
                  {
                     AddToOutput(output, operatorStack.Pop());
                  }
               }
               operatorStack.Push(c);
            }
            else if (c == '(') // open brace
            {
               operatorStack.Push(c);
            }
            else if (c == ')') // close brace
            {
               bool leftParenthesisFound = false;
               while (operatorStack.Count > 0 )
               {
                  char o = operatorStack.Peek();
                  if (o != '(')
                  {
                     AddToOutput(output, operatorStack.Pop());
                  }
                  else
                  {
                     operatorStack.Pop();
                     leftParenthesisFound = true;
                     break;
                  }
               }

               if (!leftParenthesisFound)
               {
                  throw new FormatException("The algebraic string contains mismatched parentheses (missing a left parenthesis).");
               }
            }
            else // wtf?
            {
               throw new Exception(string.Format("Unrecognized character '{0}'.", c));
            }
         }
         else
         {
            throw new Exception(string.Format("String '{0}' is not numeric and has a length greater than 1.", token));
         }
      } // end foreach

      while (operatorStack.Count > 0)
      {
         char o = operatorStack.Pop();
         if (o == '(')
         {
            throw new FormatException("The algebraic string contains mismatched parentheses (extra left parenthesis).");
         }
         else if (o == ')')
         {
            throw new FormatException("The algebraic string contains mismatched parentheses (extra right parenthesis).");
         }
         else
         {
            AddToOutput(output, o);
         }
      }

      return new string(output.ToArray());
   }
}










PostfixNotation

(evaluates the postfix notation and returns a numerical result):

public static class PostfixNotation
{
   private static string AllowedCharacters = InfixNotation.Numbers + InfixNotation.Operators + " ";

   public static int Evaluate(string postfixNotationString)
   {
      if (string.IsNullOrWhiteSpace(postfixNotationString))
      {
         throw new ArgumentException("Argument postfixNotationString must not be null, empty or whitespace.", "postfixNotationString");
      }

      Stack<string> stack = new Stack<string>();
      string sanitizedString = new string(postfixNotationString.Where(c => AllowedCharacters.Contains(c)).ToArray());
      List<string> enumerablePostfixTokens = sanitizedString.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries).ToList();

      foreach (string token in enumerablePostfixTokens)
      {
         if (token.Length > 0)
         {
            if (token.Length > 1)
            {
               if (InfixNotation.IsNumeric(token))
               {
                  stack.Push(token);
               }
               else
               {
                  throw new Exception("Operators and operands must be separated by a space.");
               }
            }
            else
            {
               char tokenChar = token[0];

               if (InfixNotation.Numbers.Contains(tokenChar))
               {
                  stack.Push(tokenChar.ToString());
               }
               else if (InfixNotation.Operators.Contains(tokenChar))
               {
                  if (stack.Count < 2)
                  {
                     throw new FormatException("The algebraic string has not sufficient values in the expression for the number of operators.");
                  }

                  string r = stack.Pop();
                  string l = stack.Pop();

                  int rhs = int.MinValue;
                  int lhs = int.MinValue;

                  bool parseSuccess = int.TryParse(r, out rhs);
                  parseSuccess &= int.TryParse(l, out lhs);
                  parseSuccess &= (rhs != int.MinValue && lhs != int.MinValue);

                  if (!parseSuccess)
                  {
                     throw new Exception("Unable to parse valueStack characters to Int32.");
                  }

                  int value = int.MinValue;
                  if (tokenChar == '+')
                  {
                     value = lhs + rhs;
                  }
                  else if (tokenChar == '-')
                  {
                     value = lhs - rhs;
                  }
                  else if (tokenChar == '*')
                  {
                     value = lhs * rhs;
                  }
                  else if (tokenChar == '/')
                  {
                     value = lhs / rhs;
                  }
                  else if (tokenChar == '^')
                  {
                     value = (int)Math.Pow(lhs, rhs);
                  }

                  if (value != int.MinValue)
                  {
                     stack.Push(value.ToString());
                  }
                  else
                  {
                     throw new Exception("Value never got set.");
                  }
               }
               else
               {
                  throw new Exception(string.Format("Unrecognized character '{0}'.", tokenChar));
               }
            }
         }
         else
         {
            throw new Exception("Token length is less than one.");
         }
      }

      if (stack.Count == 1)
      {
         int result = 0;
         if (!int.TryParse(stack.Pop(), out result))
         {
            throw new Exception("Last value on stack could not be parsed into an integer.");
         }
         else
         {
            return result;
         }
      }
      else
      {
         throw new Exception("The input has too many values for the number of operators.");
      }

   } // method
} // class


Another alternative technique is to using the Shunting-Yard Algorithm to turn infix notation into an abstract syntax tree (Linq.Expressions anyone?). I will likely post this technique later.


Other blog posts by me that are related to this article are the Threaded Equation Finder, a Mixed Radix System Calulator and Drawing Text Along a Bezier Spline.



Wednesday, December 10, 2014

Humorous and unhelpful Exception Class hierarchy



This post covers some of the lesser-known exception classes that exist. It is okay if you have never heard of these exception classes before... in fact, if you haven’t, that is probably a very, very good thing...





WTFException
The exception that is thrown when the code has encountered a genuine ‘WTF’ moment. This is usually only reserved for areas of the code that should never be reached, or indicates a state/condition that is either invalid, unexpected, or simply illogical. It can also be thrown to indicate that the developer has no idea why something is not working, and is at his wits end, or optionally at the end of some code that the developer was absolutely certain would never work.

See Also: CodeUnreachableException, ThisShouldNeverHappenException, and SpaceTimeException.


class WTFException : Exception
{
    public WTFException()
        : base() { }
    
    public WTFException(string message, params object[] messageParams)
        : base(string.Format(message, messageParams)) { }
    
    public WTFException(string message, Exception innerException)
        : base(message, innerException) { }
}




SpaceTimeException
The exception that is thrown when there is a serious problem with the fabric of space/time. Typically, the error message property will be empty or missing, as the actual even that caused this exception to be thrown has not yet occurred in the same timeline that your applications exists in. No attempt should be made to retrieve the InnerException object, as a black hole could result that would be almost certain to annihilate your code base, or possibly all of humanity. The only thing we can be certain of, however, is that your boss will be very cross with you and you will most likely get fired.


class SpaceTimeException : Exception
{
    public SpaceTimeException()
        : base() { }

    public SpaceTimeException(string message, Exception innerException)
        : base(message, innerException) { }
}




ArgumentInvalidException
You argument is invalid. You are wrong, your reasoning makes no logical sense, and you probably failed debate class in school. Even if your argument has some semblance of being based in logic or reality, you cannot argue with the compiler, there is no point, so quit trying.


class ArgumentInvalidException : Exception
{
    public ArgumentInvalidException()
        : base() { }

    public ArgumentInvalidException(string message, Exception innerException)
        : base(message, innerException) { }   
}





CodeUnreachableExeception

This exception is thrown when the assembly executes a block of code that is unreachable. This exception should never get thrown, and is, by definition, impossible. Catching this exception is like catching a leprechaun, and should it ever happen, the CLR will ask you for three wishes.


class CodeUnreachableExeception : Exception
{
    public CodeUnreachableExeception()
        : base() { }

    public CodeUnreachableExeception(string message, Exception innerException)
        : base(message, innerException) { }
}




OutOfCoffeeExecption
This exception is thrown when a developer has ran out of coffee or sufficient motivation to complete the function or feature. The exception is thrown at the exact line that the developer got up for a coffee break and never returned.


class OutOfCoffeeExecption : Exception
{
    public OutOfCoffeeExecption()
        : base() { }

    public OutOfCoffeeExecption(string message, Exception innerException)
        : base(message, innerException) { }
}




UnknownException
This exception is not thrown because a parameter or method specified is unknown, but rather that the implementing developer did not have the knowledge to implement the feature or method you just called, and threw this exception instead as quick and dirty solution.


class UnknownException : Exception
{
    public UnknownException()
        : base() { }

    public UnknownException(string message, Exception innerException)
        : base(message, innerException) { }
}




ThisIsNotTheExceptionYouAreLookingForException
What? Oh this? Oh, this was not meant for you. Now move along...


class ThisIsNotTheExceptionYouAreLookingForException : Exception
{
    /* Move along... */
}



Thursday, June 27, 2013

PeriodicTable Element Class




Periodic Table as an Array of class Element in C#



public class Element
{
    public int AtomicNumber { get; set; }
    public string Symbol { get; set; }
    public string Name { get; set; }
    public decimal AtomicWeight { get; set; }
//  public string GroupNumber { get; set; }
//  public string GroupName { get; set; }
//  public string Period { get; set; }
//  public string Block { get; set; }
//  public string CASRegistryID { get; set; }
//  public string DiscoveryDate { get; set; }
//  public string DiscovererName { get; set; }
    
    public Element() { }
    public Element(int atomicNumber,string symbol,string name,decimal atomicWeight)
    {
        AtomicNumber = atomicNumber;
        Symbol = symbol;
        Name = name;
        AtomicWeight = atomicWeight;
    }
}

public class PeriodicTable
{
    public List<element> Elements;

    public PeriodicTable()
    {
        Elements = new List();
        Elements.Add(new Element(1,     "H",    "Hydrogen",     1.007825M ));
        Elements.Add(new Element(2,     "He",   "Helium",       4.00260M  ));
        Elements.Add(new Element(3,     "Li",   "Lithium",      6.941M    ));
        Elements.Add(new Element(4,     "Be",   "Beryllium",    9.01218M  ));
        Elements.Add(new Element(5,     "B",    "Boron",        10.81M    ));
        Elements.Add(new Element(6,     "C",    "Carbon",       12.011M   ));
        Elements.Add(new Element(7,     "N",    "Nitrogen",     14.0067M  ));
        Elements.Add(new Element(8,     "O",    "Oxygen",       15.999M   ));
        Elements.Add(new Element(9,     "F",    "Fluorine",     18.99840M ));
        Elements.Add(new Element(10,    "Ne",   "Neon",         20.179M   ));
        Elements.Add(new Element(11,    "Na",   "Sodium",       22.98977M ));
        Elements.Add(new Element(12,    "Mg",   "Magnesium",    24.305M   ));
        Elements.Add(new Element(13,    "Al",   "Aluminum",     26.98154M ));
        Elements.Add(new Element(14,    "Si",   "Silicon",      28.0855M  ));
        Elements.Add(new Element(15,    "P",    "Phosphorus",   0.0M      ));
        Elements.Add(new Element(16,    "S",    "Sulphur",      32.06M    ));
        Elements.Add(new Element(17,    "Cl",   "Chlorine",     35.453M   ));
        Elements.Add(new Element(18,    "Ar",   "Argon",        39.948M   ));
        Elements.Add(new Element(19,    "K",    "Potassium",    39.0983M  ));
        Elements.Add(new Element(20,    "Ca",   "Calcium",      40.08M    ));
        Elements.Add(new Element(21,    "Sc",   "Scandium",     44.9559M  ));
        Elements.Add(new Element(22,    "Ti",   "Titanium",     47.90M    ));
        Elements.Add(new Element(23,    "V",    "Vanadium",     50.9414M  ));
        Elements.Add(new Element(24,    "Cr",   "Chromium",     51.996M   ));
        Elements.Add(new Element(25,    "Mn",   "Manganese",    54.9380M  ));
        Elements.Add(new Element(26,    "Fe",   "Iron",         55.85M    ));
        Elements.Add(new Element(27,    "Co",   "Cobalt",       58.9332M  ));
        Elements.Add(new Element(28,    "Ni",   "Nickel",       58.71M    ));
        Elements.Add(new Element(29,    "Cu",   "Copper",       63.546M   ));
        Elements.Add(new Element(30,    "Zn",   "Zinc",         65.37M    ));
        Elements.Add(new Element(31,    "Ga",   "Gallium",      69.72M    ));
        Elements.Add(new Element(32,    "Ge",   "Germanium",    72.59M    ));
        Elements.Add(new Element(33,    "As",   "Arsenic",      74.9216M  ));
        Elements.Add(new Element(34,    "Se",   "Selenium",     78.96M    ));
        Elements.Add(new Element(35,    "Br",   "Bromine",      79.904M   ));
        Elements.Add(new Element(36,    "Kr",   "Krypton",      83.80M    ));
        Elements.Add(new Element(37,    "Rb",   "Rubidium",     85.4678M  ));
        Elements.Add(new Element(38,    "Sr",   "Strontium",    87.62M    ));
        Elements.Add(new Element(39,    "Y",    "Yttrium",      88.9059M  ));
        Elements.Add(new Element(40,    "Zr",   "Zirconium",    91.22M    ));
        Elements.Add(new Element(41,    "Nb",   "Niobium",      92.91M    ));
        Elements.Add(new Element(42,    "Mo",   "Molybdenum",   95.94M    ));
        Elements.Add(new Element(43,    "Tc",   "Technetium",   99.0M     ));
        Elements.Add(new Element(44,    "Ru",   "Ruthenium",    101.1M    ));
        Elements.Add(new Element(45,    "Rh",   "Rhodium",      102.91M   ));
        Elements.Add(new Element(46,    "Pd",   "Palladium",    106.42M   ));
        Elements.Add(new Element(47,    "Ag",   "Silver",       107.87M   ));
        Elements.Add(new Element(48,    "Cd",   "Cadmium",      112.4M    ));
        Elements.Add(new Element(49,    "In",   "Indium",       114.82M   ));
        Elements.Add(new Element(50,    "Sn",   "Tin",          118.69M   ));
        Elements.Add(new Element(51,    "Sb",   "Antimony",     121.75M   ));
        Elements.Add(new Element(52,    "Te",   "Tellurium",    127.6M    ));
        Elements.Add(new Element(53,    "I",    "Iodine",       126.9045M ));
        Elements.Add(new Element(54,    "Xe",   "Xenon",        131.29M   ));
        Elements.Add(new Element(55,    "Cs",   "Cesium",       132.9054M ));
        Elements.Add(new Element(56,    "Ba",   "Barium",       137.33M   ));
        Elements.Add(new Element(57,    "La",   "Lanthanum",    138.91M   ));
        Elements.Add(new Element(58,    "Ce",   "Cerium",       140.12M   ));
        Elements.Add(new Element(59,    "Pr",   "Praseodymium", 140.91M   ));
        Elements.Add(new Element(60,    "Nd",   "Neodymium",    0.0M      ));
        Elements.Add(new Element(61,    "Pm",   "Promethium",   147.0M    ));
        Elements.Add(new Element(62,    "Sm",   "Samarium",     150.35M   ));
        Elements.Add(new Element(63,    "Eu",   "Europium",     167.26M   ));
        Elements.Add(new Element(64,    "Gd",   "Gadolinium",   157.25M   ));
        Elements.Add(new Element(65,    "Tb",   "Terbium",      158.925M  ));
        Elements.Add(new Element(66,    "Dy",   "Dysprosium",   162.50M   ));
        Elements.Add(new Element(67,    "Ho",   "Holmium",      164.9M    ));
        Elements.Add(new Element(68,    "Er",   "Erbium",       167.26M   ));
        Elements.Add(new Element(69,    "Tm",   "Thulium",      168.93M   ));
        Elements.Add(new Element(70,    "Yb",   "Ytterbium",    173.04M   ));
        Elements.Add(new Element(71,    "Lu",   "Lutetium",     174.97M   ));
        Elements.Add(new Element(72,    "Hf",   "Hafnium",      178.49M   ));
        Elements.Add(new Element(73,    "Ta",   "Tantalum",     180.95M   ));
        Elements.Add(new Element(74,    "W",    "Tungsten",     183.85M   ));
        Elements.Add(new Element(75,    "Re",   "Rhenium",      186.23M   ));
        Elements.Add(new Element(76,    "Os",   "Osmium",       190.2M    ));
        Elements.Add(new Element(77,    "Ir",   "Iridium",      192.2M    ));
        Elements.Add(new Element(78,    "Pt",   "Platinum",     195.09M   ));
        Elements.Add(new Element(79,    "Au",   "Gold",         196.9655M ));
        Elements.Add(new Element(80,    "Hg",   "Mercury",      200.59M   ));
        Elements.Add(new Element(81,    "Tl",   "Thallium",     204.383M  ));
        Elements.Add(new Element(82,    "Pb",   "Lead",         207.2M    ));
        Elements.Add(new Element(83,    "Bi",   "Bismuth",      208.9804M ));
        Elements.Add(new Element(84,    "Po",   "Polonium",     210.0M    ));
        Elements.Add(new Element(85,    "At",   "Astatine",     210.0M    ));
        Elements.Add(new Element(86,    "Rn",   "Radon",        222.0M    ));
        Elements.Add(new Element(87,    "Fr",   "Francium",     233.0M    ));
        Elements.Add(new Element(88,    "Ra",   "Radium",       226.0254M ));
        Elements.Add(new Element(89,    "Ac",   "Actinium",     227.0M    ));
        Elements.Add(new Element(90,    "Th",   "Thorium",      232.04M   ));
        Elements.Add(new Element(91,    "Pa",   "Protactinium", 231.0359M ));
        Elements.Add(new Element(92,    "U",    "Uranium",      238.03M   ));
        Elements.Add(new Element(93,    "Np",   "Neptunium",    237.0M    ));
        Elements.Add(new Element(94,    "Pu",   "Plutonium",    244.0M    ));
        Elements.Add(new Element(95,    "Am",   "Americium",    243.0M    ));
        Elements.Add(new Element(96,    "Cm",   "Curium",       247.0M    ));
        Elements.Add(new Element(97,    "Bk",   "Berkelium",    247.0M    ));
        Elements.Add(new Element(98,    "Cf",   "Californium",  251.0M    ));
        Elements.Add(new Element(99,    "Es",   "Einsteinium",  254.0M    ));
        Elements.Add(new Element(100,   "Fm",   "Fermium",      257.0M    ));
        Elements.Add(new Element(101,   "Md",   "Mendelevium",  258.0M    ));
        Elements.Add(new Element(102,   "No",   "Nobelium",     259.0M    ));
        Elements.Add(new Element(103,   "Lr",   "Lawrencium",   262.0M    ));
        Elements.Add(new Element(104,   "Rf",   "Rutherfordium",260.9M    ));
        Elements.Add(new Element(105,   "Db",   "Dubnium",      261.9M    ));
        Elements.Add(new Element(106,   "Sg",   "Seaborgium",   262.94M   ));
        Elements.Add(new Element(107,   "Bh",   "Bohrium",      262.0M    ));
        Elements.Add(new Element(108,   "Hs",   "Hassium",      264.8M    ));
        Elements.Add(new Element(109,   "Mt",   "Meitnerium",   265.9M    ));
        Elements.Add(new Element(110,   "Ds",   "Darmstadtium", 261.9M    ));
        Elements.Add(new Element(112,   "Uub",  "Ununbium",     276.8M    ));
        Elements.Add(new Element(114,   "Uuq",  "Ununquadium",  289.0M    ));
        Elements.Add(new Element(116,   "Uuh",  "Ununhexium",   0.0M      ));
    }
}



Improvements could include implementing a the PeriodicTable as a Dictionary, such as a Dictionary<string,Element>. This adds the ability to to retrieve Element information given only its symbol or atomic number. Use SortedDictionary<string,Element> to order the elements by symbol, name, atomic number or weight (the dictionary will be sorted off of the key).


Admittedly, this still isn't a very useful class, its just for fun.