Added source code.
This commit is contained in:
361
Source/GraphingCalculator/Expression/Expression.cs
Normal file
361
Source/GraphingCalculator/Expression/Expression.cs
Normal file
@ -0,0 +1,361 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace GraphingCalculator
|
||||
{
|
||||
public class Expression
|
||||
{
|
||||
#region Token type
|
||||
public enum Token
|
||||
{
|
||||
None, Literal, Variable, Operator, Function, FunctionArgumentSeparator,
|
||||
LeftParanthesis, RightParanthesis
|
||||
};
|
||||
#endregion
|
||||
|
||||
#region Variables, properties
|
||||
private string expressionString;
|
||||
private bool isProcessed = false;
|
||||
|
||||
private List<KeyValuePair<Token, string>> tokens = new List<KeyValuePair<Token, string>>();
|
||||
private List<KeyValuePair<Token, string>> output = new List<KeyValuePair<Token, string>>();
|
||||
private Dictionary<string, double> variables = new Dictionary<string, double>();
|
||||
|
||||
protected List<KeyValuePair<Token, string>> Tokens { get { return tokens; } }
|
||||
protected List<KeyValuePair<Token, string>> Output { get { return output; } }
|
||||
public Dictionary<string, double> Variables { get { return variables; } }
|
||||
|
||||
public string ExpressionString
|
||||
{
|
||||
get { return expressionString; }
|
||||
set
|
||||
{
|
||||
expressionString = value;
|
||||
isProcessed = false;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Other stuff
|
||||
public void AddVariable(string name, double value)
|
||||
{
|
||||
variables[name] = value;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructor
|
||||
public Expression()
|
||||
{
|
||||
Variables.Add("pi", Math.PI);
|
||||
Variables.Add("e", Math.E);
|
||||
}
|
||||
|
||||
public Expression(string expr)
|
||||
: this()
|
||||
{
|
||||
expressionString = expr;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Processor
|
||||
private void Tokenize()
|
||||
{
|
||||
tokens.Clear();
|
||||
|
||||
for (int i = 0; i < ExpressionString.Length; i++)
|
||||
{
|
||||
if (char.IsWhiteSpace(ExpressionString[i])) continue;
|
||||
|
||||
if (IsOperator(ExpressionString[i]))
|
||||
{
|
||||
// Unary minus
|
||||
if (ExpressionString[i] == '-' && (tokens.Count == 0 || tokens.Last().Key == Token.LeftParanthesis || tokens.Last().Key == Token.Operator || tokens.Last().Key == Token.FunctionArgumentSeparator))
|
||||
tokens.Add(new KeyValuePair<Token, string>(Token.Operator, "!-"));
|
||||
|
||||
// Any other operator
|
||||
else tokens.Add(new KeyValuePair<Token, string>(Token.Operator, ExpressionString[i].ToString()));
|
||||
|
||||
}
|
||||
|
||||
else if (ExpressionString[i] == '(')
|
||||
tokens.Add(new KeyValuePair<Token, string>(Token.LeftParanthesis, ExpressionString[i].ToString()));
|
||||
|
||||
else if (ExpressionString[i] == ')')
|
||||
tokens.Add(new KeyValuePair<Token, string>(Token.RightParanthesis, ExpressionString[i].ToString()));
|
||||
|
||||
else if (ExpressionString[i] == ',')
|
||||
tokens.Add(new KeyValuePair<Token, string>(Token.FunctionArgumentSeparator, ExpressionString[i].ToString()));
|
||||
|
||||
else if (Char.IsDigit(ExpressionString[i]))
|
||||
tokens.Add(new KeyValuePair<Token, string>(Token.Literal, GetLiteral(ExpressionString, ref i)));
|
||||
|
||||
else if (Char.IsLetter(ExpressionString[i]))
|
||||
{
|
||||
if (IsFunction(ExpressionString, i)) tokens.Add(new KeyValuePair<Token, string>(Token.Function, GetVariable(ExpressionString, ref i)));
|
||||
else tokens.Add(new KeyValuePair<Token, string>(Token.Variable, GetVariable(ExpressionString, ref i)));
|
||||
}
|
||||
|
||||
else throw new Exception("Unrecognized character found!");
|
||||
}
|
||||
}
|
||||
|
||||
private void ShuntingYard()
|
||||
{
|
||||
Stack<KeyValuePair<Token, string>> stack = new Stack<KeyValuePair<Token, string>>();
|
||||
|
||||
foreach (var i in Tokens) switch (i.Key)
|
||||
{
|
||||
case Token.Variable:
|
||||
case Token.Literal: output.Add(i); break;
|
||||
|
||||
case Token.Function: stack.Push(i); break;
|
||||
|
||||
case Token.FunctionArgumentSeparator:
|
||||
|
||||
while (stack.Peek().Key != Token.LeftParanthesis)
|
||||
{
|
||||
output.Add(stack.Pop());
|
||||
if (stack.Count == 0) throw new Exception("Syntax error!");
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case Token.Operator:
|
||||
|
||||
if (IsLeftAssociative(i.Value))
|
||||
{
|
||||
while (stack.Count != 0 && Precedence(i.Value) <= Precedence(stack.Peek().Value))
|
||||
output.Add(stack.Pop());
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
while (stack.Count != 0 && Precedence(i.Value) < Precedence(stack.Peek().Value))
|
||||
output.Add(stack.Pop());
|
||||
}
|
||||
|
||||
stack.Push(i);
|
||||
break;
|
||||
|
||||
case Token.LeftParanthesis: stack.Push(i); break;
|
||||
|
||||
case Token.RightParanthesis:
|
||||
|
||||
while (stack.Peek().Key != Token.LeftParanthesis)
|
||||
{
|
||||
output.Add(stack.Pop());
|
||||
if (stack.Count == 0) throw new Exception("Mismatched parantheses!");
|
||||
}
|
||||
|
||||
stack.Pop(); // Pop paranthesis
|
||||
if (stack.Count > 0 && stack.Peek().Key == Token.Function) output.Add(stack.Pop()); // Pop function
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
while (stack.Count > 0)
|
||||
{
|
||||
if (stack.Peek().Key == Token.LeftParanthesis) throw new Exception("Mismatched parantheses!");
|
||||
output.Add(stack.Pop());
|
||||
}
|
||||
}
|
||||
|
||||
public void Process()
|
||||
{
|
||||
if (!isProcessed)
|
||||
{
|
||||
tokens.Clear();
|
||||
output.Clear();
|
||||
|
||||
Tokenize();
|
||||
ShuntingYard();
|
||||
}
|
||||
}
|
||||
|
||||
public double Evaluate()
|
||||
{
|
||||
Process();
|
||||
if (Output.Count == 0) return 0;
|
||||
|
||||
Stack<double> stack = new Stack<double>();
|
||||
|
||||
foreach (var i in Output)
|
||||
switch (i.Key)
|
||||
{
|
||||
case Token.Variable:
|
||||
if (!Variables.ContainsKey(i.Value)) throw new Exception("Undefined variable '" + i.Value + "'.");
|
||||
stack.Push(Variables[i.Value]);
|
||||
break;
|
||||
|
||||
case Token.Literal: stack.Push(double.Parse(i.Value)); break;
|
||||
|
||||
case Token.Operator:
|
||||
switch (i.Value)
|
||||
{
|
||||
case "!-": stack.Push(stack.Pop() * -1); break;
|
||||
case "+": stack.Push(stack.Pop() + stack.Pop()); break;
|
||||
case "-":
|
||||
{
|
||||
double b = stack.Pop();
|
||||
double a = stack.Pop();
|
||||
stack.Push(a - b);
|
||||
} break;
|
||||
|
||||
case "*": stack.Push(stack.Pop() * stack.Pop()); break;
|
||||
case "/":
|
||||
{
|
||||
double b = stack.Pop();
|
||||
double a = stack.Pop();
|
||||
stack.Push(a / b);
|
||||
} break;
|
||||
|
||||
case "%":
|
||||
{
|
||||
double b = stack.Pop();
|
||||
double a = stack.Pop();
|
||||
stack.Push(a % b);
|
||||
} break;
|
||||
|
||||
case "^":
|
||||
{
|
||||
double b = stack.Pop();
|
||||
double a = stack.Pop();
|
||||
stack.Push(Math.Pow(a, b));
|
||||
} break;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case Token.Function:
|
||||
EvaluateFunction(i.Value, ref stack);
|
||||
break;
|
||||
}
|
||||
|
||||
return stack.Pop();
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Helper routines
|
||||
|
||||
private void EvaluateFunction(string func, ref Stack<double> stack)
|
||||
{
|
||||
switch (func)
|
||||
{
|
||||
case "sin": stack.Push(Math.Sin(stack.Pop())); break;
|
||||
case "sinh": stack.Push(Math.Sinh(stack.Pop())); break;
|
||||
case "cos": stack.Push(Math.Cos(stack.Pop())); break;
|
||||
case "cosh": stack.Push(Math.Cosh(stack.Pop())); break;
|
||||
case "tan": stack.Push(Math.Tan(stack.Pop())); break;
|
||||
case "tanh": stack.Push(Math.Tanh(stack.Pop())); break;
|
||||
case "ctan": stack.Push(1 / Math.Tan(stack.Pop())); break;
|
||||
|
||||
case "arcsin": stack.Push(Math.Asin(stack.Pop())); break;
|
||||
case "asin": stack.Push(Math.Asin(stack.Pop())); break;
|
||||
case "arccos": stack.Push(Math.Acos(stack.Pop())); break;
|
||||
case "acos": stack.Push(Math.Acos(stack.Pop())); break;
|
||||
case "arctan": stack.Push(Math.Atan(stack.Pop())); break;
|
||||
case "atan": stack.Push(Math.Atan(stack.Pop())); break;
|
||||
|
||||
case "truncate":
|
||||
case "int": stack.Push(Math.Truncate(stack.Pop())); break;
|
||||
case "floor": stack.Push(Math.Floor(stack.Pop())); break;
|
||||
case "ceil":
|
||||
case "ceiling": stack.Push(Math.Ceiling(stack.Pop())); break;
|
||||
|
||||
case "sqrt": stack.Push(Math.Sqrt(stack.Pop())); break;
|
||||
case "cbrt": stack.Push(Math.Pow(stack.Pop(), 1.0 / 3.0)); break;
|
||||
case "root": stack.Push(Math.Pow(stack.Pop(), 1 / stack.Pop())); break;
|
||||
|
||||
case "abs": stack.Push(Math.Abs(stack.Pop())); break;
|
||||
case "max": stack.Push(Math.Max(stack.Pop(), stack.Pop())); break;
|
||||
case "min": stack.Push(Math.Min(stack.Pop(), stack.Pop())); break;
|
||||
case "round": {
|
||||
double dec = stack.Pop(), val = stack.Pop();
|
||||
dec = Math.Max(0, dec);
|
||||
dec = Math.Min(15, dec);
|
||||
|
||||
stack.Push(Math.Round(val, Convert.ToInt32(dec)));
|
||||
break;
|
||||
}
|
||||
|
||||
case "lg": stack.Push(Math.Log10(stack.Pop())); break;
|
||||
case "log": stack.Push(Math.Log(stack.Pop(), stack.Pop())); break;
|
||||
case "ln": stack.Push(Math.Log(stack.Pop(), Math.E)); break;
|
||||
|
||||
default: throw new Exception("Undefined function '" + func + "'.");
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsLeftAssociative(string op)
|
||||
{
|
||||
return (op != "^");
|
||||
}
|
||||
|
||||
private static int Precedence(string op)
|
||||
{
|
||||
switch (op)
|
||||
{
|
||||
case "+":
|
||||
case "-": return 1;
|
||||
|
||||
case "*":
|
||||
case "/":
|
||||
case "%": return 2;
|
||||
|
||||
case "^": return 3;
|
||||
|
||||
case "!-": return 10;
|
||||
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsOperator(char c)
|
||||
{
|
||||
const string operators = "+-*/%^";
|
||||
return operators.Contains(c);
|
||||
}
|
||||
|
||||
private static bool IsFunction(string s, int index)
|
||||
{
|
||||
// Skip function/variable name
|
||||
while (index < s.Length && char.IsLetterOrDigit(s[index])) index++;
|
||||
while (index < s.Length && char.IsWhiteSpace(s[index])) index++;
|
||||
|
||||
// End of string? Than it's a variable
|
||||
if (index >= s.Length) return false;
|
||||
|
||||
// If an operator, function separator, or ), variable
|
||||
if (s[index] == '(') return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
private static string GetVariable(string s, ref int index)
|
||||
{
|
||||
StringBuilder str = new StringBuilder();
|
||||
|
||||
while (index < s.Length && (char.IsLetterOrDigit(s[index])))
|
||||
str.Append(s[index++]);
|
||||
|
||||
index -= 1;
|
||||
return str.ToString();
|
||||
}
|
||||
|
||||
private static string GetLiteral(string s, ref int index)
|
||||
{
|
||||
StringBuilder str = new StringBuilder();
|
||||
|
||||
while (index < s.Length && (char.IsDigit(s[index]) || s[index] == '.'))
|
||||
str.Append(s[index++]);
|
||||
|
||||
index -= 1;
|
||||
return str.ToString();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
}
|
116
Source/GraphingCalculator/Expression/Integrator.cs
Normal file
116
Source/GraphingCalculator/Expression/Integrator.cs
Normal file
@ -0,0 +1,116 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace GraphingCalculator
|
||||
{
|
||||
public static class Integrator
|
||||
{
|
||||
#region Settings
|
||||
private static int maximumRecursionDepth = 16;
|
||||
public static int MaximumRecursionDepth
|
||||
{
|
||||
get { return maximumRecursionDepth; }
|
||||
set { maximumRecursionDepth = value; }
|
||||
}
|
||||
|
||||
private static double initialEpsilon = 1e-10;
|
||||
public static double InitialEpsilon
|
||||
{
|
||||
get { return initialEpsilon; }
|
||||
set { initialEpsilon = value; }
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Some variables
|
||||
private static string variable = "x";
|
||||
private static Expression expression;
|
||||
private static Dictionary<double, double> results = new Dictionary<double, double>();
|
||||
#endregion
|
||||
|
||||
#region Swap function
|
||||
private static void Swap(ref double a, ref double b)
|
||||
{
|
||||
double c = a;
|
||||
a = b;
|
||||
b = c;
|
||||
}
|
||||
#endregion
|
||||
|
||||
private static double EvaluateCache(double x)
|
||||
{
|
||||
if (!results.ContainsKey(x))
|
||||
{
|
||||
expression.Variables[variable] = x;
|
||||
results[x] = expression.Evaluate();
|
||||
}
|
||||
|
||||
return results[x];
|
||||
}
|
||||
|
||||
private static double CalculateIntegralAux(double xA, double xB, double epsilon, double S, double yA, double yB, double yC, int depth)
|
||||
{
|
||||
double xC = (xA + xB) / 2;
|
||||
double width = (xB - xA);
|
||||
double xD = (xA + xC) / 2;
|
||||
double xE = (xC + xB) / 2;
|
||||
double yD = EvaluateCache(xD);
|
||||
double yE = EvaluateCache(xE);
|
||||
|
||||
double Sleft = (width / 12) * (yA + 4 * yD + yC);
|
||||
double Sright = (width / 12) * (yC + 4 * yE + yB);
|
||||
double S2 = Sleft + Sright;
|
||||
|
||||
if (depth <= 0 || Math.Abs(S2 - S) <= 15 * epsilon)
|
||||
return S2 + (S2 - S) / 15;
|
||||
|
||||
return CalculateIntegralAux(xA, xC, epsilon / 2, Sleft, yA, yC, yD, depth - 1) +
|
||||
CalculateIntegralAux(xC, xB, epsilon / 2, Sright, yC, yB, yE, depth - 1);
|
||||
}
|
||||
|
||||
private static double CalculateIntegral(double xA, double xB, double epsilon, int maxDepth)
|
||||
{
|
||||
double xC = (xA + xB) / 2;
|
||||
double width = xB - xA;
|
||||
|
||||
double yA = EvaluateCache(xA);
|
||||
double yC = EvaluateCache(xC);
|
||||
double yB = EvaluateCache(xB);
|
||||
|
||||
double S = (width / 6) * (yA + 4 * yC + yB);
|
||||
|
||||
return CalculateIntegralAux(xA, xB, epsilon, S, yA, yB, yC, maxDepth);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates a definite integral (the area between the function graph and the X axis, where x ranges between beg and end.
|
||||
/// </summary>
|
||||
/// <param name="expr">An expression to integrate.</param>
|
||||
/// <param name="beg">Beginning of the interval</param>
|
||||
/// <param name="end">End of the interval</param>
|
||||
/// <param name="var">The name of the variable, by default is 'x'</param>
|
||||
public static double Integrate(Expression expr, double beg, double end, string var = "x")
|
||||
{
|
||||
double result = 0;
|
||||
bool changeSign = false;
|
||||
|
||||
// Make sure beg < end
|
||||
if (beg > end)
|
||||
{
|
||||
Swap(ref beg, ref end);
|
||||
changeSign = true;
|
||||
}
|
||||
|
||||
// Initialization
|
||||
results.Clear();
|
||||
variable = var;
|
||||
expression = expr;
|
||||
|
||||
// Calculation
|
||||
result = CalculateIntegral(beg, end, initialEpsilon, maximumRecursionDepth);
|
||||
if (changeSign) return -result;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
64
Source/GraphingCalculator/Expression/VisualExpression.cs
Normal file
64
Source/GraphingCalculator/Expression/VisualExpression.cs
Normal file
@ -0,0 +1,64 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace GraphingCalculator
|
||||
{
|
||||
public class VisualExpression : Expression
|
||||
{
|
||||
private static int rand_seed = 0x250D;
|
||||
|
||||
#region Variables
|
||||
private Color color = Colors.Blue;
|
||||
public Color Color { get { return color; } set { color = value; } }
|
||||
|
||||
private double thickness = 2;
|
||||
public double Thickness { get { return thickness; } set { thickness = value; } }
|
||||
|
||||
public bool IsVisible { get; set; }
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
public VisualExpression() : base()
|
||||
{
|
||||
SetRandomColor();
|
||||
IsVisible = true;
|
||||
}
|
||||
|
||||
public VisualExpression(string expr) : base(expr)
|
||||
{
|
||||
SetRandomColor();
|
||||
IsVisible = true;
|
||||
}
|
||||
|
||||
public VisualExpression(string expr, Color color, double thickness = 2) : base(expr)
|
||||
{
|
||||
this.Color = color;
|
||||
this.Thickness = thickness;
|
||||
IsVisible = true;
|
||||
}
|
||||
#endregion
|
||||
|
||||
public void SetRandomColor()
|
||||
{
|
||||
Random random = new Random(rand_seed);
|
||||
rand_seed = 0x51 * (random.Next() - 0xA82F90) * random.Next() - 0x513511;
|
||||
|
||||
int r, g, b;
|
||||
r = random.Next(256);
|
||||
g = random.Next(256);
|
||||
b = random.Next(256);
|
||||
|
||||
int avg = (r + g + b) / 3;
|
||||
int min = Math.Min(r, Math.Min(g, b));
|
||||
|
||||
if (avg >= 182) {
|
||||
r -= min; g -= min; b -= min;
|
||||
}
|
||||
|
||||
this.Color = Color.FromRgb(Convert.ToByte(r), Convert.ToByte(g), Convert.ToByte(b));
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user