Skip to content

Commit 0c6e36d

Browse files
authored
Merge pull request #2 from miroiu/feature/math-expr
feat(StringMath): New API design
2 parents 00d91d2 + 9d4d541 commit 0c6e36d

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

58 files changed

+1416
-904
lines changed

README.md

Lines changed: 90 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,59 +1,103 @@
1-
# String Math [![NuGet](https://img.shields.io/nuget/v/StringMath?style=flat-square&logo=nuget)](https://www.nuget.org/packages/StringMath/) [![Downloads](https://img.shields.io/nuget/dt/StringMath?label=downloads&style=flat-square&logo=nuget)](https://www.nuget.org/packages/StringMath) ![.NET](https://img.shields.io/static/v1?label=%20&message=standard%202.0&color=5C2D91&style=flat-square&logo=.net) ![](https://img.shields.io/static/v1?label=%20&message=documentation&color=yellow&style=flat-square)
1+
# String Math [![NuGet](https://img.shields.io/nuget/v/StringMath?style=flat-square&logo=nuget)](https://www.nuget.org/packages/StringMath/) [![Downloads](https://img.shields.io/nuget/dt/StringMath?label=downloads&style=flat-square&logo=nuget)](https://www.nuget.org/packages/StringMath) ![.NET](https://img.shields.io/static/v1?label=%20&message=Framework%204.6.1%20to%20NET%206&color=5C2D91&style=flat-square&logo=.net) ![](https://img.shields.io/static/v1?label=%20&message=documentation&color=yellow&style=flat-square)
2+
23
Calculates the value of a math expression from a string returning a double.
34
Supports variables and user defined operators.
45

5-
### Creating and using a calculator
66
```csharp
7-
ICalculator myCalculator = new Calculator();
8-
double result = myCalculator.Evaluate("1 * (2 - 3) ^ 2"); // 1
7+
double result = "1 * (2 - 3) ^ 2".Eval(); // 1
8+
```
9+
10+
## Variables
11+
12+
```csharp
13+
double result = "{a} + 2 * {b}".Substitute("a", 2).Substitute("b", 3).Result; // 8
14+
```
15+
16+
### Global variables
17+
18+
These variables are inherited and cannot be substituted.
19+
20+
```csharp
21+
MathExpr.AddVariable("PI", 3.1415926535897931);
22+
double result = "1 + {PI}".Eval(); // 4.1415926535897931
23+
```
24+
25+
## Custom operators
26+
27+
### Global operators
28+
29+
These operators are inherited and can be overidden.
30+
31+
```csharp
32+
MathExpr.AddOperator("abs", a => a > 0 ? a : -a);
33+
double result = "abs -5".Eval(); // 5
34+
35+
// Operator precedence (you can specify an int for precedence)
36+
MathExpr.AddOperator("max", (a, b) => a > b ? a : b, Precedence.Power);
37+
double result = new MathExpr("2 * 3 max 4").Result; // 8
38+
```
39+
40+
### Local operators
41+
42+
These are applied only to the target expression.
43+
44+
```csharp
45+
MathExpr expr = "{PI} + 1";
46+
expr.SetOperator("+", (a, b) => Math.Pow(a, b));
47+
double result = expr; // 3.1415926535897931
948
10-
// Using custom operators
11-
myCalculator.AddOperator("abs", a => a > 0 ? a : -a);
12-
double result = myCalculator.Evaluate("abs -5"); // 5
49+
double result2 = "{PI} + 1".Eval(); // 4.1415926535897931
50+
```
1351

14-
// Using custom operator precedence (you can specify an int for precedence)
15-
myCalculator.AddOperator("max", (a, b) => a > b ? a : b, Precedence.Power);
16-
double result = myCalculator.Evaluate("2 * 3 max 4"); // 8
52+
## Advanced
53+
54+
### Extract variables
55+
56+
```csharp
57+
var expr = "{a} + {b} + {PI}".ToMathExpr();
58+
var variables = expr.Variables; // { "a", "b", "PI" }
59+
var localVariables = expr.LocalVariables; // { "a", "b" }
1760
```
1861

19-
### Creating and using variables
62+
### Conditional substitution
63+
2064
```csharp
21-
// Default variables collection is optional
22-
ICalculator myCalculator = new Calculator(new VariablesCollection
23-
{
24-
["a"] = 5,
25-
["PI"] = 3.1415926535897931
26-
});
27-
28-
// Setting or creating variables
29-
myCalculator.SetValue("a", 2);
30-
myCalculator["b"] = 1;
31-
32-
double result = myCalculator.Evaluate("{a} + 2 * {b} + {PI}"); // 7.1415926535897931
65+
MathExpr expr = "1 / {a}".Substitute("a", 1);
66+
67+
double temp = expr.Result; // 1
68+
69+
if (someCondition) // true
70+
expr.Substitute("a", 2);
71+
72+
double final = expr.Result; // 0.5
3373
```
3474

35-
### Creating and reusing operations
75+
### Sharing math context
76+
3677
```csharp
37-
myCalculator.SetValue("a", 5);
78+
MathExpr expr = "{PI} + 1";
79+
expr.SetOperator("+", (a, b) => Math.Pow(a, b));
3880

39-
// Creating operations (expression tree is optimized and cached)
40-
OperationInfo op = myCalculator.CreateOperation("2 * {a}");
41-
double result1 = myCalculator.Evaluate(op); // 10
81+
MathExpr expr2 = "3 + 2".ToMathExpr(expr.Context);
4282

43-
myCalculator["a"] = 3;
44-
// Reusing the operation (improved performance)
45-
double result2 = myCalculator.Evaluate(op); // 6
83+
double result = "1 + 2 + 3".Eval(expr.Context);
4684
```
4785

48-
### Using the static api: SMath
86+
### Custom math context
87+
4988
```csharp
50-
// Same API as a calculator instance
51-
double result = SMath.Evaluate("1 + 1"); // 2
52-
SMath.SetValue("myVar", 1);
53-
double result = SMath.Evaluate("1 + {myVar}", ); // 2
89+
var context = new MathContext(); // new MathContext(MathContext.Default); // to inherit from global
90+
context.RegisterBinary("+", (a, b) => Math.Pow(a, b));
91+
92+
MathExpr expr = new MathExpr("{PI} + 1", context);
93+
MathExpr expr2 = "3 + 2".ToMathExpr(context);
94+
double result = "1 + 2 + 3".Eval(context);
5495
```
5596

56-
#### Default binary operators
97+
## Default operators
98+
99+
### Binary
100+
57101
```csharp
58102
+ (addition)
59103
- (subtraction)
@@ -66,14 +110,20 @@ max (maximum)
66110
min (minimum)
67111
```
68112

69-
#### Default unary operators
113+
### Unary
114+
70115
```csharp
71116
- (negation)
72117
! (factorial)
73118
sqrt (square root)
74-
sin (sinus)
75-
cos (cosinus)
119+
sin (sine)
120+
asin (arcsine)
121+
cos (cosine)
122+
acos (arccosine)
76123
tan (tangent)
124+
atan (arctangent)
125+
rad (convert degrees to radians)
126+
deg (convert radians to degrees)
77127
ceil (ceiling)
78128
floor (floor)
79129
round (rounding)
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
namespace StringMath.Expressions.Tests
2+
{
3+
public class ExpressionsTests
4+
{
5+
[Theory]
6+
[InlineData("5 * (3 + 2)", "5 * 3 + 5 * 2")]
7+
[InlineData("5 * (3 - 2)", "5 * 3 - 5 * 2")]
8+
[InlineData("5 * (-3 - 2)", "5 * -3 - 5 * 2")]
9+
[InlineData("5 * -(-3 - 2)", "5 * 3 + 5 * 2")]
10+
[InlineData("{a} * (3 + {b})", "{a} * 3 + {a} * {b}")]
11+
[InlineData("{a} * (3 - {b})", "{a} * 3 - {a} * {b}")]
12+
public void Expand(string input, string expected)
13+
{
14+
var actual = input.Expand();
15+
16+
Assert.Equal(expected, actual.Text);
17+
}
18+
19+
[Theory]
20+
[InlineData("5^2", "25")]
21+
[InlineData("(5 - 2) * {a}", "3 * {a}")]
22+
[InlineData("(-5 + 2) * {a}", "-3 * {a}")]
23+
[InlineData("-(-5 + 2) * {a}", "3 * {a}")]
24+
[InlineData("-(-5 + 1)", "4")]
25+
[InlineData("-(-5 + -1)", "6")]
26+
[InlineData("-{a} + 2", "-{a} + 2")]
27+
public void Simplify(string input, string expected)
28+
{
29+
var actual = input.Simplify();
30+
31+
Assert.Equal(expected, actual.Text);
32+
}
33+
}
34+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net6.0</TargetFramework>
5+
<ImplicitUsings>enable</ImplicitUsings>
6+
<Nullable>enable</Nullable>
7+
8+
<IsPackable>false</IsPackable>
9+
</PropertyGroup>
10+
11+
<ItemGroup>
12+
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" />
13+
<PackageReference Include="xunit" Version="2.4.1" />
14+
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
15+
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
16+
<PrivateAssets>all</PrivateAssets>
17+
</PackageReference>
18+
<PackageReference Include="coverlet.collector" Version="3.1.2">
19+
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
20+
<PrivateAssets>all</PrivateAssets>
21+
</PackageReference>
22+
</ItemGroup>
23+
24+
<ItemGroup>
25+
<ProjectReference Include="..\StringMath.Expressions\StringMath.Expressions.csproj" />
26+
<ProjectReference Include="..\StringMath\StringMath.csproj" />
27+
</ItemGroup>
28+
29+
</Project>
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
global using Xunit;

StringMath.Expressions/Expand.cs

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
namespace StringMath.Expressions;
2+
3+
public static partial class Extensions
4+
{
5+
public static Expr Expand(this Expr expr)
6+
{
7+
static Expr E(Expr e) => e switch
8+
{
9+
// 5 * (a + b) => 5 * a + 5 * b
10+
Mul(Number num, Sum sum) => num * E(sum.Left) + num * E(sum.Right),
11+
12+
// (a + b) * 5 => 5 * a + 5 * b
13+
Mul(Sum sum, Number num) => num * E(sum.Left) + num * E(sum.Right),
14+
15+
// (a - b) * 5 => 5 * a - 5 * b
16+
Mul(Number num, Diff diff) => num * E(diff.Left) - num * E(diff.Right),
17+
18+
// 5 * (a - b) => 5 * a - 5 * b
19+
Mul(Diff diff, Number num) => num * E(diff.Left) - num * E(diff.Right),
20+
21+
// c * (a + b) => c * a + c * b
22+
Mul(Variable num, Sum sum) => num * E(sum.Left) + num * E(sum.Right),
23+
24+
// (a + b) * c => c * a + c * b
25+
Mul(Sum sum, Variable num) => num * E(sum.Left) + num * E(sum.Right),
26+
27+
// c * (a - b) => c * a - c * b
28+
Mul(Variable num, Diff diff) => num * E(diff.Left) - num * E(diff.Right),
29+
30+
// (a - b) * c => c * a - c * b
31+
Mul(Diff diff, Variable num) => num * E(diff.Left) - num * E(diff.Right),
32+
33+
// -a * -b => a * b
34+
Mul(Neg(Expr a), Neg(Expr b)) => E(a) * E(b),
35+
36+
// -(a - b) => -a + b
37+
Neg(Neg(Expr a)) => E(a),
38+
39+
// -(a - b) => -a + b
40+
Neg(Diff(Expr a, Expr b)) => -E(a) + E(b),
41+
42+
// -(a + b) => -a - b
43+
Neg(Sum(Expr a, Expr b)) => -E(a) - E(b),
44+
45+
// -a / -b => a / b
46+
Div(Neg(Expr a), Neg(Expr b)) => E(a) * E(b),
47+
48+
// x^(m + n) => x^m * x^n
49+
Pow(Expr x, Sum(Expr m, Expr n)) => (E(x) ^ E(m)) * (E(x) ^ E(n)),
50+
51+
// x^(m - n) => x^m / x^n
52+
Pow(Expr x, Diff(Expr m, Expr n)) => (E(x) ^ E(m)) / (E(x) ^ E(n)),
53+
54+
// (x^m)^n => x^(m*n)
55+
Pow(Pow(Expr x, Expr m), Expr n) => E(x) ^ (E(m) * E(n)),
56+
57+
// (x/y)^-m => (y/x)^m
58+
Pow(Div(Expr x, Expr y), Neg(Expr m)) => (E(y) / E(x)) ^ E(m),
59+
60+
// (x*y)^m => x^m & y^m
61+
Pow(Mul(Expr x, Expr y), Expr m) => (E(x) ^ E(m)) * (E(y) ^ E(m)),
62+
63+
// (x/y)^m => x^m / y^m
64+
Pow(Div(Expr x, Expr y), Expr m) => (E(x) ^ E(m)) / (E(y) ^ E(m)),
65+
66+
// x^-m => 1 / x^m
67+
Pow(Expr x, Neg(Expr m)) => 1 / (E(x) ^ E(m)),
68+
69+
// x^2 => x * x
70+
Pow(Expr x, Number(2)) => E(x) * E(x),
71+
72+
// generic
73+
Binary(Expr a, string op, Expr b) => SMath.Binary(E(a), op, E(b)),
74+
Unary(Expr a, string op) => SMath.Unary(E(a), op),
75+
76+
_ => e
77+
};
78+
79+
return E(expr);
80+
}
81+
}
82+

StringMath.Expressions/Expr.cs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
namespace StringMath.Expressions;
2+
3+
public record Expr
4+
{
5+
public static Mul operator *(Expr left, Expr right) => new(left, right);
6+
public static Div operator /(Expr left, Expr right) => new(left, right);
7+
public static Sum operator +(Expr left, Expr right) => new(left, right);
8+
public static Diff operator -(Expr left, Expr right) => new(left, right);
9+
public static Pow operator ^(Expr left, Expr right) => new(left, right);
10+
public static Mod operator %(Expr left, Expr right) => new(left, right);
11+
public static Neg operator -(Expr operand) => new(operand);
12+
13+
public static implicit operator Expr(double value) => new Number(value);
14+
public static implicit operator Expr(string value) => value.ToExpr();
15+
}
16+
17+
public record Variable(string Name) : Expr
18+
{
19+
public static implicit operator string(Variable var) => var.Name;
20+
public static implicit operator Variable(string name) => new(name);
21+
}
22+
23+
public record Number(double Value) : Expr
24+
{
25+
public static implicit operator Number(double val) => new(val);
26+
public static implicit operator double(Number num) => num.Value;
27+
}
28+
public record Binary(Expr Left, string Op, Expr Right) : Expr;
29+
public record Unary(Expr Value, string Op) : Expr;
30+
31+
public record Neg(Expr Value) : Unary(Value, "-");
32+
public record Factorial(Expr Value) : Unary(Value, "!");
33+
public record Sin(Expr Value) : Unary(Value, "sin");
34+
public record Cos(Expr Value) : Unary(Value, "cos");
35+
public record Abs(Expr Value) : Unary(Value, "abs");
36+
public record Sqrt(Expr Value) : Unary(Value, "sqrt");
37+
public record Tan(Expr Value) : Unary(Value, "tan");
38+
public record Atan(Expr Value) : Unary(Value, "atan");
39+
public record Exp(Expr Value) : Unary(Value, "exp");
40+
41+
public record Sum(Expr Left, Expr Right) : Binary(Left, "+", Right);
42+
public record Diff(Expr Left, Expr Right) : Binary(Left, "-", Right);
43+
public record Mul(Expr Left, Expr Right) : Binary(Left, "*", Right);
44+
public record Div(Expr Left, Expr Right) : Binary(Left, "/", Right);
45+
public record Pow(Expr Left, Expr Right) : Binary(Left, "^", Right);
46+
public record Mod(Expr Left, Expr Right) : Binary(Left, "%", Right);
47+
public record Log(Expr Left, Expr Right) : Binary(Left, "log", Right);

0 commit comments

Comments
 (0)