├── .gitignore
├── .travis.yml
├── LICENSE
├── Log.pch
├── MathSolver.podspec
├── MathSolver.xcodeproj
├── project.pbxproj
├── project.xcworkspace
│ └── contents.xcworkspacedata
└── xcshareddata
│ └── xcschemes
│ └── MathSolverTests.xcscheme
├── MathSolver.xcworkspace
└── contents.xcworkspacedata
├── MathSolver
├── analysis
│ ├── MTCanonicalizer.h
│ ├── MTCanonicalizer.m
│ ├── MTExpressionAnalysis.h
│ ├── MTExpressionAnalysis.m
│ ├── MTExpressionInfo.h
│ ├── MTExpressionInfo.m
│ └── rules
│ │ ├── MTCalculateRule.h
│ │ ├── MTCalculateRule.m
│ │ ├── MTCancelCommonFactorsRule.h
│ │ ├── MTCancelCommonFactorsRule.m
│ │ ├── MTCollectLikeTermsRule.h
│ │ ├── MTCollectLikeTermsRule.m
│ │ ├── MTDecimalReduceRule.h
│ │ ├── MTDecimalReduceRule.m
│ │ ├── MTDistributionRule.h
│ │ ├── MTDistributionRule.m
│ │ ├── MTDivisionIdentityRule.h
│ │ ├── MTDivisionIdentityRule.m
│ │ ├── MTFlattenRule.h
│ │ ├── MTFlattenRule.m
│ │ ├── MTIdentityRule.h
│ │ ├── MTIdentityRule.m
│ │ ├── MTNestedDivisionRule.h
│ │ ├── MTNestedDivisionRule.m
│ │ ├── MTNullRule.h
│ │ ├── MTNullRule.m
│ │ ├── MTRationalAdditionRule.h
│ │ ├── MTRationalAdditionRule.m
│ │ ├── MTRationalMultiplicationRule.h
│ │ ├── MTRationalMultiplicationRule.m
│ │ ├── MTReduceRule.h
│ │ ├── MTReduceRule.m
│ │ ├── MTRemoveNegativesRule.h
│ │ ├── MTRemoveNegativesRule.m
│ │ ├── MTReorderTermsRule.h
│ │ ├── MTReorderTermsRule.m
│ │ ├── MTRule.h
│ │ ├── MTRule.m
│ │ ├── MTZeroRule.h
│ │ └── MTZeroRule.m
└── expressions
│ ├── MTExpression.h
│ ├── MTExpression.m
│ ├── MTExpressionUtil.h
│ ├── MTExpressionUtil.m
│ ├── MTInfixParser.h
│ ├── MTInfixParser.m
│ ├── MTRational.h
│ ├── MTRational.m
│ └── internal
│ ├── MTSymbol.h
│ ├── MTSymbol.m
│ ├── MTTokenizer.h
│ └── MTTokenizer.m
├── MathSolverTests
├── ExpressionTest.m
├── ExpressionUtilTest.h
├── ExpressionUtilTest.m
├── InfixParserTest.h
├── InfixParserTest.m
├── Info.plist
├── MathSolverTests.m
├── RationalTest.m
├── TokenizerTest.h
├── TokenizerTest.m
└── rules
│ ├── CalculateRuleTest.h
│ ├── CalculateRuleTest.m
│ ├── CancelCommonFactorsRuleTest.m
│ ├── CanonicalizerTest.h
│ ├── CanonicalizerTest.m
│ ├── CollectLikeTermsRuleTest.h
│ ├── CollectLikeTermsRuleTest.m
│ ├── DistributionRuleTest.h
│ ├── DistributionRuleTest.m
│ ├── DivisionIdentityRuleTest.m
│ ├── FlattenRuleTest.h
│ ├── FlattenRuleTest.m
│ ├── IdentityRuleTest.h
│ ├── IdentityRuleTest.m
│ ├── NestedDivisionRuleTest.m
│ ├── NullRuleTest.m
│ ├── RationalAdditionRuleTest.m
│ ├── RationalMultiplicationRuleTest.m
│ ├── RemoveNegativesRuleTest.h
│ ├── RemoveNegativesRuleTest.m
│ ├── ReorderTermsRuleTest.h
│ ├── ReorderTermsRuleTest.m
│ ├── ZeroRuleTest.h
│ └── ZeroRuleTest.m
├── Podfile
└── Podfile.lock
/.gitignore:
--------------------------------------------------------------------------------
1 | # Xcode
2 | #
3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
4 |
5 | ## Build generated
6 | build/
7 | DerivedData
8 | .DS_Store
9 |
10 | ## Various settings
11 | *.pbxuser
12 | !default.pbxuser
13 | *.mode1v3
14 | !default.mode1v3
15 | *.mode2v3
16 | !default.mode2v3
17 | *.perspectivev3
18 | !default.perspectivev3
19 | xcuserdata
20 |
21 | ## Other
22 | *.xccheckout
23 | *.moved-aside
24 | *.xcuserstate
25 | *.xcscmblueprint
26 |
27 | ## Obj-C/Swift specific
28 | *.hmap
29 | *.ipa
30 |
31 | # CocoaPods
32 | #
33 | # We recommend against adding the Pods directory to your .gitignore. However
34 | # you should judge for yourself, the pros and cons are mentioned at:
35 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
36 | #
37 | Pods/
38 |
39 | # Carthage
40 | #
41 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
42 | # Carthage/Checkouts
43 |
44 | Carthage/Build
45 |
46 | # fastlane
47 | #
48 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
49 | # screenshots whenever they are needed.
50 | # For more information about the recommended setup visit:
51 | # https://github.com/fastlane/fastlane/blob/master/docs/Gitignore.md
52 |
53 | fastlane/report.xml
54 | fastlane/screenshots
55 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | # references:
2 | # * http://www.objc.io/issue-6/travis-ci.html
3 |
4 | language: objective-c
5 | osx_image: xcode7.1
6 | # cache: cocoapods
7 | before_install:
8 | - gem install cocoapods # Since Travis is not always on latest version
9 | - pod repo update # Travis doesn't have the latest specs.
10 |
11 | xcode_workspace: MathSolver.xcworkspace # path to your xcodeproj folder
12 | xcode_scheme: MathSolverTests
13 | xcode_sdk: iphonesimulator
14 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 Kostub Deshmukh
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Log.pch:
--------------------------------------------------------------------------------
1 | //
2 | // Log.pch
3 | // MathSolver
4 | //
5 | // Created by Kostub Deshmukh on 5/26/16.
6 | // Copyright © 2016 Kostub Deshmukh.
7 | //
8 | // This software may be modified and distributed under the terms of the
9 | // MIT license. See the LICENSE file for details.
10 | //
11 |
12 | #ifndef Log_pch
13 | #define Log_pch
14 |
15 | // Include any system framework and library headers here that should be included in all compilation units.
16 | // You will also need to set the Prefix Header build setting of one or more of your targets to reference this file.
17 |
18 | #if defined(DEBUG)
19 |
20 | #define InfoLog(__FORMAT__, ...) NSLog((@"%s [Line %d] $ " __FORMAT__), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__)
21 |
22 | #else
23 |
24 | #define InfoLog(...)
25 |
26 | #endif
27 |
28 | #if defined(DEBUG)
29 |
30 | #define DLog(__FORMAT__, ...) NSLog((@"[D] %s [Line %d] " __FORMAT__), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__)
31 | #define FatalLog(__FORMAT__, ...) InfoLog(__FORMAT__, ##__VA_ARGS__) ; NSAssert(false, __FORMAT__, ##__VA_ARGS__);
32 |
33 | #else
34 |
35 | #define FatalLog(__FORMAT__, ...) InfoLog(__FORMAT__, ##__VA_ARGS__)
36 | #define DLog(...)
37 |
38 | #endif
39 |
40 | #endif /* Log_pch */
41 |
--------------------------------------------------------------------------------
/MathSolver.podspec:
--------------------------------------------------------------------------------
1 | Pod::Spec.new do |s|
2 | s.name = "MathSolver"
3 | s.version = "0.2.0"
4 | s.summary = "Solver for math equations."
5 | s.description = <<-DESC
6 | MathSolver is a library for solving math equations. It can
7 | parse math equations from a string or LaTeX and then solve
8 | them to the lowest possible form.
9 | DESC
10 | s.homepage = "https://github.com/kostub/MathSolver"
11 | s.license = { :type => "MIT", :file => "LICENSE" }
12 | s.author = { "Kostub Deshmukh" => "kostub@gmail.com" }
13 | s.platform = :ios, "6.0"
14 | s.source = { :git => "https://github.com/kostub/MathSolver.git", :tag => s.version.to_s }
15 | s.source_files = 'MathSolver/**/*.{h,m}'
16 | s.prefix_header_file = 'Log.pch'
17 | s.private_header_files = 'MathSolver/**/internal/*.h', 'MathSolver/analysis/rules/*.h'
18 | s.dependency 'iosMath', '~> 0.7.2'
19 | s.requires_arc = true
20 | end
21 |
--------------------------------------------------------------------------------
/MathSolver.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/MathSolver.xcodeproj/xcshareddata/xcschemes/MathSolverTests.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
14 |
15 |
17 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
39 |
40 |
41 |
42 |
48 |
49 |
51 |
52 |
55 |
56 |
57 |
--------------------------------------------------------------------------------
/MathSolver.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/MathSolver/analysis/MTCanonicalizer.h:
--------------------------------------------------------------------------------
1 | //
2 | // Canonicalizer.h
3 | //
4 | // Created by Kostub Deshmukh on 7/20/13.
5 | // Copyright (c) 2013 Math FX.
6 | //
7 | // This software may be modified and distributed under the terms of the
8 | // MIT license. See the LICENSE file for details.
9 |
10 | #import
11 | #import "MTExpression.h"
12 |
13 | @class MTExpressionCanonicalizer;
14 | @class MTEquationCanonicalizer;
15 |
16 | @protocol MTCanonicalizer
17 |
18 | // Normalize the expression by removing -ves and extra parenthesis.
19 | - (id) normalize: (id) ex;
20 |
21 | // Convert the expression to its normal form polynomial representation
22 | // It assumes that the expression is already normalized using the function above.
23 | // i.e. axx + bx + c
24 | - (id) normalForm: (id) ex;
25 |
26 | @end
27 |
28 | @interface MTCanonicalizerFactory : NSObject
29 |
30 | // Returns the singleton instance of a canonicalizer applicable to the given entity
31 | + (id) getCanonicalizer:(id) entity;
32 |
33 | + (MTExpressionCanonicalizer*) getExpressionCanonicalizer;
34 | + (MTEquationCanonicalizer*) getEquationCanonicalizer;
35 |
36 | @end
37 |
38 | @interface MTExpressionCanonicalizer : NSObject
39 |
40 | // Normalize the expression by removing -ves and extra parenthesis.
41 | - (MTExpression*) normalize: (MTExpression*) ex;
42 |
43 | // Convert the expression to its normal form polynomial representation
44 | // It assumes that the expression is already normalized using the function above.
45 | // i.e. axx + bx + c
46 | - (MTExpression*) normalForm: (MTExpression*) ex;
47 |
48 | @end
49 |
50 | @interface MTEquationCanonicalizer : NSString
51 |
52 | // Normalize the expression by removing -ves and extra parenthesis.
53 | - (MTEquation*) normalize: (MTEquation*) ex;
54 |
55 | // Convert the expression to its normal form equaton representation
56 | // It assumes that the expression is already normalized using the function above.
57 | // i.e. xx + bx + c = 0, with the leading coefficient always 1
58 | - (MTEquation*) normalForm: (MTEquation*) ex;
59 |
60 | @end
61 |
--------------------------------------------------------------------------------
/MathSolver/analysis/MTCanonicalizer.m:
--------------------------------------------------------------------------------
1 | //
2 | // Canonicalizer.m
3 | //
4 | // Created by Kostub Deshmukh on 7/20/13.
5 | // Copyright (c) 2013 Math FX.
6 | //
7 | // This software may be modified and distributed under the terms of the
8 | // MIT license. See the LICENSE file for details.
9 | //
10 |
11 | #import "MTCanonicalizer.h"
12 | #import "MTRemoveNegativesRule.h"
13 | #import "MTFlattenRule.h"
14 | #import "MTCalculateRule.h"
15 | #import "MTIdentityRule.h"
16 | #import "MTDistributionRule.h"
17 | #import "MTZeroRule.h"
18 | #import "MTCollectLikeTermsRule.h"
19 | #import "MTReorderTermsRule.h"
20 | #import "MTExpressionUtil.h"
21 | #import "MTReduceRule.h"
22 | #import "MTNullRule.h"
23 | #import "MTNestedDivisionRule.h"
24 | #import "MTRationalAdditionRule.h"
25 | #import "MTCancelCommonFactorsRule.h"
26 | #import "MTRationalMultiplicationRule.h"
27 |
28 | @class MTExpression;
29 |
30 | @implementation MTCanonicalizerFactory
31 |
32 | + (id)getCanonicalizer:(id)entity
33 | {
34 | switch (entity.entityType) {
35 | case kMTEquation:
36 | return [self getEquationCanonicalizer];
37 | case kMTExpression:
38 | return [self getExpressionCanonicalizer];
39 | case kMTTypeAny:
40 | NSAssert(false, @"An actual entity with type any: %@", entity);
41 | return nil;
42 | }
43 | }
44 |
45 | + (MTExpressionCanonicalizer *)getExpressionCanonicalizer
46 | {
47 | static MTExpressionCanonicalizer* expCanonicalizer = nil;
48 | if (!expCanonicalizer) {
49 | expCanonicalizer = [MTExpressionCanonicalizer new];
50 | }
51 | return expCanonicalizer;
52 | }
53 |
54 | + (MTEquationCanonicalizer *)getEquationCanonicalizer
55 | {
56 | static MTEquationCanonicalizer* eqCanon = nil;
57 | if (!eqCanon) {
58 | eqCanon = [MTEquationCanonicalizer new];
59 | }
60 | return eqCanon;
61 | }
62 |
63 | @end
64 |
65 | #pragma mark - ExpressionCanonicalizer
66 |
67 | @implementation MTExpressionCanonicalizer {
68 | MTRemoveNegativesRule *_removeNegatives;
69 | MTFlattenRule *_flatten;
70 | MTReorderTermsRule *_reorder;
71 | NSArray *_canonicalizingRules;
72 | NSArray *_divisionRules;
73 | }
74 |
75 | - (id) init
76 | {
77 | self = [super init];
78 | if (self) {
79 | _removeNegatives = [MTRemoveNegativesRule rule];
80 | _flatten = [MTFlattenRule rule];
81 | _reorder = [MTReorderTermsRule rule];
82 | // All rules except division rules
83 | _canonicalizingRules = @[[MTCalculateRule rule],
84 | [MTNullRule rule],
85 | [MTIdentityRule rule],
86 | [MTZeroRule rule],
87 | [MTDistributionRule rule],
88 | [MTFlattenRule rule],
89 | [MTCollectLikeTermsRule rule],
90 | [MTReduceRule rule]];
91 | // All rules except distribution with the addition of division rules
92 | _divisionRules = @[[MTCalculateRule rule],
93 | [MTNullRule rule],
94 | [MTIdentityRule rule],
95 | [MTZeroRule rule],
96 | [MTFlattenRule rule],
97 | [MTNestedDivisionRule rule],
98 | [MTCollectLikeTermsRule rule],
99 | [MTReduceRule rule],
100 | [MTRationalAdditionRule rule],
101 | [MTRationalMultiplicationRule rule],
102 | [MTCancelCommonFactorsRule rule]];
103 | }
104 | return self;
105 | }
106 |
107 | // Normalize the expression by removing -ves and extra parenthesis.
108 | - (MTExpression*) normalize: (MTExpression*) ex
109 | {
110 | return [_flatten apply:[_removeNegatives apply:ex]];
111 | }
112 |
113 | // Canonicalize the expression to its polynomial representation
114 | // i.e. axx + bx + c
115 | - (MTExpression*) normalForm: (MTExpression*) ex {
116 | MTExpression* rationalForm = [self applyRules:_divisionRules toExpression:ex];
117 | // rationalForm should be of the form polynomial / polynomial
118 | DLog(@"Rational form: %@", rationalForm);
119 | if ([MTExpressionUtil isDivision:rationalForm]) {
120 | NSAssert(rationalForm.children.count == 2, @"Rational form should only have 2 children");
121 | MTExpression* numerator = rationalForm.children[0];
122 | MTExpression* denominator = rationalForm.children[1];
123 | // canonical form for each polynomial
124 | MTExpression* canonicalDenonimator = [self canonicalFormForPolynomial:denominator];
125 | // We make always make the leading coefficient of the denominator 1.
126 | MTRational* leadingCoefficient = [self getLeadingCoefficient:canonicalDenonimator];
127 | canonicalDenonimator = [self dividePolynomial:canonicalDenonimator byLeadingCoefficient:leadingCoefficient];
128 | MTExpression* canonicalNumerator = [self dividePolynomial:numerator byLeadingCoefficient:leadingCoefficient];
129 | return [MTOperator operatorWithType:kMTDivision args:canonicalNumerator :canonicalDenonimator];
130 | } else {
131 | // canonical form for the polynomial
132 | return [self canonicalFormForPolynomial:rationalForm];
133 | }
134 | }
135 |
136 | - (MTRational*) getLeadingCoefficient:(MTExpression*) normalPolynomial
137 | {
138 | MTExpression* leadingTerm = [MTExpressionUtil getLeadingTerm:normalPolynomial];
139 | // get the coefficient of the leading term
140 | MTRational* coefficient;
141 | NSArray* variables;
142 | BOOL rv = [MTExpressionUtil expression:leadingTerm getCoefficent:&coefficient variables:&variables];
143 | NSAssert(rv, @"Normalized expression leading term %@ not of the form Nxyz for expression %@", leadingTerm, normalPolynomial);
144 | return coefficient;
145 | }
146 |
147 | - (MTExpression*) dividePolynomial:(MTExpression*) expr byLeadingCoefficient:(MTRational*) coefficient
148 | {
149 | // divide by the leading coefficient
150 | MTExpression* dividedExpr = [MTOperator operatorWithType:kMTDivision args:expr :[MTNumber numberWithValue:coefficient]];
151 | return [self canonicalFormForPolynomial:dividedExpr];
152 | }
153 |
154 | - (MTExpression*) canonicalFormForPolynomial:(MTExpression*) poly
155 | {
156 | MTExpression* normalFormPoly = [self applyRules:_canonicalizingRules toExpression:poly];
157 | // Order the terms to be in the canonical order.
158 | return [_reorder apply:normalFormPoly];
159 | }
160 |
161 | - (MTExpression*) applyRules:(NSArray*) rules toExpression:(MTExpression*) ex
162 | {
163 | MTExpression* current = ex;
164 | BOOL modifed = YES;
165 | while (modifed) {
166 | modifed = NO;
167 | for (MTRule* rule in rules) {
168 | MTExpression* next = [rule apply:current];
169 | if (next != current) {
170 | modifed = YES;
171 | current = next;
172 | }
173 | }
174 | }
175 | return current;
176 | }
177 |
178 | @end
179 |
180 | #pragma mark - EquationCanonicalizer
181 |
182 | @implementation MTEquationCanonicalizer
183 |
184 | - (MTEquation *)normalize:(MTEquation *)eq
185 | {
186 | MTExpressionCanonicalizer* expCanon = [MTCanonicalizerFactory getExpressionCanonicalizer];
187 | MTExpression* normalizedLhs = [expCanon normalize:eq.lhs];
188 | MTExpression* normalizedRhs = [expCanon normalize:eq.rhs];
189 | return [MTEquation equationWithRelation:eq.relation lhs:normalizedLhs rhs:normalizedRhs];
190 | }
191 |
192 | - (MTEquation *)normalForm:(MTEquation *)eq
193 | {
194 | MTExpression* newLhs = [MTOperator operatorWithType:kMTSubtraction args:eq.lhs :eq.rhs];
195 | MTExpressionCanonicalizer* expCanon = [MTCanonicalizerFactory getExpressionCanonicalizer];
196 | MTExpression* normalizedNewLhs = [expCanon normalize:newLhs];
197 | MTExpression* normalForm = [expCanon normalForm:normalizedNewLhs];
198 |
199 | if (normalForm.expressionType == kMTExpressionTypeNull) {
200 | InfoLog(@"Equation mathematically invalid: %@", eq);
201 | return [MTEquation equationWithRelation:eq.relation lhs:normalForm rhs:[MTNumber numberWithValue:[MTRational zero]]];
202 | }
203 |
204 | MTExpression* lhsExpression = normalForm;
205 | if ([MTExpressionUtil isDivision:normalForm]) {
206 | // its a rational expression. We can ignore the denominator since it multiplies with 0.
207 | NSAssert(normalForm.children.count == 2, @"Division should have 2 children");
208 | lhsExpression = normalForm.children[0];
209 | }
210 |
211 | MTRational* coefficient = [expCanon getLeadingCoefficient:lhsExpression];
212 | lhsExpression = [expCanon dividePolynomial:lhsExpression byLeadingCoefficient:coefficient];
213 |
214 | return [MTEquation equationWithRelation:eq.relation lhs:lhsExpression rhs:[MTNumber numberWithValue:[MTRational zero]]];
215 | }
216 |
217 | @end
218 |
--------------------------------------------------------------------------------
/MathSolver/analysis/MTExpressionAnalysis.h:
--------------------------------------------------------------------------------
1 | //
2 | // ExpressionAnalysis.h
3 | //
4 | // Created by Kostub Deshmukh on 6/6/15.
5 | // Copyright (c) 2015 MathChat, Inc.
6 | //
7 | // This software may be modified and distributed under the terms of the
8 | // MIT license. See the LICENSE file for details.
9 |
10 | #import
11 |
12 | #import "MTExpressionInfo.h"
13 |
14 | @interface MTExpressionAnalysis : NSObject
15 |
16 | + (BOOL)hasCheckableAnswer:(MTExpressionInfo*) start;
17 |
18 | + (BOOL) isExpressionFinalStep:(MTExpressionInfo*) expressionInfo forEntityType:(MTMathEntityType) originalEntityType;
19 |
20 | @end
21 |
--------------------------------------------------------------------------------
/MathSolver/analysis/MTExpressionAnalysis.m:
--------------------------------------------------------------------------------
1 | //
2 | // ExpressionAnalysis.m
3 | //
4 | // Created by Kostub Deshmukh on 6/6/15.
5 | // Copyright (c) 2015 Math FX.
6 | //
7 | // This software may be modified and distributed under the terms of the
8 | // MIT license. See the LICENSE file for details.
9 | //
10 |
11 | #import "MTExpressionAnalysis.h"
12 |
13 | #import "MTExpressionInfo.h"
14 | #import "MTExpressionUtil.h"
15 | #import "MTReorderTermsRule.h"
16 | #import "MTDecimalReduceRule.h"
17 |
18 | @implementation MTExpressionAnalysis
19 |
20 | + (BOOL) isExpressionFinalStep:(MTExpressionInfo*) expressionInfo forEntityType:(MTMathEntityType) originalEntityType
21 | {
22 | if (originalEntityType == kMTExpression) {
23 | MTReorderTermsRule* rule = [MTReorderTermsRule rule];
24 | // apply the rules before checking equality since the user may not have the terms in the right order or reduced decimals.
25 | return [self isReducedExpression:[rule apply:expressionInfo.normalized] withNormalForm:expressionInfo.normalForm];
26 | } else if (originalEntityType == kMTEquation) {
27 | // Note: This works only for linear equations, systems of equations and quadratics may need different things to check.
28 | MTEquation* eq = (MTEquation*) expressionInfo.normalized;
29 | // x = 5 or 5 = x is acceptable.
30 | if ((eq.lhs.expressionType == kMTExpressionTypeVariable && [self isReducedEquationSide:eq.rhs])
31 | || (eq.rhs.expressionType == kMTExpressionTypeVariable && [self isReducedEquationSide:eq.lhs])) {
32 | return true;
33 | }
34 | }
35 | return false;
36 | }
37 |
38 | + (BOOL) isReducedExpression:(MTExpression*) expr withNormalForm:(MTExpression*) normalForm
39 | {
40 | MTDecimalReduceRule* decimalReduce = [MTDecimalReduceRule rule];
41 | return [normalForm isEqual:[decimalReduce apply:expr]];
42 | }
43 |
44 | + (BOOL) isReducedEquationSide:(MTExpression*) expr
45 | {
46 | MTExpressionInfo* exprInfo = [[MTExpressionInfo alloc] initWithExpression:expr input:nil];
47 | return expr.expressionType == kMTExpressionTypeNumber && [self isReducedExpression:expr withNormalForm:exprInfo.normalForm];
48 | }
49 |
50 | + (BOOL)hasCheckableAnswer:(MTExpressionInfo*) start
51 | {
52 | id expr = start.original;
53 | if ([self isExpressionFinalStep:start forEntityType:expr.entityType]) {
54 | InfoLog(@"Expression %@ cannot be solved further", expr);
55 | return NO;
56 | }
57 |
58 | switch (start.original.entityType) {
59 | case kMTExpression: {
60 | MTExpression* normal = start.normalForm;
61 | if ([MTExpressionUtil isDivision:normal]) {
62 | MTExpression* numerator = normal.children[0];
63 | MTExpression* denominator = normal.children[1];
64 | if (numerator.degree > 1 || denominator.degree > 1) {
65 | InfoLog(@"Expression %@ has numerator or denominator degree > 1", expr);
66 | return NO;
67 | }
68 | } else if (normal.degree > 1) {
69 | InfoLog(@"Expression %@ has degree > 1", expr);
70 | return NO;
71 | }
72 | if (normal.expressionType == kMTExpressionTypeNull) {
73 | InfoLog(@"Expression %@ is mathematically invalid.", expr);
74 | return NO;
75 | }
76 | NSSet* vars = [MTExpressionUtil getVariablesInExpression:normal];
77 | if (vars.count > 1) {
78 | InfoLog(@"Expression %@ has more than one variable.", expr);
79 | return NO;
80 | }
81 | break;
82 | }
83 |
84 | case kMTEquation: {
85 | MTEquation* eq = start.normalForm;
86 | // only need to check lhs since the rhs is always 0
87 | if (eq.lhs.expressionType == kMTExpressionTypeNull) {
88 | InfoLog(@"Expression %@ is mathematically invalid.", expr);
89 | return NO;
90 | } else if (eq.lhs.degree == 0) {
91 | // It is just a constant on the lhs, i.e. constant = 0.
92 | InfoLog(@"Expression %@ is not an equation.", expr);
93 | return NO;
94 | } else if (eq.lhs.degree > 1) {
95 | InfoLog(@"Expression %@ has degree > 1", expr);
96 | return NO;
97 | } else if ([MTExpressionUtil getVariablesInExpression:eq.lhs].count > 1) {
98 | InfoLog(@"Equation %@ has more than one variable", expr);
99 | return NO;
100 | }
101 | break;
102 | }
103 |
104 | case kMTTypeAny:
105 | InfoLog(@"Unknown expression type %@", start.original);
106 | return NO;
107 | }
108 |
109 | return YES;
110 | }
111 | @end
112 |
--------------------------------------------------------------------------------
/MathSolver/analysis/MTExpressionInfo.h:
--------------------------------------------------------------------------------
1 | //
2 | // ExpressonInfo.h
3 | //
4 | // Created by Kostub Deshmukh on 9/11/13.
5 | // Copyright (c) 2013 Math FX.
6 | //
7 | // This software may be modified and distributed under the terms of the
8 | // MIT license. See the LICENSE file for details.
9 |
10 | #import
11 | #import "MTExpression.h"
12 | #import "MTMathList.h"
13 |
14 | // Information about an expression
15 | @interface MTExpressionInfo : NSObject
16 |
17 | // Create an ExpressionInfo object with the parsed expression, the original unparsed input MTMathList and if present a variableName
18 | - (id) initWithExpression:(id) expression input:(MTMathList*) input variable:(NSString*) variable;
19 | // Same as above but variableName is nil
20 | - (id) initWithExpression:(id) expression input:(MTMathList*) input;
21 | // We allow empty expression infos with just a variable.
22 | - (id) initWithVariable:(NSString*) variable;
23 |
24 | - (NSString *)description;
25 |
26 | // The original expression as displayed
27 | @property (nonatomic, readonly) id original;
28 | // Normalized form of the expression
29 | @property (nonatomic, readonly) id normalized;
30 | // The expression in normal form (i.e. where 0 is 0) this is represented as A/B where A and B are polynomials or A = 0 for equations.
31 | @property (nonatomic, readonly) id normalForm;
32 | // The variable (if any) for this expression.
33 | @property (nonatomic, readonly) NSString* variableName;
34 | // The original input from the user.
35 | @property (nonatomic, readonly) MTMathList* input;
36 |
37 | @end
38 |
--------------------------------------------------------------------------------
/MathSolver/analysis/MTExpressionInfo.m:
--------------------------------------------------------------------------------
1 | //
2 | // ExpressonInfo.m
3 | //
4 | // Created by Kostub Deshmukh on 9/11/13.
5 | // Copyright (c) 2013 Math FX.
6 | //
7 | // This software may be modified and distributed under the terms of the
8 | // MIT license. See the LICENSE file for details.
9 | //
10 |
11 | #import "MTExpressionInfo.h"
12 | #import "MTCanonicalizer.h"
13 |
14 | @implementation MTExpressionInfo
15 |
16 | - (id)initWithExpression:(id)expression input:(MTMathList *)input
17 | {
18 | return [self initWithExpression:expression input:input variable:nil];
19 | }
20 |
21 | - (id)initWithVariable:(NSString *)variable
22 | {
23 | return [self initWithExpression:nil input:nil variable:variable];
24 | }
25 |
26 | - (id)initWithExpression:(id)expression input:(MTMathList *)input variable:(NSString *)variable
27 | {
28 | self = [super init];
29 | if (self) {
30 | if (expression) {
31 | id canonicalizer = [MTCanonicalizerFactory getCanonicalizer:expression];
32 | _original = expression;
33 | _normalized = [canonicalizer normalize:expression];
34 | _normalForm = [canonicalizer normalForm:_normalized];
35 | }
36 | _input = input;
37 | _variableName = [variable copy];
38 | }
39 | return self;
40 | }
41 |
42 | - (NSString *)description
43 | {
44 | NSMutableString *str = [NSMutableString string];
45 | if (self.variableName) {
46 | [str appendFormat:@"Variable: %@ ", self.variableName];
47 | }
48 | [str appendFormat:@"Original:%@ Normalized:%@ Normal Form:%@", self.original, self.normalized, self.normalForm];
49 | return str;
50 | }
51 |
52 | @end
53 |
--------------------------------------------------------------------------------
/MathSolver/analysis/rules/MTCalculateRule.h:
--------------------------------------------------------------------------------
1 | //
2 | // CalculateRule.h
3 | //
4 | // Created by Kostub Deshmukh on 7/19/13.
5 | // Copyright (c) 2013 Math FX.
6 | //
7 | // This software may be modified and distributed under the terms of the
8 | // MIT license. See the LICENSE file for details.
9 | //
10 |
11 | #import "MTRule.h"
12 |
13 | // Performs arithmetic calculations.
14 | @interface MTCalculateRule : MTRule
15 |
16 | - (MTExpression*) applyToTopLevelNode:(MTExpression *)expr withChildren:(NSArray *)args;
17 |
18 | @end
19 |
--------------------------------------------------------------------------------
/MathSolver/analysis/rules/MTCalculateRule.m:
--------------------------------------------------------------------------------
1 | //
2 | // CalculateRule.m
3 | //
4 | // Created by Kostub Deshmukh on 7/19/13.
5 | // Copyright (c) 2013 Math FX.
6 | //
7 | // This software may be modified and distributed under the terms of the
8 | // MIT license. See the LICENSE file for details.
9 | //
10 |
11 | #import "MTCalculateRule.h"
12 | #import "MTExpression.h"
13 |
14 | @implementation MTCalculateRule
15 |
16 | - (MTExpression*) applyToTopLevelNode:(MTExpression *)expr withChildren:(NSArray *)args
17 | {
18 | if (expr.expressionType != kMTExpressionTypeOperator) {
19 | return expr;
20 | }
21 | if ([expr equalsExpressionValue:kMTDivision]) {
22 |
23 | NSAssert(args.count == 2, @"Division with more than 2 arguments: %@", args);
24 |
25 | MTExpression* dividend = args[0];
26 | MTExpression* divisor = args[1];
27 | if (divisor.expressionType == kMTExpressionTypeNumber) {
28 | MTExpression* divided = [self performDivision:(MTNumber*)divisor dividend:dividend];
29 | if (divided) {
30 | return divided;
31 | }
32 | }
33 | } else if ([expr equalsExpressionValue:kMTAddition] || [expr equalsExpressionValue:kMTMultiplication]) {
34 | MTOperator* oper = (MTOperator*) expr;
35 | MTExpression* reduced = [self calculate:args operator:oper.type];
36 | if (reduced) {
37 | return reduced;
38 | }
39 | }
40 |
41 | return expr;
42 | }
43 |
44 | - (MTExpression*)performDivision:(MTNumber *)divisor dividend:(MTExpression *)dividend
45 | {
46 | // Get the coefficent of dividend
47 | MTRational* divisorValue = divisor.value;
48 | if (![divisorValue isEquivalent:[MTRational zero]]) {
49 | MTExpression* multiplication = [MTOperator operatorWithType:kMTMultiplication args:dividend :[MTNumber numberWithValue:divisorValue.reciprocal]];
50 | switch (dividend.expressionType) {
51 |
52 | case kMTExpressionTypeNumber:
53 | return [MTNumber numberWithValue:[divisorValue.reciprocal multiply:dividend.expressionValue]];
54 |
55 | case kMTExpressionTypeVariable:
56 | return multiplication;
57 |
58 | case kMTExpressionTypeOperator:
59 | if ([dividend equalsExpressionValue:kMTMultiplication]) {
60 | // for a multiplication, perform a reduce
61 | NSMutableArray* newArgs = [NSMutableArray arrayWithArray:dividend.children];
62 | [newArgs addObject:[MTNumber numberWithValue:divisorValue.reciprocal]];
63 | MTExpression* reduced = [self calculate:newArgs operator:kMTMultiplication];
64 | return (reduced) ? reduced : multiplication;
65 | } else {
66 | return multiplication;
67 | }
68 |
69 | case kMTExpressionTypeNull:
70 | return [MTNull null];
71 | }
72 | } else {
73 | // Division by 0 is not supported.
74 | return [MTNull null];
75 | }
76 | }
77 |
78 | - (MTExpression*) calculate:(NSArray *)children operator:(char)operType
79 | {
80 | NSMutableArray* newArgs = [NSMutableArray arrayWithCapacity:[children count]];
81 | NSMutableArray* numbersToOperateOn = [NSMutableArray arrayWithCapacity:[children count]];
82 |
83 | for (MTExpression *arg in children) {
84 | if ([arg isKindOfClass:[MTNumber class]]) {
85 | [numbersToOperateOn addObject:arg];
86 | } else {
87 | [newArgs addObject:arg];
88 | }
89 | }
90 | if ([numbersToOperateOn count] > 1) {
91 | MTNumber* number = [self reduce:numbersToOperateOn withOperator:operType];
92 | if ([newArgs count] == 0) {
93 | // there are no non-number expressions, so remove the operator
94 | return number;
95 | } else {
96 | [newArgs addObject:number];
97 | return [MTOperator operatorWithType:operType args:newArgs];
98 | }
99 | }
100 | return nil;
101 | }
102 |
103 |
104 | - (MTNumber*) reduce:(NSArray*) numbers withOperator:(char) operType
105 | {
106 | switch (operType) {
107 | case '+':
108 | {
109 | MTRational* answer = [MTRational zero];
110 | for (MTNumber* arg in numbers) {
111 | answer = [answer add:arg.value];
112 | }
113 | return [MTNumber numberWithValue:answer];
114 | }
115 | case '*':
116 | {
117 | MTRational* answer = [MTRational one];
118 | for (MTNumber* arg in numbers) {
119 | answer = [answer multiply:arg.value];
120 | }
121 | return [MTNumber numberWithValue:answer];
122 | }
123 |
124 | default:
125 | @throw [NSException exceptionWithName:@"RuleEvaluationError"
126 | reason:[NSString stringWithFormat:@"Unknown operator %c during evaluation", operType]
127 | userInfo:nil];
128 |
129 | }
130 | }
131 |
132 | @end
133 |
--------------------------------------------------------------------------------
/MathSolver/analysis/rules/MTCancelCommonFactorsRule.h:
--------------------------------------------------------------------------------
1 | //
2 | // CancelCommonFactorsRule.h
3 | //
4 | // Created by Kostub Deshmukh on 7/17/14.
5 | // Copyright (c) 2014 Math FX.
6 | //
7 | // This software may be modified and distributed under the terms of the
8 | // MIT license. See the LICENSE file for details.
9 | //
10 |
11 | #import "MTRule.h"
12 |
13 | @interface MTCancelCommonFactorsRule : MTRule
14 |
15 | @end
16 |
--------------------------------------------------------------------------------
/MathSolver/analysis/rules/MTCancelCommonFactorsRule.m:
--------------------------------------------------------------------------------
1 | //
2 | // CancelCommonFactorsRule.m
3 | //
4 | // Created by Kostub Deshmukh on 7/17/14.
5 | // Copyright (c) 2014 Math FX.
6 | //
7 | // This software may be modified and distributed under the terms of the
8 | // MIT license. See the LICENSE file for details.
9 | //
10 |
11 | #import "MTCancelCommonFactorsRule.h"
12 | #import "MTExpression.h"
13 | #import "MTExpressionUtil.h"
14 |
15 | @implementation MTCancelCommonFactorsRule
16 |
17 | - (MTExpression*) applyToTopLevelNode:(MTExpression *)expr withChildren:(NSArray *)args
18 | {
19 | if ([MTExpressionUtil isDivision:expr]) {
20 | NSAssert(args.count == 2, @"A division can only have 2 arguments.");
21 | MTExpression* first = args[0];
22 | MTExpression* second = args[1];
23 | NSArray* numeratorFactors = [self factors:first];
24 | NSMutableArray* denominatorFactors = [self factors:second].mutableCopy;
25 |
26 | BOOL foundCommonFactors = NO;
27 | NSMutableArray* remainingNumerators = [NSMutableArray arrayWithCapacity:numeratorFactors.count];
28 | for (MTExpression* factor in numeratorFactors) {
29 | MTExpression* common = [MTExpressionUtil getExpressionEquivalentTo:factor in:denominatorFactors];
30 | if (common) {
31 | [denominatorFactors removeObject:common];
32 | foundCommonFactors = YES;
33 | } else {
34 | [remainingNumerators addObject:factor];
35 | }
36 | }
37 |
38 | if (foundCommonFactors) {
39 | MTExpression* newNumerator = [MTExpressionUtil combineExpressions:remainingNumerators withOperatorType:kMTMultiplication];
40 | MTExpression* newDenominator = [MTExpressionUtil combineExpressions:denominatorFactors withOperatorType:kMTMultiplication];
41 | return [MTOperator operatorWithType:kMTDivision args:newNumerator :newDenominator];
42 | }
43 | }
44 | return expr;
45 | }
46 |
47 | - (NSArray*) factors:(MTExpression*) expr
48 | {
49 | if ([MTExpressionUtil isMultiplication:expr]) {
50 | return expr.children;
51 | } else {
52 | return [NSArray arrayWithObject:expr];
53 | }
54 | }
55 |
56 | @end
57 |
--------------------------------------------------------------------------------
/MathSolver/analysis/rules/MTCollectLikeTermsRule.h:
--------------------------------------------------------------------------------
1 | //
2 | // CollectLikeTermsRule.h
3 | //
4 | // Created by Kostub Deshmukh on 7/19/13.
5 | // Copyright (c) 2013 Math FX.
6 | //
7 | // This software may be modified and distributed under the terms of the
8 | // MIT license. See the LICENSE file for details.
9 | //
10 |
11 | #import "MTRule.h"
12 | #import "MTExpression.h"
13 |
14 | // Collects all terms with the same variables together and adds their coefficients.
15 | @interface MTCollectLikeTermsRule : MTRule
16 |
17 | - (MTExpression*) applyToTopLevelNode:(MTExpression *)expr withChildren:(NSArray *)args;
18 |
19 | @end
20 |
--------------------------------------------------------------------------------
/MathSolver/analysis/rules/MTCollectLikeTermsRule.m:
--------------------------------------------------------------------------------
1 | //
2 | // CollectLikeTermsRule.m
3 | //
4 | // Created by Kostub Deshmukh on 7/19/13.
5 | // Copyright (c) 2013 Math FX.
6 | //
7 | // This software may be modified and distributed under the terms of the
8 | // MIT license. See the LICENSE file for details.
9 | //
10 |
11 | #import "MTCollectLikeTermsRule.h"
12 | #import "MTExpression.h"
13 | #import "MTExpressionUtil.h"
14 |
15 | @implementation MTCollectLikeTermsRule
16 |
17 |
18 | // Collects the terms with the same variables together and adds the coefficients of the variables.
19 | // This only works for addition operators. so 5x + 3 + 2x + 5 will become 7x + 3 + 5
20 | - (MTExpression*) applyToTopLevelNode:(MTExpression *)expr withChildren:(NSArray *)args {
21 |
22 | if (![MTExpressionUtil isAddition:expr]) {
23 | return expr;
24 | }
25 |
26 | NSMutableArray* otherTerms = [NSMutableArray arrayWithCapacity:[args count]];
27 | NSMutableDictionary* dict = [NSMutableDictionary dictionary];
28 | BOOL combinedTerms = NO;
29 | for (MTExpression* arg in args) {
30 | NSArray* vars;
31 | MTRational* coefficent;
32 | if ([MTExpressionUtil expression:arg getCoefficent:&coefficent variables:&vars]) {
33 | if (arg.expressionType == kMTExpressionTypeNumber) {
34 | // numbers get combined if it is a CLT but by themselves don't trigger a CLT rule
35 | [self combineTerms:vars withValue:coefficent inDict:dict];
36 | } else {
37 | combinedTerms |= [self combineTerms:vars withValue:coefficent inDict:dict];
38 | }
39 | } else {
40 | // not combinable
41 | [otherTerms addObject:arg];
42 | }
43 | }
44 |
45 | if (combinedTerms) {
46 | // if we combined the terms, then create a new expression with the combined terms
47 | for (NSArray* key in dict) {
48 | MTRational* coeff = [dict objectForKey:key];
49 | MTNumber* coeffNum = [MTNumber numberWithValue:coeff];
50 | if (key.count > 0) {
51 | NSMutableArray* args = [NSMutableArray arrayWithObject:coeffNum];
52 | [args addObjectsFromArray:key];
53 | [otherTerms addObject:[MTOperator operatorWithType:kMTMultiplication args:args]];
54 | } else {
55 | // no variables, just a number
56 | [otherTerms addObject:coeffNum];
57 | }
58 | }
59 | assert([otherTerms count] > 0);
60 | if ([otherTerms count] == 1) {
61 | // skip the addition operator
62 | return [otherTerms lastObject];
63 | }
64 | return [MTOperator operatorWithType:kMTAddition args:otherTerms];
65 | } else {
66 | return expr;
67 | }
68 | }
69 |
70 | - (BOOL) combineTerms:(NSArray*) variables withValue:(MTRational*) value inDict:(NSMutableDictionary*) dict
71 | {
72 | MTRational* currentVal = [dict objectForKey:variables];
73 | if (currentVal) {
74 | MTRational* updatedValue = [currentVal add:value];
75 | [dict setObject:updatedValue forKey:variables];
76 | return YES;
77 | } else {
78 | [dict setObject:value forKey:variables];
79 | return NO;
80 | }
81 | }
82 |
83 |
84 |
85 | @end
86 |
--------------------------------------------------------------------------------
/MathSolver/analysis/rules/MTDecimalReduceRule.h:
--------------------------------------------------------------------------------
1 | //
2 | // DecimalReduceRule.h
3 | //
4 | // Created by Kostub Deshmukh on 10/11/13.
5 | // Copyright (c) 2013 Math FX.
6 | //
7 | // This software may be modified and distributed under the terms of the
8 | // MIT license. See the LICENSE file for details.
9 | //
10 |
11 | #import "MTReduceRule.h"
12 |
13 | // Same as the reduce rule but only applies it to Decimals.
14 | @interface MTDecimalReduceRule : MTReduceRule
15 |
16 | @end
17 |
--------------------------------------------------------------------------------
/MathSolver/analysis/rules/MTDecimalReduceRule.m:
--------------------------------------------------------------------------------
1 | //
2 | // DecimalReduceRule.m
3 | //
4 | // Created by Kostub Deshmukh on 10/11/13.
5 | // Copyright (c) 2013 Math FX.
6 | //
7 | // This software may be modified and distributed under the terms of the
8 | // MIT license. See the LICENSE file for details.
9 | //
10 |
11 | #import "MTDecimalReduceRule.h"
12 | #import "MTExpression.h"
13 |
14 | @implementation MTDecimalReduceRule
15 |
16 | - (MTExpression *)applyToTopLevelNode:(MTExpression *)expr withChildren:(NSArray *)args
17 | {
18 | if (expr.expressionType == kMTExpressionTypeNumber) {
19 | MTRational* value = expr.expressionValue;
20 | if (value.format == kMTRationalFormatDecimal) {
21 | return [super applyToTopLevelNode:expr withChildren:args];
22 | }
23 | }
24 | return expr;
25 | }
26 |
27 | @end
28 |
--------------------------------------------------------------------------------
/MathSolver/analysis/rules/MTDistributionRule.h:
--------------------------------------------------------------------------------
1 | //
2 | // DistributionRule.h
3 | //
4 | // Created by Kostub Deshmukh on 7/19/13.
5 | // Copyright (c) 2013 Math FX.
6 | //
7 | // This software may be modified and distributed under the terms of the
8 | // MIT license. See the LICENSE file for details.
9 | //
10 |
11 | #import "MTRule.h"
12 | #import "MTExpression.h"
13 |
14 | // Distributes multiplication over addition.
15 | @interface MTDistributionRule : MTRule
16 |
17 | - (MTExpression*) applyToTopLevelNode:(MTExpression *)expr withChildren:(NSArray *)args;
18 |
19 | // Returns true if a operator can be distributed on. Only expressions of the form a*(b+c) are
20 | // considered distributable. Does not recurse to children of the operator.
21 | +(BOOL) canDistribute:(MTExpression*) expr;
22 |
23 | // Get all the children of expr which can be distributed on. E.g. for a*(b+c) will return (b+c)
24 | // For a(b+c)(d+e) will return an array containing (b+c) and (d+e).
25 | +(NSArray*) getDistributees:(MTExpression*) expr;
26 |
27 | @end
28 |
--------------------------------------------------------------------------------
/MathSolver/analysis/rules/MTDistributionRule.m:
--------------------------------------------------------------------------------
1 | //
2 | // DistributionRule.m
3 | //
4 | // Created by Kostub Deshmukh on 7/19/13.
5 | // Copyright (c) 2013 Math FX.
6 | //
7 | // This software may be modified and distributed under the terms of the
8 | // MIT license. See the LICENSE file for details.
9 | //
10 |
11 | #import "MTDistributionRule.h"
12 | #import "MTExpression.h"
13 |
14 | @implementation MTDistributionRule
15 |
16 | // Distrubution distributes multiplication over addition ie. A*(B+C) becomes A*B + A*C
17 | // This rule does distribution from both left and right.
18 | - (MTExpression*) applyToTopLevelNode:(MTExpression *)expr withChildren:(NSArray *)args {
19 | if (expr.expressionType != kMTExpressionTypeOperator) {
20 | return expr;
21 | }
22 | MTOperator *oper = (MTOperator *) expr;
23 | NSMutableArray* leftMultipliers = [NSMutableArray array];
24 | NSMutableArray* rightMultipliers = [NSMutableArray array];
25 |
26 | MTOperator* distributee = [MTDistributionRule findDistributee:oper withArgs:args leftMultipliers:leftMultipliers rightMultiplers:rightMultipliers];
27 | if (!distributee) {
28 | return expr;
29 | }
30 |
31 | NSMutableArray *newArgs = [NSMutableArray arrayWithCapacity:[distributee.children count]];
32 | for (MTExpression *arg in distributee.children) {
33 | NSMutableArray *multiplicationArgs = [NSMutableArray arrayWithArray:leftMultipliers];
34 | [multiplicationArgs addObject:arg];
35 | [multiplicationArgs addObjectsFromArray:rightMultipliers];
36 | [newArgs addObject:[MTOperator operatorWithType:kMTMultiplication args:multiplicationArgs]];
37 | }
38 | return [MTOperator operatorWithType:kMTAddition args:newArgs];
39 | }
40 |
41 | // Returns nil if it cannot find a distributee.
42 | + (MTOperator*) findDistributee:(MTExpression*)op withArgs:(NSArray*) args leftMultipliers:(NSMutableArray*) leftMultipliers rightMultiplers:(NSMutableArray*) rightMultipliers
43 | {
44 | if (![MTDistributionRule isDistributableOperator:op]) {
45 | return nil;
46 | }
47 |
48 | // If any of the children are addition operators then this rule is valid.
49 | // There may be multiple children who may be addition e.g. (a + b)*(c + d)
50 | // In that case we only open the first one so it will become a * (c + d) + b * (c + d)
51 | // And the rule will need to be applied again to open the subsequent distributions.
52 | // If there are more than 2 args all will be distributed, so
53 | // a * (b + c) * d will become a * b * d + a * c * d.
54 |
55 | MTOperator *distributee = nil;
56 |
57 | for (MTExpression *arg in args) {
58 | // Find an arg to distribute on
59 | if (!distributee && [MTDistributionRule isDistributee:arg]) {
60 | distributee = (MTOperator*) arg;
61 | continue;
62 | }
63 | // For everything else, add them to the left or right multipliers as appropriate.
64 | if (distributee && rightMultipliers) {
65 | [rightMultipliers addObject:arg];
66 | } else if (!distributee && leftMultipliers) {
67 | [leftMultipliers addObject:arg];
68 | }
69 | }
70 | return distributee;
71 | }
72 |
73 | +(BOOL) isDistributee:(MTExpression*) expr
74 | {
75 | // We can only distribute over addition
76 | return expr.expressionType == kMTExpressionTypeOperator && [expr equalsExpressionValue:kMTAddition];
77 | }
78 |
79 | +(BOOL) isDistributableOperator:(MTExpression *)expr
80 | {
81 | // Only multiplication operators can have distributees
82 | return expr.expressionType == kMTExpressionTypeOperator && [expr equalsExpressionValue:kMTMultiplication];
83 | }
84 |
85 | +(BOOL) canDistribute:(MTExpression*) expr
86 | {
87 | return [self findDistributee:expr withArgs:expr.children leftMultipliers:nil rightMultiplers:nil] != nil;
88 | }
89 |
90 | +(NSArray*) getDistributees:(MTExpression*) expr
91 | {
92 | if (![self isDistributableOperator:expr]) {
93 | return nil;
94 | }
95 |
96 | NSMutableArray* array = [NSMutableArray array];
97 | for (MTExpression* arg in expr.children) {
98 | if ([self isDistributee:arg]) {
99 | [array addObject:arg];
100 | }
101 | }
102 | return array;
103 | }
104 |
105 | @end
106 |
--------------------------------------------------------------------------------
/MathSolver/analysis/rules/MTDivisionIdentityRule.h:
--------------------------------------------------------------------------------
1 | //
2 | // DivisionIdentityRule.h
3 | //
4 | // Created by Kostub Deshmukh on 7/17/14.
5 | // Copyright (c) 2014 Math FX.
6 | //
7 | // This software may be modified and distributed under the terms of the
8 | // MIT license. See the LICENSE file for details.
9 | //
10 |
11 | #import "MTRule.h"
12 |
13 | @interface MTDivisionIdentityRule : MTRule
14 |
15 | @end
16 |
--------------------------------------------------------------------------------
/MathSolver/analysis/rules/MTDivisionIdentityRule.m:
--------------------------------------------------------------------------------
1 | //
2 | // DivisionIdentityRule.m
3 | //
4 | // Created by Kostub Deshmukh on 7/17/14.
5 | // Copyright (c) 2014 Math FX.
6 | //
7 | // This software may be modified and distributed under the terms of the
8 | // MIT license. See the LICENSE file for details.
9 | //
10 |
11 | #import "MTDivisionIdentityRule.h"
12 | #import "MTExpression.h"
13 | #import "MTExpressionUtil.h"
14 |
15 | @implementation MTDivisionIdentityRule
16 |
17 | - (MTExpression*) applyToTopLevelNode:(MTExpression *)expr withChildren:(NSArray *)args
18 | {
19 | if ([MTExpressionUtil isDivision:expr]) {
20 | NSAssert(args.count == 2, @"A division can only have 2 arguments.");
21 | MTExpression* first = args[0];
22 | MTExpression* second = args[1];
23 | if ([second isEqual:[MTExpressionUtil getIdentity:kMTMultiplication]]) {
24 | return first;
25 | }
26 | }
27 | return expr;
28 | }
29 |
30 | @end
31 |
--------------------------------------------------------------------------------
/MathSolver/analysis/rules/MTFlattenRule.h:
--------------------------------------------------------------------------------
1 | //
2 | // FlattenRule.h
3 | //
4 | // Created by Kostub Deshmukh on 7/19/13.
5 | // Copyright (c) 2013 Math FX.
6 | //
7 | // This software may be modified and distributed under the terms of the
8 | // MIT license. See the LICENSE file for details.
9 | //
10 |
11 | #import
12 | #import "MTRule.h"
13 |
14 | @class MTExpression;
15 |
16 | // Flattens operators from binary to n-ary.
17 | @interface MTFlattenRule : MTRule
18 |
19 | - (MTExpression*) applyToTopLevelNode:(MTExpression *)expr withChildren:(NSArray *)args;
20 |
21 | @end
22 |
--------------------------------------------------------------------------------
/MathSolver/analysis/rules/MTFlattenRule.m:
--------------------------------------------------------------------------------
1 | //
2 | // FlattenRule.m
3 | //
4 | // Created by Kostub Deshmukh on 7/19/13.
5 | // Copyright (c) 2013 Math FX.
6 | //
7 | // This software may be modified and distributed under the terms of the
8 | // MIT license. See the LICENSE file for details.
9 | //
10 |
11 | #import "MTFlattenRule.h"
12 | #import "MTExpression.h"
13 | #import "MTExpressionUtil.h"
14 |
15 | @implementation MTFlattenRule
16 |
17 | - (MTExpression*) applyToTopLevelNode:(MTExpression *)expr withChildren:(NSArray *)args
18 | {
19 | if ([self canFlatten:expr]) {
20 | NSMutableArray* newArgs = [NSMutableArray arrayWithCapacity:[args count]];
21 | MTOperator *oper = (MTOperator *) expr;
22 | BOOL flattened = NO;
23 | for (MTExpression *arg in args) {
24 | // if the operator is of the same type as the parent, we can flatten out any arguments since our operators are commutative and associative.
25 | if (arg.expressionType == kMTExpressionTypeOperator && [arg equalsExpressionValue:oper.type]) {
26 | [newArgs addObjectsFromArray:[arg children]];
27 | flattened = YES;
28 | } else {
29 | [newArgs addObject:arg];
30 | }
31 | }
32 | if (flattened) {
33 | // at least one child was flattened then rebuild the expression
34 | // Note: the range does not change.
35 | return [MTOperator operatorWithType:oper.type args:newArgs range:oper.range];
36 | }
37 | }
38 | return expr;
39 | }
40 |
41 | - (BOOL) canFlatten:(MTExpression*) expr
42 | {
43 | // Only addition and multiplication are commutative & associative.
44 | return [MTExpressionUtil isAddition:expr] || [MTExpressionUtil isMultiplication:expr];
45 | }
46 |
47 |
48 | @end
49 |
--------------------------------------------------------------------------------
/MathSolver/analysis/rules/MTIdentityRule.h:
--------------------------------------------------------------------------------
1 | //
2 | // IdentityRule.h
3 | //
4 | // Created by Kostub Deshmukh on 7/20/13.
5 | // Copyright (c) 2013 Math FX.
6 | //
7 | // This software may be modified and distributed under the terms of the
8 | // MIT license. See the LICENSE file for details.
9 | //
10 |
11 | #import "MTRule.h"
12 | #import "MTExpression.h"
13 |
14 | // Removes addititive and multiplicative identities.
15 | @interface MTIdentityRule : MTRule
16 |
17 | - (MTExpression*) applyToTopLevelNode:(MTExpression *)expr withChildren:(NSArray *)args;
18 |
19 | @end
20 |
--------------------------------------------------------------------------------
/MathSolver/analysis/rules/MTIdentityRule.m:
--------------------------------------------------------------------------------
1 | //
2 | // IdentityRule.m
3 | //
4 | // Created by Kostub Deshmukh on 7/20/13.
5 | // Copyright (c) 2013 Math FX.
6 | //
7 | // This software may be modified and distributed under the terms of the
8 | // MIT license. See the LICENSE file for details.
9 | //
10 |
11 | #import "MTIdentityRule.h"
12 | #import "MTExpression.h"
13 | #import "MTExpressionUtil.h"
14 |
15 | @implementation MTIdentityRule
16 |
17 | - (MTExpression*) applyToTopLevelNode:(MTExpression *)expr withChildren:(NSArray *)args
18 | {
19 | // Removes addition and multiplication identities from the operators.
20 | if (expr.expressionType != kMTExpressionTypeOperator) {
21 | return expr;
22 | }
23 | MTOperator *oper = (MTOperator *) expr;
24 | MTNumber* identity = [MTExpressionUtil getIdentity:oper.type];
25 | if (!identity) {
26 | return expr;
27 | }
28 | for (MTExpression *arg in args) {
29 | if ([arg isEqual:identity]) {
30 | NSMutableArray* newArgs = [NSMutableArray arrayWithArray:args];
31 | [newArgs removeObject:arg];
32 | assert([newArgs count] > 0);
33 | if ([newArgs count] == 1) {
34 | return [newArgs lastObject];
35 | } else {
36 | return [MTOperator operatorWithType:oper.type args:newArgs];
37 | }
38 | }
39 | }
40 |
41 | return expr;
42 | }
43 |
44 | @end
45 |
--------------------------------------------------------------------------------
/MathSolver/analysis/rules/MTNestedDivisionRule.h:
--------------------------------------------------------------------------------
1 | //
2 | // NestedDivisionRule.h
3 | //
4 | // Created by Kostub Deshmukh on 7/16/14.
5 | // Copyright (c) 2014 Math FX.
6 | //
7 | // This software may be modified and distributed under the terms of the
8 | // MIT license. See the LICENSE file for details.
9 | //
10 |
11 | #import "MTRule.h"
12 |
13 | // If there is a division inside a division, this converts the second division to a multiplication
14 | // e.g. (a/b) / c => a / (b*c)
15 | // and a / (b/c) => (a*c) / b
16 | @interface MTNestedDivisionRule : MTRule
17 |
18 | @end
19 |
--------------------------------------------------------------------------------
/MathSolver/analysis/rules/MTNestedDivisionRule.m:
--------------------------------------------------------------------------------
1 | //
2 | // NestedDivisionRule.m
3 | //
4 | // Created by Kostub Deshmukh on 7/16/14.
5 | // Copyright (c) 2014 Math FX.
6 | //
7 | // This software may be modified and distributed under the terms of the
8 | // MIT license. See the LICENSE file for details.
9 | //
10 |
11 | #import "MTNestedDivisionRule.h"
12 | #import "MTExpression.h"
13 | #import "MTExpressionUtil.h"
14 |
15 | @implementation MTNestedDivisionRule
16 |
17 | - (MTExpression*) applyToTopLevelNode:(MTExpression *)expr withChildren:(NSArray *)args
18 | {
19 | if ([MTExpressionUtil isDivision:expr]) {
20 | NSAssert(args.count == 2, @"A division can only have 2 arguments.");
21 | MTExpression* first = args[0];
22 | MTExpression* second = args[1];
23 |
24 | if ([MTExpressionUtil isDivision:first] ) {
25 | return [self simplifyNumeratorIsDivision:(MTOperator*) first denominator:second];
26 | } else if ([MTExpressionUtil isDivision:second]) {
27 | return [self simplifyDenominatorIsDivision:first denominator:(MTOperator*) second];
28 | }
29 | }
30 | return expr;
31 | }
32 |
33 | // (a/b) / c becomes a / (b*c)
34 | - (MTExpression*) simplifyNumeratorIsDivision:(MTOperator*) numerator denominator:(MTExpression*) denominator
35 | {
36 | NSAssert(numerator.type == kMTDivision, @"Expected numerator to be division");
37 | NSAssert(numerator.children.count == 2, @"Division can only have 2 arguments");
38 |
39 | MTExpression* nFirst = numerator.children[0];
40 | MTExpression* nSecond = numerator.children[1];
41 |
42 | MTOperator* newDenominator = [MTOperator operatorWithType:kMTMultiplication args:nSecond :denominator];
43 | return [MTOperator operatorWithType:kMTDivision args:nFirst :newDenominator];
44 | }
45 |
46 | // a / (b/c) becomes (a*c) / b
47 | - (MTExpression*) simplifyDenominatorIsDivision:(MTExpression*) numerator denominator:(MTOperator*) denominator
48 | {
49 | NSAssert(denominator.type == kMTDivision, @"Expected numerator to be division");
50 | NSAssert(denominator.children.count == 2, @"Division can only have 2 arguments");
51 |
52 | MTExpression* dFirst = denominator.children[0];
53 | MTExpression* dSecond = denominator.children[1];
54 |
55 | MTOperator* newNumerator = [MTOperator operatorWithType:kMTMultiplication args:numerator :dSecond];
56 | return [MTOperator operatorWithType:kMTDivision args:newNumerator :dFirst];
57 | }
58 |
59 |
60 | @end
61 |
--------------------------------------------------------------------------------
/MathSolver/analysis/rules/MTNullRule.h:
--------------------------------------------------------------------------------
1 | //
2 | // NullRule.h
3 | //
4 | // Created by Kostub Deshmukh on 7/15/14.
5 | // Copyright (c) 2014 Math FX.
6 | //
7 | // This software may be modified and distributed under the terms of the
8 | // MIT license. See the LICENSE file for details.
9 | //
10 |
11 | #import "MTRule.h"
12 |
13 | // Removes any subexpressions which are FxNull
14 | @interface MTNullRule : MTRule
15 |
16 | @end
17 |
--------------------------------------------------------------------------------
/MathSolver/analysis/rules/MTNullRule.m:
--------------------------------------------------------------------------------
1 | //
2 | // NullRule.m
3 | //
4 | // Created by Kostub Deshmukh on 7/15/14.
5 | // Copyright (c) 2014 Math FX.
6 | //
7 | // This software may be modified and distributed under the terms of the
8 | // MIT license. See the LICENSE file for details.
9 | //
10 |
11 | #import "MTNullRule.h"
12 | #import "MTExpression.h"
13 |
14 | @implementation MTNullRule
15 |
16 | - (MTExpression *)applyToTopLevelNode:(MTExpression *)expr withChildren:(NSArray *)args
17 | {
18 | // This rule only applies to operators
19 | if (expr.expressionType != kMTExpressionTypeOperator) {
20 | return expr;
21 | }
22 | // If any argument is null, this returns null.
23 | for (MTExpression *arg in args) {
24 | if (arg.expressionType == kMTExpressionTypeNull) {
25 | return arg;
26 | }
27 | }
28 | return expr;
29 | }
30 |
31 | @end
32 |
--------------------------------------------------------------------------------
/MathSolver/analysis/rules/MTRationalAdditionRule.h:
--------------------------------------------------------------------------------
1 | //
2 | // RationalAdditionRule.h
3 | //
4 | // Created by Kostub Deshmukh on 7/16/14.
5 | // Copyright (c) 2014 Math FX.
6 | //
7 | // This software may be modified and distributed under the terms of the
8 | // MIT license. See the LICENSE file for details.
9 | //
10 |
11 | #import "MTRule.h"
12 |
13 | // Rule for adding rational functions. Does
14 | // a/b + c => (a + b*c) / b
15 | // TODO: Optimize by pulling out the common factors before adding.
16 | @interface MTRationalAdditionRule : MTRule
17 |
18 | @end
19 |
--------------------------------------------------------------------------------
/MathSolver/analysis/rules/MTRationalAdditionRule.m:
--------------------------------------------------------------------------------
1 | //
2 | // RationalAdditionRule.m
3 | //
4 | // Created by Kostub Deshmukh on 7/16/14.
5 | // Copyright (c) 2014 Math FX.
6 | //
7 | // This software may be modified and distributed under the terms of the
8 | // MIT license. See the LICENSE file for details.
9 | //
10 |
11 | #import "MTRationalAdditionRule.h"
12 | #import "MTExpression.h"
13 | #import "MTExpressionUtil.h"
14 |
15 | @implementation MTRationalAdditionRule
16 |
17 | - (MTExpression*) applyToTopLevelNode:(MTExpression *)expr withChildren:(NSArray *)args
18 | {
19 | if ([MTExpressionUtil isAddition:expr]) {
20 | MTOperator* division = nil;
21 | NSMutableArray* addends = [NSMutableArray arrayWithCapacity:args.count];
22 | for (MTExpression* arg in args) {
23 | if (division == nil && [MTExpressionUtil isDivision:arg]) {
24 | division = (MTOperator*) arg;
25 | } else {
26 | [addends addObject:arg];
27 | }
28 | }
29 |
30 | if (division) {
31 | // Add all the addends together.
32 | // a/b + c => (a + (b*c)) / b. mulitplier is c.
33 | NSAssert(addends.count > 0, @"There should be at least one addend");
34 | MTOperator* multiplier;
35 | if (addends.count == 1) {
36 | multiplier = addends[0];
37 | } else {
38 | multiplier = [MTOperator operatorWithType:kMTAddition args:addends];
39 | }
40 | NSAssert(division.children.count == 2, @"Division should have exactly 2 children");
41 | MTExpression* numerator = division.children[0];
42 | MTExpression* denominator = division.children[1];
43 | // a/b + c => (a + (b*c)) / b. numeratorAddend is b*c.
44 | MTOperator* numeratorAddend = [MTOperator operatorWithType:kMTMultiplication args:denominator :multiplier];
45 | MTOperator* newNumerator = [MTOperator operatorWithType:kMTAddition args:numerator :numeratorAddend];
46 | return [MTOperator operatorWithType:kMTDivision args:newNumerator :denominator];
47 | }
48 | }
49 | return expr;
50 | }
51 |
52 | @end
53 |
--------------------------------------------------------------------------------
/MathSolver/analysis/rules/MTRationalMultiplicationRule.h:
--------------------------------------------------------------------------------
1 | //
2 | // RationalMultiplicationRule.h
3 | //
4 | // Created by Kostub Deshmukh on 7/16/14.
5 | // Copyright (c) 2014 Math FX.
6 | //
7 | // This software may be modified and distributed under the terms of the
8 | // MIT license. See the LICENSE file for details.
9 | //
10 |
11 | #import "MTRule.h"
12 |
13 | // This allows you to multiply two rational functions:
14 | // i.e. a/b * (c/d) = (a*c) / (b*d)
15 | @interface MTRationalMultiplicationRule : MTRule
16 |
17 | @end
18 |
--------------------------------------------------------------------------------
/MathSolver/analysis/rules/MTRationalMultiplicationRule.m:
--------------------------------------------------------------------------------
1 | //
2 | // RationalMultiplicationRule.m
3 | //
4 | // Created by Kostub Deshmukh on 7/16/14.
5 | // Copyright (c) 2014 Math FX.
6 | //
7 | // This software may be modified and distributed under the terms of the
8 | // MIT license. See the LICENSE file for details.
9 | //
10 |
11 | #import "MTRationalMultiplicationRule.h"
12 | #import "MTExpression.h"
13 | #import "MTExpressionUtil.h"
14 |
15 | @implementation MTRationalMultiplicationRule
16 |
17 |
18 | - (MTExpression*) applyToTopLevelNode:(MTExpression *)expr withChildren:(NSArray *)args
19 | {
20 | if ([MTExpressionUtil isMultiplication:expr]) {
21 | BOOL applicable = NO;
22 | // collect all the numerators & denominators
23 | NSMutableArray* numerators = [NSMutableArray arrayWithCapacity:args.count];
24 | NSMutableArray* denominators = [NSMutableArray arrayWithCapacity:args.count];
25 | for (MTExpression* arg in args) {
26 | if ([MTExpressionUtil isDivision:arg]) {
27 | applicable = YES;
28 | NSAssert(arg.children.count == 2, @"Division should have exactly 2 arguments.");
29 | [numerators addObject:arg.children[0]];
30 | [denominators addObject:arg.children[1]];
31 | } else {
32 | [numerators addObject:arg];
33 | }
34 | }
35 |
36 | if (applicable) {
37 | // Multiply all the numerators together
38 | MTOperator* numerator = [MTOperator operatorWithType:kMTMultiplication args:numerators];
39 | NSAssert(denominators.count > 0, @"There should be at least one denominator for this rule to be applicable.");
40 | MTExpression* denominator = denominators[0];
41 | if (denominators.count > 1) {
42 | // If there is more than one denominator, multiply them all.
43 | denominator = [MTOperator operatorWithType:kMTMultiplication args:denominators];
44 | }
45 | return [MTOperator operatorWithType:kMTDivision args:numerator :denominator];
46 | }
47 | }
48 | return expr;
49 | }
50 |
51 | @end
52 |
--------------------------------------------------------------------------------
/MathSolver/analysis/rules/MTReduceRule.h:
--------------------------------------------------------------------------------
1 | //
2 | // ReduceRule.h
3 | //
4 | // Created by Kostub Deshmukh on 9/11/13.
5 | // Copyright (c) 2013 Math FX.
6 | //
7 | // This software may be modified and distributed under the terms of the
8 | // MIT license. See the LICENSE file for details.
9 | //
10 |
11 | #import "MTRule.h"
12 |
13 | // Reduces rationals to their simplest form
14 | @interface MTReduceRule : MTRule
15 |
16 | @end
17 |
--------------------------------------------------------------------------------
/MathSolver/analysis/rules/MTReduceRule.m:
--------------------------------------------------------------------------------
1 | //
2 | // ReduceRule.m
3 | //
4 | // Created by Kostub Deshmukh on 9/11/13.
5 | // Copyright (c) 2013 Math FX.
6 | //
7 | // This software may be modified and distributed under the terms of the
8 | // MIT license. See the LICENSE file for details.
9 | //
10 |
11 | #import "MTReduceRule.h"
12 | #import "MTExpression.h"
13 |
14 | @implementation MTReduceRule
15 |
16 | - (MTExpression *)applyToTopLevelNode:(MTExpression *)expr withChildren:(NSArray *)args
17 | {
18 | if (expr.expressionType == kMTExpressionTypeNumber) {
19 | MTRational* value = expr.expressionValue;
20 | if (!value.isReduced) {
21 | return [MTNumber numberWithValue:value.reduced];
22 | }
23 | }
24 | return expr;
25 | }
26 |
27 | @end
28 |
--------------------------------------------------------------------------------
/MathSolver/analysis/rules/MTRemoveNegativesRule.h:
--------------------------------------------------------------------------------
1 | //
2 | // RemoveNegativesRule.h
3 | //
4 | // Created by Kostub Deshmukh on 7/18/13.
5 | // Copyright (c) 2013 Math FX.
6 | //
7 | // This software may be modified and distributed under the terms of the
8 | // MIT license. See the LICENSE file for details.
9 | //
10 |
11 | #import
12 | #import "MTRule.h"
13 |
14 | @class MTExpression;
15 |
16 | // Removes -ve signs and subtraction operators.
17 | @interface MTRemoveNegativesRule : MTRule
18 |
19 | - (MTExpression*) applyToTopLevelNode:(MTExpression *)expr withChildren:(NSArray *)args;
20 |
21 | @end
22 |
--------------------------------------------------------------------------------
/MathSolver/analysis/rules/MTRemoveNegativesRule.m:
--------------------------------------------------------------------------------
1 | //
2 | // RemoveNegativesRule.m
3 | //
4 | // Created by Kostub Deshmukh on 7/18/13.
5 | // Copyright (c) 2013 Math FX.
6 | //
7 | // This software may be modified and distributed under the terms of the
8 | // MIT license. See the LICENSE file for details.
9 | //
10 |
11 | #import "MTRemoveNegativesRule.h"
12 | #import "MTExpression.h"
13 | #import "MTExpressionUtil.h"
14 |
15 | @implementation MTRemoveNegativesRule
16 |
17 | - (MTExpression*) applyToTopLevelNode:(MTExpression *)expr withChildren:(NSArray *)args
18 | {
19 | // traverse the expressions to find any -ve signs and unary minus
20 | if ([expr isKindOfClass:[MTOperator class]]) {
21 | MTOperator *oper = (MTOperator *) expr;
22 | if (oper.type == kMTUnaryMinus) {
23 | assert([args count] == 1);
24 | MTExpression *arg1 = [args lastObject];
25 | MTExpression* arg1WithNegative = [self addNegativeSignIfPossible:arg1];
26 | if (arg1WithNegative) {
27 | return arg1WithNegative;
28 | } else {
29 | // convert unary minus: - A to: (-1) * A
30 | return [MTExpressionUtil negate:arg1];
31 | }
32 | } else if (oper.type == kMTSubtraction) {
33 | // convert subtraction: A - B to: A + (-1) * B
34 | assert([args count] == 2);
35 | MTExpression *arg1 = [args objectAtIndex:0];
36 | MTExpression *arg2 = [args lastObject];
37 | MTExpression* arg2WithNegative = [self addNegativeSignIfPossible:arg2];
38 | if (arg2WithNegative) {
39 | return [MTOperator operatorWithType:kMTAddition args:arg1 :arg2WithNegative range:expr.range];
40 | } else {
41 | return [MTOperator operatorWithType:kMTAddition args:arg1 :[MTExpressionUtil negate:arg2] range:expr.range];
42 | }
43 | }
44 | }
45 | return expr;
46 | }
47 |
48 | - (MTExpression*) addNegativeSignIfPossible:(MTExpression*) expr
49 | {
50 | if (expr.expressionType == kMTExpressionTypeNumber) {
51 | // convert the number to it's negative
52 | MTNumber* num = (MTNumber *) expr;
53 | return [MTNumber numberWithValue:num.value.negation range:num.range];
54 | } else if (expr.expressionType == kMTExpressionTypeOperator && [expr equalsExpressionValue:kMTMultiplication]) {
55 | // recurse
56 | MTExpression* neg = [self addNegativeSignIfPossible:expr.children[0]];
57 | if (neg) {
58 | NSArray* args = [NSArray arrayWithObject:neg];
59 | args = [args arrayByAddingObjectsFromArray:[expr.children subarrayWithRange:NSMakeRange(1, expr.children.count - 1)]];
60 | return [MTOperator operatorWithType:kMTMultiplication args:args range:expr.range];
61 | }
62 | }
63 | return nil;
64 | }
65 | @end
66 |
--------------------------------------------------------------------------------
/MathSolver/analysis/rules/MTReorderTermsRule.h:
--------------------------------------------------------------------------------
1 | //
2 | // ReorderTermsRule.h
3 | //
4 | // Created by Kostub Deshmukh on 7/21/13.
5 | // Copyright (c) 2013 Math FX.
6 | //
7 | // This software may be modified and distributed under the terms of the
8 | // MIT license. See the LICENSE file for details.
9 | //
10 |
11 | #import
12 | #import "MTRule.h"
13 |
14 | // Reorder the terms in descending order of degree and then by lexicographic order.
15 | // This should only be used for one level deep trees. The results for multilevel expression trees are not guaranteed to be what you expect.
16 | @interface MTReorderTermsRule : MTRule
17 |
18 | - (MTExpression*) applyToTopLevelNode:(MTExpression *)expr withChildren:(NSArray *)args;
19 |
20 | @end
21 |
--------------------------------------------------------------------------------
/MathSolver/analysis/rules/MTReorderTermsRule.m:
--------------------------------------------------------------------------------
1 | //
2 | // ReorderTermsRule.m
3 | //
4 | // Created by Kostub Deshmukh on 7/21/13.
5 | // Copyright (c) 2013 Math FX.
6 | //
7 | // This software may be modified and distributed under the terms of the
8 | // MIT license. See the LICENSE file for details.
9 | //
10 |
11 | #import "MTReorderTermsRule.h"
12 | #import "MTExpression.h"
13 |
14 | @implementation MTReorderTermsRule
15 |
16 | unsigned long getDegree(MTExpression* expr) {
17 | if (expr.hasDegree) {
18 | return [expr degree];
19 | } else {
20 | return LONG_MAX;
21 | }
22 | }
23 |
24 | // Returns the variables as an array. Also counts the number of sub operators (i.e. operators which are children of this operator) and returns them in subOperatorCount
25 | NSArray* getVariables(MTOperator* op, int* subOperatorCount) {
26 | NSMutableArray* vars = [NSMutableArray array];
27 | *subOperatorCount = 0;
28 | for (MTExpression* expr in [op children]) {
29 | if (expr.expressionType == kMTExpressionTypeVariable) {
30 | [vars addObject:expr];
31 | } else if (expr.expressionType == kMTExpressionTypeOperator) {
32 | (*subOperatorCount)++;
33 | }
34 | }
35 | return vars;
36 | }
37 |
38 | NSComparisonResult compareOperatorsForAddition(MTOperator *op1, MTOperator *op2) {
39 | // These operators have the same degree.
40 | // There arguments should have been sorted by now.
41 |
42 | int op1Count = 0, op2Count = 0;
43 | NSArray* var1 = getVariables(op1, &op1Count);
44 | NSArray* var2 = getVariables(op2, &op2Count);
45 |
46 | if (op1Count && op2Count) {
47 | // if both expressions have operators then they are the same,
48 | // we can't really order x(x+1) and x(x+2) and we don't need to, since this only applies at the end of expansion.
49 | return NSOrderedSame;
50 | } else if (op2Count) {
51 | return NSOrderedAscending; // operators always come after variables
52 | } else if (op1Count) {
53 | return NSOrderedDescending; // operators always come after variables
54 | } else {
55 | assert(!op1Count && !op2Count);
56 | // They have the same degree and no operators so the number of variables must be the same.
57 | assert([var1 count] == [var2 count]);
58 | // The variables should already be in lexicographic order. So pick the first one that is different.
59 | for (int i = 0; i < [var1 count]; ++i) {
60 | MTVariable *v1 = [var1 objectAtIndex:i];
61 | MTVariable *v2 = [var2 objectAtIndex:i];
62 | NSComparisonResult result = [v1 compare:v2];
63 | if (result != NSOrderedSame) {
64 | return result;
65 | }
66 | }
67 | // If the variables all match up then they are ordered same.
68 | return NSOrderedSame;
69 | }
70 | }
71 |
72 | NSComparisonResult compareVariableToOperatorForAddition(MTOperator *op1, MTVariable *op2) {
73 | int subOpCount = 0;
74 | NSArray* vars = getVariables(op1, &subOpCount);
75 | if (subOpCount > 0) {
76 | return NSOrderedDescending;
77 | }
78 | // there should only be one variable since the degrees match.
79 | assert([vars count] == 1);
80 | MTVariable* op1Var = [vars lastObject];
81 | return [op1Var compare:op2];
82 | }
83 |
84 | NSArray* reorderForMultiplication(const NSArray* args) {
85 | return[args sortedArrayUsingComparator:^NSComparisonResult(id obj1, id obj2) {
86 | if ([obj1 isKindOfClass:[MTNumber class]]) {
87 | if ([obj2 isKindOfClass:[MTNumber class]]) {
88 | MTNumber* n1 = obj1;
89 | return [n1 compare:obj2];
90 | } else {
91 | // numbers come before operators and variables
92 | return NSOrderedAscending;
93 | }
94 | } else if ([obj1 isKindOfClass:[MTVariable class]]) {
95 | if ([obj2 isKindOfClass:[MTNumber class]]) {
96 | // numbers come before variables.
97 | return NSOrderedDescending;
98 | } else if ([obj2 isKindOfClass:[MTVariable class]]) {
99 | MTVariable* v1 = obj1;
100 | return [v1 compare:obj2];
101 | } else {
102 | assert([obj2 isKindOfClass:[MTOperator class]]);
103 | // variables always come before operators
104 | return NSOrderedAscending;
105 | }
106 | } else if ([obj1 isKindOfClass:[MTOperator class]]) {
107 | if([obj2 isKindOfClass:[MTOperator class]]) {
108 | // Should 2x + 1 be lower than x + 2? Don't know, don't really care since this rule should be applied after canoncicalization. Return same for simplicity.
109 | return NSOrderedSame;
110 | } else {
111 | // operators come after numbers and variables
112 | return NSOrderedDescending;
113 | }
114 | } else {
115 | @throw [NSException exceptionWithName:@"InternalException"
116 | reason:[NSString stringWithFormat:@"Unknown Expression class %@", [obj1 class], nil]
117 | userInfo:nil];
118 | }
119 | }];
120 | }
121 |
122 | // Note there is some advantage of ordering the operators in a different way for optimizing gcd calculations. Consult book to figure that out.
123 | NSArray* reorderForAddition(const NSArray* args) {
124 | return[args sortedArrayUsingComparator:^NSComparisonResult(id obj1, id obj2) {
125 | MTExpression* ex1 = obj1;
126 | MTExpression* ex2 = obj2;
127 | if (!ex1.hasDegree && !ex2.hasDegree) {
128 | // These are degree less and can't be ordered, just say they are the same.
129 | return NSOrderedSame;
130 | }
131 | long obj1Degree = getDegree(obj1);
132 | long obj2Degree = getDegree(obj2);
133 | if (obj1Degree > obj2Degree) {
134 | return NSOrderedAscending;
135 | } else if (obj2Degree > obj1Degree) {
136 | return NSOrderedDescending;
137 | } else {
138 | // lexicographic ordering of variables
139 | if ([obj1 isKindOfClass:[MTNumber class]]) {
140 | if ([obj2 isKindOfClass:[MTNumber class]]) {
141 | MTNumber* n1 = obj1;
142 | return [n1 compare:obj2];
143 | } else {
144 | assert([obj2 isKindOfClass:[MTOperator class]]);
145 | // numbers come before operators
146 | return NSOrderedAscending;
147 | }
148 | } else if ([obj1 isKindOfClass:[MTVariable class]]) {
149 | if ([obj2 isKindOfClass:[MTVariable class]]) {
150 | MTVariable* v1 = obj1;
151 | return [v1 compare:obj2];
152 | } else {
153 | assert([obj2 isKindOfClass:[MTOperator class]]);
154 | NSComparisonResult result = compareVariableToOperatorForAddition(obj2, obj1);
155 | // note the compare function compares obj2 to obj1, so we need to reverse the result
156 | if (result == NSOrderedDescending) {
157 | return NSOrderedAscending;
158 | } else if (result == NSOrderedAscending) {
159 | return NSOrderedDescending;
160 | } else {
161 | return NSOrderedSame;
162 | }
163 | }
164 | } else if ([obj1 isKindOfClass:[MTOperator class]]) {
165 | if([obj2 isKindOfClass:[MTOperator class]]) {
166 | return compareOperatorsForAddition(obj1, obj2);
167 | } else if ([obj2 isKindOfClass:[MTVariable class]]) {
168 | return compareVariableToOperatorForAddition(obj1, obj2);
169 | } else {
170 | // operators come after numbers
171 | return NSOrderedDescending;
172 | }
173 | } else {
174 | @throw [NSException exceptionWithName:@"InternalException"
175 | reason:[NSString stringWithFormat:@"Unknown Expression class %@", [obj1 class], nil]
176 | userInfo:nil];
177 | }
178 | }
179 | }];
180 | }
181 |
182 | - (MTExpression*) applyToTopLevelNode:(MTExpression *)expr withChildren:(NSArray *)args
183 | {
184 | // Removes addition and multiplication identities from the operators.
185 | if (![expr isKindOfClass:[MTOperator class]]) {
186 | return expr;
187 | }
188 | MTOperator *oper = (MTOperator *) expr;
189 |
190 | NSArray* sortedArgs = nil;
191 | if (oper.type == kMTMultiplication) {
192 | sortedArgs = reorderForMultiplication(args);
193 | } else if (oper.type == kMTAddition) {
194 | sortedArgs = reorderForAddition(args);
195 | } else {
196 | // No ordering implemented for other operators.
197 | return expr;
198 | }
199 | return [MTOperator operatorWithType:oper.type args:sortedArgs];
200 | }
201 |
202 | @end
203 |
--------------------------------------------------------------------------------
/MathSolver/analysis/rules/MTRule.h:
--------------------------------------------------------------------------------
1 | //
2 | // Rule.h
3 | //
4 | // Created by Kostub Deshmukh on 7/19/13.
5 | // Copyright (c) 2013 Math FX.
6 | //
7 | // This software may be modified and distributed under the terms of the
8 | // MIT license. See the LICENSE file for details.
9 | //
10 |
11 | #import
12 |
13 | @class MTExpression;
14 |
15 | @interface MTRule : NSObject
16 |
17 | // create a rule
18 | + (instancetype) rule;
19 |
20 | // Does a recursive post-order traversal of the expression, applying the rule.
21 | - (MTExpression*) apply:(MTExpression*) expr;
22 |
23 | // Apply the rule only to the top level node. Subclasses need to implement this method. The children already have the rule applied to them.
24 | - (MTExpression*) applyToTopLevelNode:(MTExpression *)expr withChildren:(NSArray*) args;
25 |
26 | // Apply the rule to the inner most level. Only make one application if onlyFirst is true.
27 | - (MTExpression*) applyInnerMost:(MTExpression *)expr onlyFirst:(BOOL) onlyFirst;
28 |
29 | @end
30 |
--------------------------------------------------------------------------------
/MathSolver/analysis/rules/MTRule.m:
--------------------------------------------------------------------------------
1 | //
2 | // Rule.m
3 | //
4 | // Created by Kostub Deshmukh on 7/19/13.
5 | // Copyright (c) 2013 Math FX.
6 | //
7 | // This software may be modified and distributed under the terms of the
8 | // MIT license. See the LICENSE file for details.
9 | //
10 |
11 | #import "MTRule.h"
12 | #import "MTExpression.h"
13 |
14 | @implementation MTRule
15 |
16 | + (instancetype) rule {
17 | return [[self alloc] init];
18 | }
19 |
20 | - (MTExpression*) apply:(MTExpression *)expr
21 | {
22 | // This does a post order traversal of the Expression tree.
23 | NSArray *args = expr.children;
24 | NSMutableArray* modifiedArgs = [NSMutableArray arrayWithCapacity:[args count]];
25 | BOOL newExpressionNeeded = NO;
26 | for (MTExpression* child in args) {
27 | MTExpression* modified = [self apply:child];
28 | if (modified != child) {
29 | newExpressionNeeded = YES;
30 | }
31 | [modifiedArgs addObject:modified];
32 | }
33 |
34 | MTExpression* updatedExpr = [self applyToTopLevelNode:expr withChildren:modifiedArgs];
35 | if (updatedExpr != expr) {
36 | return updatedExpr;
37 | } else if (newExpressionNeeded) {
38 | // The args were modified even if the top level node wasn't, so recreate the top level node with the new args.
39 |
40 | // currently only operators have children, but in the future we could have functions too.
41 | MTOperator *oper = (MTOperator *) expr;
42 | return [MTOperator operatorWithType:oper.type args:modifiedArgs range:expr.range];
43 | }
44 |
45 | return expr;
46 | }
47 |
48 | - (MTExpression*) applyToTopLevelNode:(MTExpression *)expr withChildren:(NSArray *)args
49 | {
50 | @throw [NSException exceptionWithName:@"InternalException"
51 | reason:[NSString stringWithFormat:@"You must override %@ in a subclass", NSStringFromSelector(_cmd)]
52 | userInfo:nil];
53 | }
54 |
55 | - (MTExpression*) applyInnerMost:(MTExpression *)expr onlyFirst:(BOOL) onlyFirst
56 | {
57 | // This does a post order traversal of the Expression tree.
58 | NSArray *args = expr.children;
59 | NSMutableArray* modifiedArgs = [NSMutableArray arrayWithCapacity:[args count]];
60 | BOOL newExpressionNeeded = NO;
61 | for (MTExpression* child in args) {
62 | if (!onlyFirst || !newExpressionNeeded) {
63 | MTExpression* modified = [self applyInnerMost:child onlyFirst:onlyFirst];
64 | if (modified != child) {
65 | newExpressionNeeded = YES;
66 | }
67 | [modifiedArgs addObject:modified];
68 | } else {
69 | [modifiedArgs addObject:child];
70 | }
71 | }
72 |
73 | if (newExpressionNeeded) {
74 | // The args were modified which means that the rule was applied. Do not apply the rule to the top level, since we only apply to the inner most level.
75 | MTOperator *oper = (MTOperator *) expr;
76 | return [MTOperator operatorWithType:oper.type args:modifiedArgs];
77 | } else {
78 | return [self applyToTopLevelNode:expr withChildren:expr.children];
79 | }
80 | }
81 |
82 | @end
83 |
--------------------------------------------------------------------------------
/MathSolver/analysis/rules/MTZeroRule.h:
--------------------------------------------------------------------------------
1 | //
2 | // ZeroRule.h
3 | //
4 | // Created by Kostub Deshmukh on 7/20/13.
5 | // Copyright (c) 2013 Math FX.
6 | //
7 | // This software may be modified and distributed under the terms of the
8 | // MIT license. See the LICENSE file for details.
9 | //
10 |
11 | #import "MTRule.h"
12 |
13 | // Removes expressions multiplied by 0.
14 | @interface MTZeroRule : MTRule
15 |
16 | @end
17 |
--------------------------------------------------------------------------------
/MathSolver/analysis/rules/MTZeroRule.m:
--------------------------------------------------------------------------------
1 | //
2 | // ZeroRule.m
3 | //
4 | // Created by Kostub Deshmukh on 7/20/13.
5 | // Copyright (c) 2013 Math FX.
6 | //
7 | // This software may be modified and distributed under the terms of the
8 | // MIT license. See the LICENSE file for details.
9 | //
10 |
11 | #import "MTZeroRule.h"
12 | #import "MTExpression.h"
13 |
14 | @implementation MTZeroRule
15 |
16 | - (MTExpression*) applyToTopLevelNode:(MTExpression *)expr withChildren:(NSArray *)args
17 | {
18 | // Multiplication by 0 returns 0
19 | if (expr.expressionType != kMTExpressionTypeOperator || ![expr equalsExpressionValue:kMTMultiplication]) {
20 | return expr;
21 | }
22 | for (MTExpression *arg in args) {
23 | if (arg.expressionType == kMTExpressionTypeNumber && [arg.expressionValue isEquivalent:[MTRational zero]]) {
24 | return arg;
25 | }
26 | }
27 | return expr;
28 | }
29 |
30 | @end
31 |
--------------------------------------------------------------------------------
/MathSolver/expressions/MTExpression.h:
--------------------------------------------------------------------------------
1 | //
2 | // Expression.h
3 | //
4 | // Created by Kostub Deshmukh on 7/14/13.
5 | // Copyright (c) 2013 Math FX.
6 | //
7 | // This software may be modified and distributed under the terms of the
8 | // MIT license. See the LICENSE file for details.
9 | //
10 |
11 | @import Foundation;
12 |
13 | #import
14 | #import "MTRational.h"
15 |
16 | extern const char kMTUnaryMinus;
17 | extern const char kMTSubtraction;
18 | extern const char kMTAddition;
19 | extern const char kMTMultiplication;
20 | extern const char kMTDivision;
21 |
22 | typedef enum {
23 | kMTTypeAny = 0,
24 | kMTExpression,
25 | kMTEquation,
26 | } MTMathEntityType;
27 |
28 | @protocol MTMathEntity
29 |
30 | - (NSString*) stringValue;
31 |
32 | - (MTMathEntityType) entityType;
33 |
34 | /**
35 | * Returns true if the two entities are to be considered equivalent for the purposes.
36 | * of correctness. This is not the same as equals as certain entities may be considered
37 | * equivalent even though they may not be equal. e.g. 0.33 and 1/3.
38 | * @param entity The entity to compare with.
39 | */
40 | - (BOOL) isEquivalent:(id) entity;
41 |
42 | @end
43 |
44 | @interface MTExpression : NSObject
45 |
46 | enum MTExpressionType {
47 | kMTExpressionTypeNumber = 1,
48 | kMTExpressionTypeVariable,
49 | kMTExpressionTypeOperator,
50 | kMTExpressionTypeNull,
51 | };
52 |
53 | // The range in the original MTMathList that created it, that this expression denotes.
54 | // Note range is only present when the Expression is created by the parser. For subsequent manipulations range is not required and not maintained.
55 | @property (nonatomic, readonly) MTMathListRange* range;
56 |
57 | // Returns a copy of the expression with the given range.
58 | - (MTExpression*) expressionWithRange:(MTMathListRange*) range;
59 |
60 | // The children of this expression, all of whom are expressions themselves.
61 | - (NSArray*) children;
62 |
63 | // The degree of the expression.
64 | - (NSUInteger) degree;
65 |
66 | // If the expression has a degree. If hasDegree returns false, do not call degree. The result may be unpredictable.
67 | - (BOOL) hasDegree;
68 |
69 | - (enum MTExpressionType) expressionType;
70 |
71 | - (id) expressionValue;
72 |
73 | // Returns true if the expression has the given value
74 | - (BOOL) equalsExpressionValue:(int) value;
75 | - (BOOL) isExpressionValueEqualToNumber:(NSNumber*) number;
76 |
77 | - (BOOL) isEqualUptoRearrangement:(MTExpression*) expr;
78 |
79 | // Same as above but recursive
80 | - (BOOL) isEqualUptoRearrangementRecursive:(MTExpression*) expr;
81 |
82 | @end
83 |
84 |
85 | @interface MTNumber : MTExpression
86 |
87 | @property (nonatomic, readonly) MTRational* value;
88 |
89 | +(id) numberWithValue:(MTRational*) value;
90 | +(id) numberWithValue:(MTRational*) value range:(MTMathListRange*) range;
91 |
92 | - (BOOL) isEqualToNumber:(MTNumber*) number;
93 | - (NSComparisonResult) compare: (MTNumber*) aNumber;
94 |
95 | @end
96 |
97 | @interface MTVariable : MTExpression
98 |
99 | @property (nonatomic, readonly) char name;
100 |
101 | +(id) variableWithName:(char) name;
102 | +(id) variableWithName:(char) name range:(MTMathListRange*) range;
103 |
104 | - (NSComparisonResult) compare: (MTVariable*) aVariable;
105 |
106 | @end
107 |
108 | @interface MTOperator : MTExpression
109 |
110 | @property (nonatomic, readonly) char type;
111 |
112 | // binary
113 | +(id) operatorWithType:(char)type args:(MTExpression *)arg1 :(MTExpression *)arg2 range:(MTMathListRange*)range;
114 | +(id) operatorWithType:(char) type args:(MTExpression*) arg1 :(MTExpression*) arg2;
115 |
116 | // unary
117 | +(id) unaryOperatorWithType:(char) type arg:(MTExpression*) arg range:(MTMathListRange*) range;
118 |
119 | +(id) operatorWithType:(char)type args:(NSArray*) args;
120 | +(id) operatorWithType:(char)type args:(NSArray*) args range:(MTMathListRange*) range;
121 |
122 | @end
123 |
124 | @interface MTNull : MTExpression
125 |
126 | // Returns the singleton FXNull object
127 | + (instancetype) null;
128 |
129 | @end
130 |
131 | @interface MTEquation : NSObject
132 |
133 | +(id) equationWithRelation:(char) relation lhs:(MTExpression *)lhs rhs:(MTExpression*) rhs;
134 |
135 | @property (nonatomic, readonly) char relation; // = > < etc.
136 |
137 | @property (nonatomic, readonly) MTExpression* lhs;
138 | @property (nonatomic, readonly) MTExpression* rhs;
139 |
140 | @end
141 |
--------------------------------------------------------------------------------
/MathSolver/expressions/MTExpressionUtil.h:
--------------------------------------------------------------------------------
1 | //
2 | // ExpressionUtil.h
3 | //
4 | // Created by Kostub Deshmukh on 7/29/13.
5 | // Copyright (c) 2013 Math FX.
6 | //
7 | // This software may be modified and distributed under the terms of the
8 | // MIT license. See the LICENSE file for details.
9 | //
10 |
11 | #import
12 | #import "MTExpression.h"
13 |
14 | // Utility functions for modifing expressions.
15 | @interface MTExpressionUtil : NSObject
16 |
17 | // Negate an expression by multiplying it by -1
18 | + (MTOperator*) negate:(MTExpression*) expr;
19 |
20 | // Convert a variable to an operator by multiplying it by 1
21 | + (MTOperator*) toOperator:(MTExpression*) var;
22 |
23 | // Returns true if the expression is of the form Nxyz, N is returned as the coefficient, x,y,z are returned as variables.
24 | // If the expression is not of the required form return false. The returned variables are sorted by name.
25 | // Either argument could be nil
26 | + (BOOL) expression: (MTExpression*) oper getCoefficent:(MTRational**) c variables:(NSArray**) vars;
27 | // Returns true if the expression is of the form Nxyz, returns |N|xyz by stripping away the -ve sign from N if any.
28 | // If the expression is not of the required form return the original expression.
29 | + (MTExpression*) absoluteValue:(MTExpression*) expr;
30 | // Formats an expression of the form Nxyz as Nxyz. If the expression is not of the required form, return nil.
31 | + (NSString*) formatAsString:(MTExpression*) expr;
32 |
33 | // Returns true if expr is equivalent to other after doing top level calculations.
34 | + (BOOL) isEquivalentUptoCalculation:(MTExpression*) expr toExpression:(MTExpression*) other;
35 |
36 | // Finds an expression in the given array which is equivalent (upto calculation) to expr.
37 | + (MTExpression*) getExpressionEquivalentTo:(MTExpression*) expr in:(NSArray*) array;
38 |
39 | + (BOOL) isEquivalentUptoCalculationAndRearrangement:(NSArray*) array1 :(NSArray*) array2;
40 |
41 | // Find the difference between the children of two operators. Returns true if there is a difference. Note: This only makes sense for operators with the same type, if this function
42 | // is called with operators of different types, then it returns true with removedChildren and addedChildren as nil.
43 | // removedChildren and addedChildren could be nil and in that case the children are not returned.
44 | + (BOOL) diffOperator:(MTOperator*) first with:(MTOperator*) second removedChildren:(NSArray**) removedChildren addedChildren:(NSArray**) addedChildren;
45 |
46 | // Returns true if expr is a subset of oper, i.e. if expr is a child of oper or all children of expr are children of
47 | // oper. Returns true only for proper subsets. If difference is not nil, the set difference is returned in difference.
48 | + (BOOL) isExpression:(MTExpression*) expr subsetOf:(MTOperator*) oper difference:(NSArray**) difference;
49 |
50 | // Get the identity for the given operator.
51 | + (MTNumber*) getIdentity:(char) operatorType;
52 |
53 | // Get an expression combining the expressions with the given operator. If there are no exprs, then this returns the identity for the operator.
54 | // If there is only one expr, then that is returned for the expression.
55 | + (MTExpression*) combineExpressions:(NSArray*) exprs withOperatorType:(char) operatorType;
56 |
57 | // Return a set of all the variables in the expression
58 | + (NSSet*) getVariablesInExpression:(MTExpression*) expr;
59 |
60 | // Returns true if expression expr contains the variable var.
61 | + (BOOL) expression:(MTExpression*)expr containsVariable:(MTVariable*) var;
62 |
63 | // Get the leading term for an expression in normal form.
64 | + (MTExpression*) getLeadingTerm:(MTExpression*) expr;
65 |
66 | // Check whether the expression is a given operator
67 | + (BOOL) isDivision:(MTExpression*) expr;
68 | + (BOOL) isMultiplication:(MTExpression*) expr;
69 | + (BOOL) isAddition:(MTExpression*) expr;
70 |
71 | @end
72 |
--------------------------------------------------------------------------------
/MathSolver/expressions/MTExpressionUtil.m:
--------------------------------------------------------------------------------
1 | //
2 | // ExpressionUtil.m
3 | //
4 | // Created by Kostub Deshmukh on 7/29/13.
5 | // Copyright (c) 2013 Math FX.
6 | //
7 | // This software may be modified and distributed under the terms of the
8 | // MIT license. See the LICENSE file for details.
9 | //
10 |
11 | #import "MTExpressionUtil.h"
12 | #import "MTCalculateRule.h"
13 | #import "MTIdentityRule.h"
14 | #import "MTReduceRule.h"
15 | #import "MTCanonicalizer.h"
16 |
17 | static MTCalculateRule *_calc;
18 | static MTIdentityRule *_identity;
19 |
20 | static MTRule* getCalculateRule() {
21 | if (_calc == nil) {
22 | _calc = [MTCalculateRule rule];
23 | }
24 | return _calc;
25 | }
26 |
27 | static MTRule* getIdentityRule() {
28 | if (_identity == nil) {
29 | _identity = [MTIdentityRule rule];
30 | }
31 | return _identity;
32 | }
33 |
34 | @implementation MTExpressionUtil
35 |
36 | + (MTOperator*) negate:(MTExpression *)expr
37 | {
38 | return [MTOperator operatorWithType:kMTMultiplication args:[MTNumber numberWithValue:[MTRational one].negation range:expr.range] :expr range:expr.range];
39 | }
40 |
41 | + (BOOL) expression: (MTExpression*) expr getCoefficent:(MTRational**) c variables:(NSArray**) vars
42 | {
43 | switch (expr.expressionType) {
44 | case kMTExpressionTypeNumber: {
45 | MTNumber *numberArg = (MTNumber *) expr;
46 | if (c) {
47 | *c = numberArg.value;
48 | }
49 | if (vars) {
50 | *vars = [NSArray array];
51 | }
52 | return true;
53 | }
54 | case kMTExpressionTypeVariable: {
55 | if (c) {
56 | *c = [MTRational one];
57 | }
58 | if (vars) {
59 | *vars = [NSArray arrayWithObject:expr];
60 | }
61 | return true;
62 | }
63 | case kMTExpressionTypeOperator: {
64 | MTOperator* oper = (MTOperator*) expr;
65 | if (oper.type != kMTMultiplication) {
66 | // none of the other operators are of this form
67 | return NO;
68 | }
69 | // we require that there be exactly one FXNumber and no operators as children
70 | BOOL numberFound = NO;
71 | MTNumber *coefficient = nil;
72 | NSMutableArray* mutableVars = [NSMutableArray array];
73 | for (MTExpression* childArg in oper.children) {
74 | if ([childArg isKindOfClass:[MTNumber class]] && !numberFound) {
75 | numberFound = YES;
76 | coefficient = (MTNumber *)childArg;
77 | } else if ([childArg isKindOfClass:[MTVariable class]]) {
78 | [mutableVars addObject:childArg];
79 | } else {
80 | // arg is notof the form we are looking for.
81 | return NO;
82 | }
83 | }
84 | // at this point the operator is combinable, we need to find out what variables to combine by and the coefficient.
85 | // if we found a coefficent then get its value, otherwise the default is 1.
86 | if (c) {
87 | *c = (coefficient) ? coefficient.value : [MTRational one];
88 | }
89 | if (vars) {
90 | // sort the variables by name
91 | [mutableVars sortUsingDescriptors:[NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:@"name" ascending:YES]]];
92 | *vars = mutableVars;
93 | }
94 | return YES;
95 | }
96 |
97 | case kMTExpressionTypeNull: {
98 | return NO;
99 | }
100 | }
101 | }
102 |
103 | + (MTExpression *)absoluteValue:(MTExpression *)expr
104 | {
105 | MTRational* coeff;
106 | NSArray* vars;
107 | if (![self expression:expr getCoefficent:&coeff variables:&vars]) {
108 | // Not of required form, can't take the absolute value.
109 | return expr;
110 | }
111 | if (coeff.isPositive) {
112 | return expr;
113 | } else {
114 | // Normalize this to remove the -ve sign. (Calculation and removal of identity are the only things that should happen)
115 | id canon = [MTCanonicalizerFactory getExpressionCanonicalizer];
116 | MTExpression* normalized = [canon normalize:[self negate:expr]];
117 | return [canon normalForm:normalized];
118 | }
119 | }
120 |
121 | + (NSString*) formatAsString:(MTExpression*) expr
122 | {
123 | MTRational* coeff;
124 | NSArray* vars;
125 | if (![self expression:expr getCoefficent:&coeff variables:&vars]) {
126 | // Not of required form, can't format
127 | return nil;
128 | }
129 | return [NSString stringWithFormat:@"%@%@", coeff, [vars componentsJoinedByString:@""]];
130 | }
131 |
132 | + (BOOL) isEquivalentUptoCalculation:(MTExpression*) expr toExpression:(MTExpression*) other
133 | {
134 | if ([expr isEqualUptoRearrangement:other]) {
135 | return true;
136 | }
137 | MTRule* calculate = getCalculateRule();
138 | MTExpression* calculatedTerm = [calculate applyToTopLevelNode:expr withChildren:expr.children];
139 | if ([calculatedTerm isEqualUptoRearrangement:other]) {
140 | return true;
141 | }
142 |
143 | // Reduce fractions in the expression if any
144 | MTRule* reduceRule = [MTReduceRule rule];
145 | MTExpression* fractionsReduced = [reduceRule apply:calculatedTerm];
146 | if (fractionsReduced != calculatedTerm && [fractionsReduced isEqualUptoRearrangement:other]) {
147 | return true;
148 | }
149 |
150 | // apply the identity rule to strip away any 1s.
151 | MTRule* identity = getIdentityRule();
152 | MTExpression* identityRemovedTerm = [identity applyToTopLevelNode:fractionsReduced withChildren:fractionsReduced.children];
153 | if (identityRemovedTerm != fractionsReduced && [identityRemovedTerm isEqualUptoRearrangement:other]) {
154 | return true;
155 | }
156 |
157 | return false;
158 | }
159 |
160 | + (MTExpression*) getExpressionEquivalentTo:(MTExpression*) expr in:(NSArray*) array
161 | {
162 | for (MTExpression* arg in array) {
163 | if ([self isEquivalentUptoCalculation:expr toExpression:arg] || [self isEquivalentUptoCalculation:arg toExpression:expr]) {
164 | return arg;
165 | }
166 | }
167 | return nil;
168 | }
169 |
170 | +(BOOL) isEquivalentUptoCalculationAndRearrangement:(NSArray *)array1 :(NSArray *)array2
171 | {
172 | NSMutableArray* otherArray = [NSMutableArray arrayWithArray:array2];
173 | for (MTExpression* expr in array1) {
174 | MTExpression* other = [self getExpressionEquivalentTo:expr in:otherArray];
175 | if (other) {
176 | [otherArray removeObject:other];
177 | } else {
178 | return false;
179 | }
180 | }
181 | return (otherArray.count == 0);
182 | }
183 |
184 | + (MTOperator*) toOperator:(MTExpression *)var
185 | {
186 | return [MTOperator operatorWithType:kMTMultiplication args:var :[MTNumber numberWithValue:[MTRational one]]];
187 | }
188 |
189 | + (BOOL) diffOperator:(MTOperator*) first with:(MTOperator*) second removedChildren:(NSArray**) removedChildren addedChildren:(NSArray**) addedChildren
190 | {
191 | if (first.type != second.type) {
192 | if (removedChildren) {
193 | *removedChildren = nil;
194 | }
195 | if (addedChildren) {
196 | *addedChildren = nil;
197 | }
198 | return true;
199 | }
200 | // This is an n^2 algorithm for checking every child of first against every child of second
201 | // (Since there isn't a well ordering amongst expressions, (it might be useful to establish one)
202 | // Note this does not recurse, i.e. it checks the children using isEquals
203 | // So we don't catch 5(a+b) = (b+a)*5.
204 | NSMutableArray* added = [NSMutableArray arrayWithCapacity:second.children.count];
205 |
206 | NSMutableArray* removed = [NSMutableArray arrayWithArray:first.children];
207 | for (MTExpression* child in second.children) {
208 | NSUInteger index = [removed indexOfObject:child];
209 | if (index == NSNotFound) {
210 | [added addObject:child];
211 | } else {
212 | // remove the object so that we don't double count it
213 | [removed removeObjectAtIndex:index];
214 | }
215 | }
216 |
217 | if (removedChildren) {
218 | *removedChildren = removed;
219 | }
220 | if (addedChildren) {
221 | *addedChildren = added;
222 | }
223 | return (removed.count > 0) || (added.count > 0);
224 | }
225 |
226 |
227 | + (MTNumber*) getIdentity:(char) operatorType
228 | {
229 | if (operatorType == kMTMultiplication) {
230 | return [MTNumber numberWithValue:[MTRational one]];
231 | } else if (operatorType == kMTAddition) {
232 | return [MTNumber numberWithValue:[MTRational zero]];
233 | }
234 | return nil;
235 | }
236 |
237 | + (MTMathListRange*) unionedRange:(NSArray*) exprs
238 | {
239 | NSMutableArray* ranges = [NSMutableArray arrayWithCapacity:exprs.count];
240 | for (MTExpression* expr in exprs) {
241 | if (expr.range) {
242 | [ranges addObject:expr.range];
243 | } else {
244 | return nil;
245 | }
246 | }
247 | return [MTMathListRange unionRanges:ranges];
248 | }
249 |
250 | + (MTExpression*) combineExpressions:(NSArray*) exprs withOperatorType:(char) operatorType
251 | {
252 | if (exprs.count == 0) {
253 | return [MTExpressionUtil getIdentity:operatorType ];
254 | } else if (exprs.count == 1) {
255 | return exprs[0];
256 | } else {
257 | return [MTOperator operatorWithType:operatorType args:exprs range:[self unionedRange:exprs]];
258 | }
259 | }
260 |
261 | + (BOOL)expression:(MTExpression *)expr containsVariable:(MTVariable *)var
262 | {
263 | switch (expr.expressionType) {
264 | case kMTExpressionTypeNumber:
265 | return false;
266 |
267 | case kMTExpressionTypeVariable:
268 | return [var isEqual:expr];
269 |
270 | case kMTExpressionTypeOperator: {
271 | for (MTExpression* child in expr.children) {
272 | if ([self expression:child containsVariable:var]) {
273 | return true;
274 | }
275 | }
276 | return false;
277 | }
278 |
279 | case kMTExpressionTypeNull:
280 | return false;
281 | }
282 | }
283 |
284 | + (BOOL) isExpression:(MTExpression*) expr subsetOf:(MTOperator*) oper difference:(NSArray**) difference
285 | {
286 | // There are two cases of a subset, one when the expression expr is wholly a part of oper. Two when both are the same
287 | // operators and all children of expr are children of oper.
288 | if ([oper.children containsObject:expr]) {
289 | // clearly a subset
290 | // All other terms should resolve to identity.
291 | if (difference) {
292 | NSMutableArray* childrenWithoutExpr = [NSMutableArray arrayWithArray:oper.children];
293 | [childrenWithoutExpr removeObject:expr];
294 | *difference = childrenWithoutExpr;
295 | }
296 | return true;
297 | } else if (expr.expressionType == kMTExpressionTypeOperator && [expr equalsExpressionValue:oper.type]) {
298 | NSArray *added;
299 | if([MTExpressionUtil diffOperator:oper with:(MTOperator*) expr removedChildren:difference addedChildren:&added]) {
300 | NSParameterAssert(added);
301 | if (added.count > 0) {
302 | // for a subset all children of expr should be children of oper
303 | return false;
304 | }
305 | return true;
306 | }
307 | }
308 | return false;
309 | }
310 |
311 |
312 | + (MTExpression*) getLeadingTerm:(MTExpression*) expr
313 | {
314 | if (expr.expressionType != kMTExpressionTypeOperator) {
315 | return expr;
316 | }
317 |
318 | // operator
319 | if ([expr equalsExpressionValue:kMTMultiplication]) {
320 | // This is the leading term, eg. 5x
321 | return expr;
322 | } else if ([expr equalsExpressionValue:kMTAddition]) {
323 | // first child
324 | return expr.children[0];
325 | } else {
326 | NSAssert(false, @"Unknown type of operator encountered: %@", expr);
327 | return nil;
328 | }
329 | }
330 |
331 | + (NSSet *)getVariablesInExpression:(MTExpression *)expr
332 | {
333 | switch (expr.expressionType) {
334 | case kMTExpressionTypeNumber:
335 | // empty set
336 | return [NSSet set];
337 |
338 | case kMTExpressionTypeVariable:
339 | return [NSSet setWithObject:expr];
340 |
341 | case kMTExpressionTypeOperator: {
342 | NSMutableSet* set = [NSMutableSet set];
343 | for (MTExpression* child in expr.children) {
344 | [set unionSet:[self getVariablesInExpression:child]];
345 | }
346 | return set;
347 | }
348 |
349 | case kMTExpressionTypeNull:
350 | // empty set
351 | return [NSSet set];
352 |
353 | }
354 | }
355 |
356 | + (BOOL) isDivision:(MTExpression*) expr
357 | {
358 | return expr.expressionType == kMTExpressionTypeOperator && [expr equalsExpressionValue:kMTDivision];
359 | }
360 |
361 | + (BOOL) isMultiplication:(MTExpression*) expr
362 | {
363 | return expr.expressionType == kMTExpressionTypeOperator && [expr equalsExpressionValue:kMTMultiplication];
364 | }
365 |
366 | + (BOOL) isAddition:(MTExpression *)expr
367 | {
368 | return expr.expressionType == kMTExpressionTypeOperator && [expr equalsExpressionValue:kMTAddition];
369 | }
370 |
371 | @end
372 |
--------------------------------------------------------------------------------
/MathSolver/expressions/MTInfixParser.h:
--------------------------------------------------------------------------------
1 | //
2 | // InfixParser.h
3 | //
4 | // Created by Kostub Deshmukh on 7/14/13.
5 | // Copyright (c) 2013 Math FX.
6 | //
7 | // This software may be modified and distributed under the terms of the
8 | // MIT license. See the LICENSE file for details.
9 | //
10 |
11 | #import
12 | #import "MTExpression.h"
13 |
14 | @class MTMathList;
15 |
16 |
17 | FOUNDATION_EXPORT NSString *const MTParseErrorDomain;
18 | FOUNDATION_EXPORT NSString *const MTParseErrorOffset;
19 |
20 | // A Simple parser that parses an infix string into an abstract syntax tree using the shunting yard algorithm
21 | // http://en.wikipedia.org/wiki/Shunting-yard_algorithm
22 | @interface MTInfixParser : NSObject
23 |
24 | // Create a parser with the string to parse
25 | - (id) init;
26 |
27 | // Tokenizes and parses the string or mathlist as an expression. Returns nil on error
28 | - (MTExpression*) parseFromString:(NSString*) string;
29 | // If expectsEquation is false, then parsing an equation returns an error, if it is true then there is an error
30 | // if an equation isn't found.
31 | - (id) parseFromMathList:(MTMathList*) mathList expectedEntityType:(MTMathEntityType) entityType;
32 |
33 | - (MTExpression*) parseToExpressionFromMathList:(MTMathList*) mathList;
34 | - (MTEquation*) parseToEquationFromMathList:(MTMathList*) mathList;
35 |
36 | // Returns true if the parsing has an error.
37 | - (BOOL) hasError;
38 |
39 | // Get the error associated with the parsing
40 | - (NSError *) error;
41 |
42 | enum MTParserErrors : NSUInteger {
43 | MTParserMismatchParens = 1,
44 | MTParserNotEnoughArguments,
45 | MTParserMissingOperator,
46 | MTParserInvalidCharacter,
47 | MTParserDivisionByZero,
48 | MTParserPlaceholderPresent,
49 | MTParserMultipleRelations,
50 | MTParserEquationExpected,
51 | MTParserMissingExpression,
52 | MTParserUnsupportedOperation,
53 | MTParserInvalidNumber,
54 | };
55 |
56 | @end
57 |
--------------------------------------------------------------------------------
/MathSolver/expressions/MTRational.h:
--------------------------------------------------------------------------------
1 | //
2 | // Rational.h
3 | //
4 | // Created by Kostub Deshmukh on 9/6/13.
5 | // Copyright (c) 2013 Math FX.
6 | //
7 | // This software may be modified and distributed under the terms of the
8 | // MIT license. See the LICENSE file for details.
9 | //
10 |
11 | // Represents a rational number
12 | #import
13 |
14 | typedef enum
15 | {
16 | kMTRationalFormatNone = 0, // Default value
17 | kMTRationalFormatWhole, // A whole number
18 | kMTRationalFormatDecimal, // A number with a decimal point
19 | kMTRationalFormatImproper,
20 | kMTRationalFormatMixed,
21 | } MTRationalFormat;
22 |
23 | @interface MTRational : NSObject
24 |
25 | @property (nonatomic, readonly) NSInteger numerator;
26 | @property (nonatomic, readonly) NSInteger denominator;
27 | // The format in which this rational was entered.
28 | @property (nonatomic, readonly) MTRationalFormat format;
29 |
30 | + (instancetype) rationalWithNumerator:(NSInteger) numerator denominator:(NSInteger) denominator;
31 |
32 | - (MTRational*) negation;
33 | - (MTRational*) reciprocal;
34 | - (MTRational*) absoluteValue;
35 |
36 | - (MTRational*) add:(MTRational*) r;
37 | - (MTRational*) subtract:(MTRational*) r;
38 | - (MTRational*) multiply:(MTRational*) r;
39 | - (MTRational*) divideBy:(MTRational*) r;
40 |
41 | // Reduced to it's base form
42 | - (MTRational*) reduced;
43 |
44 | // Return the rational as a floating point number.
45 | - (float) floatValue;
46 | - (BOOL) isInteger;
47 | - (long) floor;
48 |
49 | - (NSString *)description;
50 | - (BOOL)isEqual:(id)object;
51 | - (BOOL)isEqualToRational:(MTRational*) r;
52 |
53 | // The number is equivalent, i.e. the same number, but it could be a different representation.
54 | // e.g. 1/2 and 2/4 are equivalent fractions.
55 | - (BOOL)isEquivalent:(MTRational*) r;
56 |
57 | - (NSComparisonResult) compare:(MTRational *)aNumber;
58 | - (BOOL) isPositive;
59 | - (BOOL) isNegative;
60 | - (BOOL) isZero;
61 | - (BOOL) isReduced;
62 | - (BOOL) isGreaterThan:(MTRational*) r;
63 | - (BOOL) isLessThan:(MTRational*) r;
64 | - (NSUInteger)hash;
65 |
66 | + (MTRational*) zero;
67 | + (MTRational*) one;
68 | + (MTRational*) rationalWithNumber:(NSInteger) number;
69 | // Parses a string of the form a.b where a and b are integers to a rational. Does not handle -ve signs.
70 | // If the string is not in the given format, this fails.
71 | + (MTRational*) rationalFromDecimalRepresentation:(NSString*) str;
72 |
73 | @end
74 |
--------------------------------------------------------------------------------
/MathSolver/expressions/MTRational.m:
--------------------------------------------------------------------------------
1 | //
2 | // Rational.m
3 | //
4 | // Created by Kostub Deshmukh on 9/6/13.
5 | // Copyright (c) 2013 Math FX.
6 | //
7 | // This software may be modified and distributed under the terms of the
8 | // MIT license. See the LICENSE file for details.
9 | //
10 |
11 | #import "MTRational.h"
12 |
13 | static NSUInteger gcd(NSUInteger a, NSUInteger b) {
14 | while (b != 0) {
15 | NSUInteger prev = b;
16 | b = a % b;
17 | a = prev;
18 | }
19 | return a;
20 | }
21 |
22 | // Represents a rational number
23 | @implementation MTRational {
24 | NSUInteger _gcd;
25 | }
26 |
27 | + (instancetype) rationalWithNumerator:(NSInteger) numerator denominator:(NSInteger) denominator format:(MTRationalFormat) format;
28 | {
29 | if (denominator == 0) {
30 | // can't have a rational number divided by 0
31 | return nil;
32 | }
33 | if (format == kMTRationalFormatWhole && denominator != 1) {
34 | // Whole number should always have a denominator of 1.
35 | return nil;
36 | }
37 | return [[[self class] alloc] initWithNumerator:numerator denominator:denominator format:format];
38 | }
39 |
40 | + (instancetype)rationalWithNumerator:(NSInteger)numerator denominator:(NSInteger)denominator
41 | {
42 | return [self rationalWithNumerator:numerator denominator:denominator format:kMTRationalFormatImproper];
43 | }
44 |
45 | + (MTRational *)zero
46 | {
47 | static MTRational* zero = nil;
48 | if (!zero) {
49 | zero = [MTRational rationalWithNumber:0];
50 | }
51 | return zero;
52 | }
53 |
54 | + (MTRational *)one
55 | {
56 | static MTRational* one = nil;
57 | if (!one) {
58 | one = [MTRational rationalWithNumber:1];
59 | }
60 | return one;
61 | }
62 |
63 | + (MTRational *)rationalWithNumber:(NSInteger)number
64 | {
65 | return [[[self class] alloc] initWithNumerator:number denominator:1u format:kMTRationalFormatWhole];
66 | }
67 |
68 | + (MTRational*)rationalFromDecimalRepresentation:(NSString *)str
69 | {
70 | NSParameterAssert(str);
71 | if (str.length == 0) {
72 | return nil;
73 | }
74 | NSScanner* scanner = [NSScanner scannerWithString:str];
75 | scanner.charactersToBeSkipped = nil;
76 | // check for -ve sign
77 | if ([scanner scanString:@"-" intoString:NULL]) {
78 | // -ve signs are not supported
79 | return nil;
80 | }
81 |
82 | NSInteger whole;
83 | if (![scanner scanInteger:&whole]) {
84 | whole = 0;
85 | }
86 | if ([scanner isAtEnd]) {
87 | return [self rationalWithNumber:whole];
88 | }
89 | // The only possible character that can come here is a '.'
90 | if (![scanner scanString:@"." intoString:NULL]) {
91 | // We encountered some other character other than a .
92 | return nil;
93 | }
94 | if ([scanner isAtEnd]) {
95 | // The . at the end of the number is useless, we represent this as x.0
96 | return [self rationalWithNumerator:whole*10 denominator:10 format:kMTRationalFormatDecimal];
97 | }
98 | long numDigitsLeft = str.length - scanner.scanLocation;
99 | assert(numDigitsLeft > 0); // otherwise the scanner should have been at the end
100 | NSInteger fractional;
101 | if (![scanner scanInteger:&fractional]) {
102 | fractional = 0;
103 | }
104 | if (![scanner isAtEnd]) {
105 | // more non digit characters
106 | return nil;
107 | }
108 | if (fractional < 0) {
109 | // Can't have a -ve fractional
110 | return nil;
111 | }
112 | NSUInteger denominator = 1;
113 | for (int i = 0; i < numDigitsLeft; i++) {
114 | denominator *= 10;
115 | }
116 | MTRational* r = [self rationalWithNumerator:(whole*denominator + fractional) denominator:denominator format:kMTRationalFormatDecimal];
117 | return r;
118 | }
119 |
120 | - (instancetype) initWithNumerator:(NSInteger)numerator denominator:(NSUInteger)denominator format:(MTRationalFormat) format
121 | {
122 | self = [super init];
123 | if (self) {
124 | _numerator = numerator;
125 | _denominator = denominator;
126 | _format = format;
127 | _gcd = gcd(ABS(self.numerator), ABS(self.denominator));
128 | }
129 | return self;
130 | }
131 |
132 | - (MTRational *)negation
133 | {
134 | // negations retain the format
135 | MTRational* neg = [MTRational rationalWithNumerator:-_numerator denominator:_denominator format:_format];
136 | return neg;
137 | }
138 |
139 | - (MTRational *)add:(MTRational *)r
140 | {
141 | if (self.denominator == r.denominator) {
142 | // Special case for common denominators to make the fractions look more normal
143 | return [MTRational rationalWithNumerator:(r.numerator + self.numerator) denominator:r.denominator];
144 | }
145 | // worry about oveflow?, should we always reduce?
146 | NSUInteger d = self.denominator * r.denominator;
147 | NSInteger n = self.numerator * r.denominator + r.numerator * self.denominator;
148 | return [MTRational rationalWithNumerator:n denominator:d];
149 | }
150 |
151 | - (MTRational *)multiply:(MTRational *)r
152 | {
153 | NSUInteger d = self.denominator * r.denominator;
154 | NSInteger n = self.numerator * r.numerator;
155 | return [MTRational rationalWithNumerator:n denominator:d];
156 | }
157 |
158 | - (MTRational *)subtract:(MTRational *)r
159 | {
160 | return [self add:r.negation];
161 | }
162 |
163 | - (MTRational *)reciprocal
164 | {
165 | return [MTRational rationalWithNumerator:self.denominator denominator:self.numerator];
166 | }
167 |
168 | - (MTRational *)divideBy:(MTRational *)r
169 | {
170 | return [self multiply:r.reciprocal];
171 | }
172 |
173 | - (BOOL) isReduced
174 | {
175 | return (_gcd == 1 && _denominator > 0);
176 | }
177 |
178 | - (MTRational *)reduced
179 | {
180 | if (self.isReduced) {
181 | return self;
182 | } else if (_gcd == 0) {
183 | return [MTRational zero];
184 | }
185 | // In C dividing an signed int by an unsigned will cause both to become unsigned!!, so cast to signed first.
186 | NSInteger numerator = self.numerator/(NSInteger) _gcd;
187 | NSInteger denominator = self.denominator / (NSInteger) _gcd;
188 | if (denominator < 0) {
189 | denominator = -denominator;
190 | numerator = -numerator;
191 | }
192 | return [MTRational rationalWithNumerator:numerator denominator:denominator];
193 | }
194 |
195 | - (float)floatValue
196 | {
197 | return (float) _numerator / (float) _denominator;
198 | }
199 |
200 | - (BOOL)isInteger
201 | {
202 | if (self.isReduced) {
203 | return (self.denominator == 1);
204 | } else {
205 | return self.reduced.isInteger;
206 | }
207 | }
208 |
209 | - (long)floor
210 | {
211 | return self.numerator / self.denominator;
212 | }
213 |
214 | - (BOOL)isEqualToRational:(MTRational *)r
215 | {
216 | return (self.denominator == r.denominator && self.numerator == r.numerator);
217 | }
218 |
219 | - (BOOL) isEqual:(id) anObject
220 | {
221 | if (self == anObject) {
222 | return YES;
223 | }
224 | if (!anObject || ![anObject isKindOfClass:[self class]]) {
225 | return NO;
226 | }
227 | return [self isEqualToRational:anObject];
228 | }
229 |
230 | - (NSUInteger) hash
231 | {
232 | const int prime = 31;
233 | return prime * self.denominator + self.numerator;
234 | }
235 |
236 | - (NSString *)description
237 | {
238 | if (_format == kMTRationalFormatWhole || _denominator == 1) {
239 | return [NSString stringWithFormat:@"%ld", (long)self.numerator];
240 | } else if (_format == kMTRationalFormatDecimal) {
241 | // write it in decimal format.
242 | NSUInteger absNumerator = ABS(self.numerator);
243 | NSUInteger integerVal = absNumerator/self.denominator;
244 | NSUInteger decimalVal = absNumerator - self.denominator*integerVal;
245 | NSString* sign = (self.numerator < 0) ? @"-" : @"";
246 | return [NSString stringWithFormat:@"%@%lu.%lu", sign, (unsigned long)integerVal, (unsigned long)decimalVal];
247 | } else {
248 | return [NSString stringWithFormat:@"%ld/%ld", (long)self.numerator, (long)self.denominator];
249 | }
250 | }
251 |
252 | - (BOOL)isEquivalent:(MTRational *)r
253 | {
254 | if ([self.reduced isEqualToRational:r.reduced]) {
255 | return YES;
256 | } else if (lroundf(self.floatValue * 100) == lroundf(r.floatValue*100)) {
257 | // Decimal expansions are close, then these are equivalent
258 | return YES;
259 | }
260 | return NO;
261 | }
262 |
263 | - (NSComparisonResult) compare:(MTRational *)aNumber
264 | {
265 | MTRational* r = [self subtract:aNumber];
266 | if (r.numerator > 0) {
267 | return NSOrderedDescending;
268 | } else if (r.numerator < 0) {
269 | return NSOrderedAscending;
270 | } else {
271 | assert(r.numerator == 0);
272 | return NSOrderedSame;
273 | }
274 | }
275 |
276 | - (BOOL) isNegative
277 | {
278 | return (self.numerator < 0);
279 | }
280 |
281 | - (BOOL) isPositive
282 | {
283 | return self.numerator > 0;
284 | }
285 |
286 | - (BOOL) isZero
287 | {
288 | return self.numerator == 0;
289 | }
290 |
291 | - (MTRational *)absoluteValue
292 | {
293 | return (self.isNegative) ? self.negation : self;
294 | }
295 |
296 | - (BOOL)isGreaterThan:(MTRational *)r
297 | {
298 | return ([self compare:r] == NSOrderedDescending);
299 | }
300 |
301 | - (BOOL) isLessThan:(MTRational *)r
302 | {
303 | return ([self compare:r] == NSOrderedAscending);
304 | }
305 |
306 | @end
307 |
--------------------------------------------------------------------------------
/MathSolver/expressions/internal/MTSymbol.h:
--------------------------------------------------------------------------------
1 | //
2 | // Symbol.h
3 | //
4 | // Created by Kostub Deshmukh on 7/14/13.
5 | // Copyright (c) 2013 Math FX.
6 | //
7 | // This software may be modified and distributed under the terms of the
8 | // MIT license. See the LICENSE file for details.
9 | //
10 |
11 | #import
12 |
13 | @interface MTSymbol : NSObject
14 |
15 | enum MTSymbolType {
16 | kMTSymbolTypeVariable = 1,
17 | kMTSymbolTypeNumber,
18 | kMTSymbolTypeOperator,
19 | kMTSymbolTypeOpenParen,
20 | kMTSymbolTypeClosedParen,
21 | kMTSymbolTypeRelation
22 | };
23 |
24 |
25 | // Create an symbol with type and value
26 | + (id) symbolWithType:(enum MTSymbolType) type value:(NSNumber*) value offset:(NSRange) offset;
27 |
28 | - (unichar) charValue;
29 | - (unsigned int) intValue;
30 |
31 | @property (nonatomic, readonly) enum MTSymbolType type;
32 | @property (nonatomic, readonly) NSNumber *value;
33 | @property (nonatomic, readonly) NSRange offset;
34 |
35 | @end
36 |
37 |
--------------------------------------------------------------------------------
/MathSolver/expressions/internal/MTSymbol.m:
--------------------------------------------------------------------------------
1 | //
2 | // Symbol.m
3 | //
4 | // Created by Kostub Deshmukh on 7/14/13.
5 | // Copyright (c) 2013 Math FX.
6 | //
7 | // This software may be modified and distributed under the terms of the
8 | // MIT license. See the LICENSE file for details.
9 | //
10 |
11 | #import "MTSymbol.h"
12 |
13 |
14 | @interface MTSymbol ()
15 |
16 | @property (nonatomic) enum MTSymbolType type;
17 | @property (nonatomic) NSNumber* value;
18 | @property (nonatomic) NSRange offset;
19 |
20 | @end
21 |
22 | @implementation MTSymbol
23 |
24 | + (id) symbolWithType:(enum MTSymbolType)type value:(NSNumber *)value offset:(NSRange)offset
25 | {
26 | MTSymbol *sym = [[MTSymbol alloc] init];
27 | sym.type = type;
28 | sym.value = value;
29 | sym.offset = offset;
30 | return sym;
31 | }
32 |
33 | - (unichar) charValue
34 | {
35 | return [self.value unsignedShortValue];
36 | }
37 |
38 | - (unsigned int) intValue
39 | {
40 | return [self.value unsignedIntValue];
41 | }
42 |
43 | @end
44 |
--------------------------------------------------------------------------------
/MathSolver/expressions/internal/MTTokenizer.h:
--------------------------------------------------------------------------------
1 | //
2 | // Tokenizer.h
3 | //
4 | // Created by Kostub Deshmukh on 7/14/13.
5 | // Copyright (c) 2013 Math FX.
6 | //
7 | // This software may be modified and distributed under the terms of the
8 | // MIT license. See the LICENSE file for details.
9 | //
10 |
11 | #import
12 |
13 | @class MTSymbol;
14 | @interface MTTokenizer : NSObject
15 |
16 | - (id) initWithString:(NSString*) string;
17 |
18 | // Returns nil when no more tokens left
19 | - (MTSymbol*) getNextToken;
20 |
21 | @end
22 |
--------------------------------------------------------------------------------
/MathSolver/expressions/internal/MTTokenizer.m:
--------------------------------------------------------------------------------
1 | //
2 | // Tokenizer.m
3 | //
4 | // Created by Kostub Deshmukh on 7/14/13.
5 | // Copyright (c) 2013 Math FX.
6 | //
7 | // This software may be modified and distributed under the terms of the
8 | // MIT license. See the LICENSE file for details.
9 | //
10 |
11 | #import "MTTokenizer.h"
12 | #import "MTSymbol.h"
13 | #import "MTExpression.h"
14 |
15 | @implementation MTTokenizer {
16 | NSString* _string;
17 | int _current;
18 | }
19 |
20 | - (id) initWithString:(NSString *)string
21 | {
22 | self = [super init];
23 | if (self) {
24 | _string = string;
25 | _current = 0;
26 | }
27 | return self;
28 | }
29 |
30 | - (MTSymbol*) getNextToken
31 | {
32 | NSUInteger length = [_string length];
33 | // safety check
34 | if (_current >= length) {
35 | return nil;
36 | }
37 |
38 | // skip spaces
39 | unichar ch;
40 | do {
41 | ch = [_string characterAtIndex:_current];
42 | ++_current;
43 | } while(ch == ' ' && _current < length);
44 |
45 | int offset = _current - 1;
46 |
47 | switch (ch) {
48 | case ' ':
49 | // we are still at a space and _current has gone past the end, so no more characters are left.
50 | return nil;
51 | case 0x00D7:
52 | return [MTSymbol symbolWithType:kMTSymbolTypeOperator value:[NSNumber numberWithUnsignedShort:kMTMultiplication] offset:NSMakeRange(offset, 1)];
53 | case '+':
54 | case '-':
55 | case '*':
56 | case '/':
57 | return [MTSymbol symbolWithType:kMTSymbolTypeOperator value:[NSNumber numberWithUnsignedShort:ch] offset:NSMakeRange(offset, 1)];
58 | case '(':
59 | return [MTSymbol symbolWithType:kMTSymbolTypeOpenParen value:nil offset:NSMakeRange(offset, 1)];
60 | case ')':
61 | return [MTSymbol symbolWithType:kMTSymbolTypeClosedParen value:nil offset:NSMakeRange(offset, 1)];
62 |
63 | default:
64 | break;
65 | }
66 | if (ch >= '0' && ch <= '9') {
67 | unsigned int value = 0;
68 | _current--; // set current to the current character since it will be incremented the first time in the loop.
69 | do {
70 | value *= 10;
71 | value += (ch - '0');
72 | _current++;
73 | if (_current < length) {
74 | ch = [_string characterAtIndex:_current];
75 | } else {
76 | ch = 0;
77 | }
78 | } while(ch >= '0' && ch <= '9');
79 | return [MTSymbol symbolWithType:kMTSymbolTypeNumber value:[NSNumber numberWithUnsignedInt:value] offset:NSMakeRange(offset, 1)]; // note this is not really 1
80 | } else if(ch >= 'a' && ch <= 'z') {
81 | return [MTSymbol symbolWithType:kMTSymbolTypeVariable value:[NSNumber numberWithUnsignedShort:ch] offset:NSMakeRange(offset, 1)];
82 | }
83 | // throw exception?
84 | [NSException raise:@"ParseError" format:@"Unknown type of character: %c", ch];
85 | return nil;
86 | }
87 |
88 | @end
89 |
--------------------------------------------------------------------------------
/MathSolverTests/ExpressionTest.m:
--------------------------------------------------------------------------------
1 | //
2 | // ExpressionTest.m
3 | //
4 | // Created by Kostub Deshmukh on 7/21/13.
5 | // Copyright (c) 2013 Math FX.
6 | //
7 | // This software may be modified and distributed under the terms of the
8 | // MIT license. See the LICENSE file for details.
9 | //
10 | #import
11 |
12 | #import "MTInfixParser.h"
13 | #import "MTExpression.h"
14 | #import "MTFlattenRule.h"
15 | #import "MTMathList.h"
16 | #import "MTMathListBuilder.h"
17 |
18 | @interface ExpressionTest : XCTestCase
19 |
20 | @end
21 |
22 | @implementation ExpressionTest {
23 | MTFlattenRule* _flatten;
24 | }
25 |
26 | - (void) setUp
27 | {
28 | [super setUp];
29 | _flatten = [MTFlattenRule rule];
30 | }
31 |
32 | - (MTExpression*) parseExpression:(NSString*) expr {
33 | MTInfixParser *parser = [MTInfixParser new];
34 | MTMathList* ml = [MTMathListBuilder buildFromString:expr];
35 | return [_flatten apply:[parser parseToExpressionFromMathList:ml]];
36 | }
37 |
38 | static NSDictionary* getTestData() {
39 | return @{
40 | @"5" : @0,
41 | @"x" : @1,
42 | @"4*2" : @0,
43 | @"5x" : @1,
44 | @"5xy": @2,
45 | @"5xx" : @2,
46 | @"1 + 3" : @0,
47 | @"1 + x" : @1,
48 | @"1 + x + y" : @1,
49 | @"1 + x + xy" : @2,
50 | @"(1 + x)x" : @2,
51 | @"2(1 + x)" : @1,
52 | @"2x(1 + x) + 3xy(1+xy(1+z))" : @5,
53 | };
54 |
55 | }
56 |
57 | - (void)testDegree
58 | {
59 | NSDictionary* dict = getTestData();
60 | for (NSString* testExpr in dict) {
61 | NSNumber* expected = [dict valueForKey:testExpr];
62 | MTExpression* expr = [self parseExpression:testExpr];
63 | NSNumber *degree = @(expr.degree);
64 | XCTAssertEqualObjects(degree, expected, @"For expression %@", testExpr);
65 | }
66 | }
67 |
68 | static NSArray* getTestDataForRearrangement() {
69 | return @[
70 | @[@"x + y", @"y + x", @YES],
71 | @[@"5x", @"x*5", @YES],
72 | @[@"x", @"x", @YES],
73 | @[@"x+y+z", @"z+x+y", @YES],
74 | @[@"5", @"5", @YES],
75 | @[@"x(a+b)", @"(a+b)x", @YES],
76 | @[@"x(a+b)", @"(b+a)x", @NO], // doesn't recurse
77 | @[@"x + x", @"x", @NO], // each x is accounted for separately
78 | @[@"5x + x", @"x", @NO],
79 | @[@"x + y", @"x + y + z", @NO], // extra term
80 | ];
81 | }
82 |
83 | - (void) testEqualsUptoRearragement
84 | {
85 | NSArray* testData = getTestDataForRearrangement();
86 | for (NSArray* testCase in testData) {
87 | MTExpression* expr1 = [self parseExpression:testCase[0]];
88 | MTExpression* expr2 = [self parseExpression:testCase[1]];
89 | BOOL equals = [expr1 isEqualUptoRearrangement:expr2];
90 | NSString* desc = [NSString stringWithFormat:@"Error for expr1:%@ expr2:%@", testCase[0], testCase[1]];
91 | XCTAssertEqualObjects([NSNumber numberWithBool:equals], testCase[2], @"%@", desc);
92 | BOOL equals2 = [expr2 isEqualUptoRearrangement:expr1];
93 | XCTAssertEqualObjects([NSNumber numberWithBool:equals2], testCase[2], @"%@", desc);
94 | }
95 | }
96 |
97 | static NSArray* getTestDataForRearrangementRecursive() {
98 | return @[
99 | @[@"x + y", @"y + x", @YES],
100 | @[@"5x", @"x*5", @YES],
101 | @[@"x", @"x", @YES],
102 | @[@"x+y+z", @"z+x+y", @YES],
103 | @[@"5", @"5", @YES],
104 | @[@"x(a+b)", @"(a+b)x", @YES],
105 | @[@"x(a+b)", @"(b+a)x", @YES],
106 | @[@"(x+x)(a+b)", @"(b+a)x", @NO],
107 | @[@"x + x", @"x", @NO], // each x is accounted for separately
108 | @[@"5x + x", @"x", @NO],
109 | @[@"x + y", @"x + y + z", @NO], // extra term
110 | ];
111 | }
112 |
113 | - (void) testEqualsUptoRearragementRecursive
114 | {
115 | NSArray* testData = getTestDataForRearrangementRecursive();
116 | for (NSArray* testCase in testData) {
117 | MTExpression* expr1 = [self parseExpression:testCase[0]];
118 | MTExpression* expr2 = [self parseExpression:testCase[1]];
119 | BOOL equals = [expr1 isEqualUptoRearrangementRecursive:expr2];
120 | NSString* desc = [NSString stringWithFormat:@"Error for expr1:%@ expr2:%@", testCase[0], testCase[1]];
121 | XCTAssertEqualObjects([NSNumber numberWithBool:equals], testCase[2], @"%@", desc);
122 | BOOL equals2 = [expr2 isEqualUptoRearrangementRecursive:expr1];
123 | XCTAssertEqualObjects([NSNumber numberWithBool:equals2], testCase[2], @"%@", desc);
124 | }
125 | }
126 |
127 | static NSArray* getTestDataForEquivalence() {
128 | return @[
129 | @[@"5", @"y", @NO],
130 | @[@"5x", @"x", @NO ],
131 | @[@"\\frac{1}{3}", @"0.33", @YES],
132 | @[@"\\frac{1}{3}x", @"0.33x", @YES],
133 | @[@"5x", @"5y", @NO],
134 | @[@"5x", @"x*5", @NO],
135 | @[@"x(0.101+b)", @"x(0.102+b)", @YES],
136 | @[@"x + y", @"x + y + z", @NO], // extra term
137 | ];
138 | }
139 |
140 | - (void) testEquivalence
141 | {
142 | NSArray* testData = getTestDataForEquivalence();
143 | for (NSArray* testCase in testData) {
144 | MTExpression* expr1 = [self parseExpression:testCase[0]];
145 | MTExpression* expr2 = [self parseExpression:testCase[1]];
146 | BOOL equiv = [expr1 isEquivalent:expr2];
147 | NSString* desc = [NSString stringWithFormat:@"Error for expr1:%@ expr2:%@", testCase[0], testCase[1]];
148 | XCTAssertEqualObjects([NSNumber numberWithBool:equiv], testCase[2], @"%@", desc);
149 | BOOL equiv2 = [expr2 isEquivalent:expr1];
150 | XCTAssertEqualObjects([NSNumber numberWithBool:equiv2], testCase[2], @"%@", desc);
151 | }
152 | }
153 | @end
154 |
--------------------------------------------------------------------------------
/MathSolverTests/ExpressionUtilTest.h:
--------------------------------------------------------------------------------
1 | //
2 | // ExpressionUtilTest.h
3 | //
4 | // Created by Kostub Deshmukh on 7/29/13.
5 | // Copyright (c) 2013 Math FX.
6 | //
7 | // This software may be modified and distributed under the terms of the
8 | // MIT license. See the LICENSE file for details.
9 | //
10 |
11 | #import
12 |
13 | @interface ExpressionUtilTest : XCTestCase
14 |
15 | @end
16 |
--------------------------------------------------------------------------------
/MathSolverTests/ExpressionUtilTest.m:
--------------------------------------------------------------------------------
1 | //
2 | // ExpressionUtilTest.m
3 | //
4 | // Created by Kostub Deshmukh on 7/29/13.
5 | // Copyright (c) 2013 Math FX.
6 | //
7 | // This software may be modified and distributed under the terms of the
8 | // MIT license. See the LICENSE file for details.
9 | //
10 |
11 | #import "ExpressionUtilTest.h"
12 | #import "MTExpressionUtil.h"
13 | #import "MTInfixParser.h"
14 | #import "MTExpression.h"
15 | #import "MTFlattenRule.h"
16 |
17 | @implementation ExpressionUtilTest {
18 | MTFlattenRule* _flatten;
19 | }
20 |
21 | - (void) setUp
22 | {
23 | [super setUp];
24 | _flatten = [MTFlattenRule rule];
25 | }
26 |
27 | - (MTExpression*) parseExpression:(NSString*) expr {
28 | MTInfixParser *parser = [MTInfixParser new];
29 | return [_flatten apply:[parser parseFromString:expr]];
30 | }
31 |
32 | - (NSArray*) parseExpressionArray:(NSArray*) exprs {
33 | // build an array of variables
34 | NSMutableArray* array = [NSMutableArray arrayWithCapacity:exprs.count];
35 | for (NSString* expr in exprs) {
36 | [array addObject:[self parseExpression:expr]];
37 | }
38 | return array;
39 | }
40 |
41 | static NSArray* getTestData() {
42 | return @[
43 | @[@"x + y", @NO],
44 | @[@"5", @YES, @5, @[]],
45 | @[@"x", @YES, @1, @[@"x"]],
46 | @[@"5x", @YES, @5, @[@"x"]],
47 | @[@"5(x+1)", @NO],
48 | @[@"5x*3", @NO],
49 | @[@"xy", @YES, @1, @[@"x", @"y"]],
50 | @[@"5xx", @YES, @5, @[@"x", @"x"]],
51 | @[@"5yx", @YES, @5, @[@"x", @"y"]], // variables are sorted
52 | ];
53 | }
54 |
55 | static NSArray* getTestDataForDiff() {
56 | return @[
57 | @[@"2*3", @"2+3", @YES, [NSNull null], [NSNull null]],
58 | @[@"2*3", @"3*2", @NO, @[], @[]],
59 | @[@"2+3", @"3+2", @NO, @[], @[]],
60 | @[@"2+3+4", @"2+3", @YES, @[], @[@"4"]],
61 | @[@"2+3", @"2+3+4", @YES, @[@"4"], @[]],
62 | @[@"2+3*4", @"2+4*3", @YES, @[@"4*3"], @[@"3*4"]],
63 | @[@"2+2", @"2+4", @YES, @[@"4"], @[@"2"]],
64 | @[@"-2*-2*-2", @"4*-2", @YES, @[@"4"], @[@"-2", @"-2"]],
65 | ];
66 | }
67 |
68 | - (void) testGetCoeffientAndVariables
69 | {
70 | NSArray* testData = getTestData();
71 | for (NSArray* testCase in testData) {
72 | MTExpression* expr = [self parseExpression:testCase[0]];
73 | MTRational* coeff;
74 | NSArray* vars;
75 | BOOL val = [MTExpressionUtil expression:expr getCoefficent:&coeff variables:&vars];
76 | NSString* desc = [NSString stringWithFormat:@"Error for expr:%@", testCase[0]];
77 | XCTAssertEqualObjects([NSNumber numberWithBool:val], testCase[1], @"%@", desc);
78 | if (val) {
79 | XCTAssertEqual(coeff.denominator, 1u, @"%@", desc);
80 | XCTAssertEqualObjects(@(coeff.numerator), testCase[2], @"%@", desc);
81 | // build an array of variables
82 | NSArray* expectedVars = [self parseExpressionArray:testCase[3]];
83 | XCTAssertEqualObjects(vars, expectedVars, @"%@", desc);
84 | }
85 | }
86 | }
87 |
88 | - (void) testDiffOperator
89 | {
90 | NSArray* testData = getTestDataForDiff();
91 | for (NSArray* testCase in testData) {
92 | MTExpression* first = [self parseExpression:testCase[0]];
93 | MTExpression* second = [self parseExpression:testCase[1]];
94 | NSArray* added, *removed;
95 | BOOL diff = [MTExpressionUtil diffOperator:(MTOperator*)first with:(MTOperator*)second removedChildren:&removed addedChildren:&added];
96 | NSString* desc = [NSString stringWithFormat:@"Error for diff:%@ and %@", testCase[0], testCase[1]];
97 | XCTAssertEqualObjects([NSNumber numberWithBool:diff], testCase[2], @"%@", desc);
98 | if (testCase[3] == [NSNull null]) {
99 | XCTAssertNil(added, @"%@", desc);
100 | XCTAssertNil(removed, @"%@", desc);
101 | } else {
102 | NSArray* expectedAdded = [self parseExpressionArray:testCase[3]];
103 | NSArray* expectedRemoved = [self parseExpressionArray:testCase[4]];
104 | XCTAssertEqualObjects(added, expectedAdded, @"%@", desc);
105 | XCTAssertEqualObjects(removed, expectedRemoved, @"%@", desc);
106 | }
107 | }
108 | }
109 |
110 | @end
111 |
--------------------------------------------------------------------------------
/MathSolverTests/InfixParserTest.h:
--------------------------------------------------------------------------------
1 | //
2 | // InfixParserTest.h
3 | //
4 | // Created by Kostub Deshmukh on 7/15/13.
5 | // Copyright (c) 2013 Math FX.
6 | //
7 | // This software may be modified and distributed under the terms of the
8 | // MIT license. See the LICENSE file for details.
9 | //
10 |
11 | #import
12 |
13 | @interface InfixParserTest : XCTestCase
14 |
15 | @end
16 |
--------------------------------------------------------------------------------
/MathSolverTests/InfixParserTest.m:
--------------------------------------------------------------------------------
1 | //
2 | // InfixParserTest.m
3 | //
4 | // Created by Kostub Deshmukh on 7/15/13.
5 | // Copyright (c) 2013 Math FX.
6 | //
7 | // This software may be modified and distributed under the terms of the
8 | // MIT license. See the LICENSE file for details.
9 | //
10 |
11 | #import "InfixParserTest.h"
12 | #import "MTInfixParser.h"
13 | #import "MTExpression.h"
14 | #import "MTMathListBuilder.h"
15 |
16 | @implementation InfixParserTest
17 |
18 | - (void)setUp
19 | {
20 | [super setUp];
21 |
22 | // Set-up code here.
23 | }
24 |
25 | - (void)tearDown
26 | {
27 | // Tear-down code here.
28 |
29 | [super tearDown];
30 | }
31 |
32 | static NSDictionary* getTestData() {
33 | return @{
34 | @"5" : @"5",
35 | @"x" : @"x",
36 | @"x+5" : @"(x + 5)",
37 | @"x+5+3" : @"((x + 5) + 3)",
38 | @"x+5*3" : @"(x + (5 * 3))",
39 | @"5x" : @"(5 * x)",
40 | @"5x*3" : @"((5 * x) * 3)",
41 | @"(x+5)" : @"(x + 5)",
42 | @"(x+5)*3" : @"((x + 5) * 3)",
43 | @"x*((1/3)+5)" : @"(x * ((1 / 3) + 5))",
44 | @"x(3+x)" : @"(x * (3 + x))",
45 | @"5(3+x)" : @"(5 * (3 + x))",
46 | @"(x+1)(3+x)" : @"((x + 1) * (3 + x))",
47 | @"(x+1)3" : @"((x + 1) * 3)",
48 | @"(x+1)x" : @"((x + 1) * x)",
49 | @"3xx" : @"((3 * x) * x)",
50 | @"5xy" : @"((5 * x) * y)",
51 | @"x-3" : @"(x - 3)",
52 | @"x-3+x" : @"((x - 3) + x)",
53 | @"x-3x" : @"(x - (3 * x))",
54 | @"-3" : @"-3",
55 | @"-x" : @"(_ x)",
56 | @"-3 + x" : @"(-3 + x)",
57 | @"-3x" : @"(-3 * x)",
58 | @"x + (-3)" : @"(x + -3)",
59 | @"x * -3" : @"(x * -3)",
60 | @"-(x+3)" : @"(_ (x + 3))",
61 | @"(5x - 3)/(2y + 1) - (-3x - 3)(1-(-2z))" : @"((((5 * x) - 3) / ((2 * y) + 1)) - (((-3 * x) - 3) * (1 - (-2 * z))))",
62 | @"3x/0" : @"((3 * x) / 0)",
63 | };
64 |
65 | }
66 |
67 | static NSDictionary* getTestData2() {
68 | return @{
69 | @"\\frac{1}{2}": @"1/2",
70 | @"x+\\frac{1}{2}": @"(x + 1/2)",
71 | @"x+\\frac{1}{2}+3": @"((x + 1/2) + 3)",
72 | @"x+\\frac{1}{2}*3": @"(x + (1/2 * 3))",
73 | @"\\frac{1}{2}x": @"(1/2 * x)",
74 | @"\\frac{1}{2}x + \\frac{2}{3}": @"((1/2 * x) + 2/3)",
75 | @"-\\frac{1}{2}": @"-1/2",
76 | @"\\frac{-1}{2}": @"-1/2",
77 | @"\\frac{1}{-2}": @"1/-2",
78 | @"(x)\\frac{1}{2}": @"(x * 1/2)",
79 | @"\\frac{1}{2}(3)": @"(1/2 * 3)",
80 | @"0.2": @"0.2",
81 | @"x+0.2": @"(x + 0.2)",
82 | @"x+0.2+3": @"((x + 0.2) + 3)",
83 | @"x+0.2*3": @"(x + (0.2 * 3))",
84 | @"0.2x": @"(0.2 * x)",
85 | @"0.2x + 0.5": @"((0.2 * x) + 0.5)",
86 | @"-0.2": @"-0.2",
87 | @"(x)0.2": @"(x * 0.2)",
88 | @"0.5(3)" : @"(0.5 * 3)",
89 | @"\\frac{5x}{2}" : @"((5 * x) / 2)",
90 | @"\\frac{5x}{2}-\\frac{2}{3}" : @"(((5 * x) / 2) - 2/3)",
91 | @"\\frac{2}{3} - \\frac{5x}{2}" : @"(2/3 - ((5 * x) / 2))",
92 | @"5." : @"5.0",
93 | @"2 \\frac{1}{2}" : @"5/2",
94 | @"\\frac{3x}{0}" : @"((3 * x) / 0)",
95 | @"3x \\div 0" : @"((3 * x) / 0)",
96 | };
97 | }
98 |
99 | - (void) testParseExpressionFromString
100 | {
101 | NSDictionary* dict = getTestData();
102 | for (NSString* testExpr in dict) {
103 | NSString* expected = [dict valueForKey:testExpr];
104 | NSString* desc = [NSString stringWithFormat:@"Error for %@", testExpr];
105 | MTInfixParser *parser = [MTInfixParser new];
106 | MTExpression* expr = [parser parseFromString:testExpr];
107 | XCTAssertNotNil(expr, @"%@", desc);
108 | XCTAssertFalse(parser.hasError, @"Expr: %@ Error:%@ ", testExpr, parser.error.localizedDescription);
109 | XCTAssertEqualObjects(expected, expr.stringValue, @"%@", desc);
110 | }
111 | }
112 |
113 | - (void) testParseExpressionFromMathList
114 | {
115 | NSDictionary* dict = getTestData();
116 | for (NSString* testExpr in dict) {
117 | NSString* expected = [dict valueForKey:testExpr];
118 | NSString* desc = [NSString stringWithFormat:@"Error for %@", testExpr];
119 | MTInfixParser *parser = [MTInfixParser new];
120 | MTMathList* ml = [MTMathListBuilder buildFromString:testExpr];
121 | MTExpression* expr = [parser parseToExpressionFromMathList:ml];
122 | XCTAssertNotNil(expr, @"%@", desc);
123 | XCTAssertFalse(parser.hasError, @"Expr: %@ Error:%@ ", testExpr, parser.error.localizedDescription);
124 | XCTAssertEqualObjects(expected, expr.stringValue, @"%@", desc);
125 | }
126 | }
127 |
128 | - (void) testParseAdditionalExpressionsFromMathList
129 | {
130 | NSDictionary* dict = getTestData2();
131 | for (NSString* testExpr in dict) {
132 | NSString* expected = [dict valueForKey:testExpr];
133 | NSString* desc = [NSString stringWithFormat:@"Error for %@", testExpr];
134 | MTInfixParser *parser = [MTInfixParser new];
135 | MTMathList* ml = [MTMathListBuilder buildFromString:testExpr];
136 | MTExpression* expr = [parser parseToExpressionFromMathList:ml];
137 | XCTAssertNotNil(expr, @"%@", desc);
138 | XCTAssertFalse(parser.hasError, @"Expr: %@ Error:%@ ", testExpr, parser.error.localizedDescription);
139 | XCTAssertEqualObjects(expected, expr.stringValue, @"%@", desc);
140 | }
141 | }
142 |
143 | // test equations
144 | static NSDictionary* getEquationTests() {
145 | return @{
146 | @"x=0": @"x = 0",
147 | @"x=3": @"x = 3",
148 | @"5x+2 = \\frac{1}{3}": @"((5 * x) + 2) = 1/3",
149 | @"2(3y+z) - 0.3x = 2x + \\frac32": @"((2 * ((3 * y) + z)) - (0.3 * x)) = ((2 * x) + 3/2)"
150 | };
151 | }
152 |
153 | - (void) testParseEquations
154 | {
155 | NSDictionary* dict = getEquationTests();
156 | for (NSString* testExpr in dict) {
157 | NSString* expected = [dict valueForKey:testExpr];
158 | NSString* desc = [NSString stringWithFormat:@"Error for %@", testExpr];
159 | MTInfixParser *parser = [MTInfixParser new];
160 | MTMathList* ml = [MTMathListBuilder buildFromString:testExpr];
161 | MTEquation* eq = [parser parseToEquationFromMathList:ml];
162 | XCTAssertNotNil(eq, @"%@", desc);
163 | XCTAssertFalse(parser.hasError, @"Expr: %@ Error:%@ ", testExpr, parser.error.localizedDescription);
164 | XCTAssertEqualObjects(expected, eq.stringValue, @"%@", desc);
165 | }
166 | }
167 |
168 | // test failed equations
169 | static NSArray* getFailedExpressionTests() {
170 | return @[
171 | @[@"+5", @(MTParserNotEnoughArguments), @0],
172 | @[@"(5x", @(MTParserMismatchParens), @0],
173 | @[@"5x)", @(MTParserMismatchParens), @2],
174 | @[@"x 5", @(MTParserMissingOperator), @1],
175 | @[@"x = 5", @(MTParserMultipleRelations), @1],
176 | @[@"\\frac{3}{0}", @(MTParserDivisionByZero), @0],
177 | @[@"\\frac{\\square}{3}", @(MTParserPlaceholderPresent), @0],
178 | @[@"5. \\frac{1}{2}", @(MTParserMissingOperator), @2],
179 | @[@"3 \\frac{\\frac{1}{2}}{3}", @(MTParserMissingOperator), @1],
180 | @[@"3 \\frac{3}{\\frac{1}{2}}", @(MTParserMissingOperator), @1],
181 | @[@"\\frac{3}{1} \\frac{1}{2}", @(MTParserMissingOperator), @1],
182 | @[@"3 \\frac{1}{2} \\frac{1}{2}", @(MTParserMissingOperator), @2],
183 | @[@"3..5", @(MTParserInvalidNumber), @0],
184 | @[@"3.5.2", @(MTParserInvalidNumber), @0],
185 | ];
186 | }
187 |
188 | - (void) testFailedExpressions
189 | {
190 | MTInfixParser *parser = [MTInfixParser new];
191 | NSArray* array = getFailedExpressionTests();
192 | for (NSArray* testCase in array) {
193 | NSString* testExpr = testCase[0];
194 | NSString* desc = [NSString stringWithFormat:@"Error for %@", testExpr];
195 | MTMathList* ml = [MTMathListBuilder buildFromString:testExpr];
196 | MTExpression* expr = [parser parseToExpressionFromMathList:ml];
197 | XCTAssertNil(expr, @"%@", desc);
198 | XCTAssertTrue(parser.hasError, @"%@", desc);
199 | NSError* error = parser.error;
200 | XCTAssertEqual(error.domain, MTParseError, @"%@", desc);
201 | XCTAssertEqualObjects(@(error.code), testCase[1], @"%@", desc);
202 | MTMathListIndex* index = [error.userInfo objectForKey:MTParseErrorOffset];
203 | if (!index) {
204 | XCTAssertEqualObjects([NSNull null], testCase[2], @"%@", desc);
205 | } else {
206 | XCTAssertEqualObjects(@(index.atomIndex), testCase[2], @"%@", desc);
207 | }
208 | }
209 | }
210 |
211 | // test failed equations
212 | static NSArray* getFailedEquationTests() {
213 | return @[
214 | @[ @"5x", @(MTParserEquationExpected), [NSNull null]],
215 | @[ @"5x = 3y = 2z", @(MTParserMultipleRelations), @5],
216 | @[ @"\\frac{2=3}{42}", @(MTParserMultipleRelations), @0],
217 | @[ @"= x + 2", @(MTParserMissingExpression), @0],
218 | @[ @"x + 2 =", @(MTParserMissingExpression), @3],
219 | @[ @"x + = 3", @(MTParserNotEnoughArguments), @1],
220 | @[ @"x = +3", @(MTParserNotEnoughArguments), @2],
221 | @[ @"x + (3 = 5) * 2", @(MTParserMismatchParens), @2],
222 | @[ @"x 2 = 3", @(MTParserMissingOperator), @1],
223 | @[ @"3 = x 2", @(MTParserMissingOperator), @3],
224 | ];
225 | }
226 | - (void) testFailedEquations
227 | {
228 | MTInfixParser *parser = [MTInfixParser new];
229 | NSArray* array = getFailedEquationTests();
230 | for (NSArray* testCase in array) {
231 | NSString* testExpr = testCase[0];
232 | NSString* desc = [NSString stringWithFormat:@"Error for %@", testExpr];
233 | MTMathList* ml = [MTMathListBuilder buildFromString:testExpr];
234 | MTEquation* eq = [parser parseToEquationFromMathList:ml];
235 | XCTAssertNil(eq, @"%@", desc);
236 | XCTAssertTrue(parser.hasError, @"%@", desc);
237 | NSError* error = parser.error;
238 | XCTAssertEqual(error.domain, MTParseError, @"%@", desc);
239 | XCTAssertEqualObjects(@(error.code), testCase[1], @"%@", desc);
240 | MTMathListIndex* index = [error.userInfo objectForKey:MTParseErrorOffset];
241 | if (!index) {
242 | XCTAssertEqualObjects([NSNull null], testCase[2], @"%@", desc);
243 | } else {
244 | XCTAssertEqualObjects(@(index.atomIndex), testCase[2], @"%@", desc);
245 | }
246 | }
247 | }
248 |
249 | @end
250 |
--------------------------------------------------------------------------------
/MathSolverTests/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | BNDL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1
23 |
24 |
25 |
--------------------------------------------------------------------------------
/MathSolverTests/MathSolverTests.m:
--------------------------------------------------------------------------------
1 | //
2 | // MathSolverTests.m
3 | //
4 | // Created by Kostub Deshmukh on 5/26/16.
5 | // Copyright (c) 2016 Kostub Deshmukh.
6 | //
7 | // This software may be modified and distributed under the terms of the
8 | // MIT license. See the LICENSE file for details.
9 | //
10 |
11 | #import
12 |
13 | @interface MathSolverTests : XCTestCase
14 |
15 | @end
16 |
17 | @implementation MathSolverTests
18 |
19 | - (void)setUp {
20 | [super setUp];
21 | // Put setup code here. This method is called before the invocation of each test method in the class.
22 | }
23 |
24 | - (void)tearDown {
25 | // Put teardown code here. This method is called after the invocation of each test method in the class.
26 | [super tearDown];
27 | }
28 |
29 | - (void)testExample {
30 | // This is an example of a functional test case.
31 | // Use XCTAssert and related functions to verify your tests produce the correct results.
32 | }
33 |
34 | - (void)testPerformanceExample {
35 | // This is an example of a performance test case.
36 | [self measureBlock:^{
37 | // Put the code you want to measure the time of here.
38 | }];
39 | }
40 |
41 | @end
42 |
--------------------------------------------------------------------------------
/MathSolverTests/RationalTest.m:
--------------------------------------------------------------------------------
1 | //
2 | // RationalTest.m
3 | //
4 | // Created by Kostub Deshmukh on 9/6/13.
5 | // Copyright (c) 2013 Math FX.
6 | //
7 | // This software may be modified and distributed under the terms of the
8 | // MIT license. See the LICENSE file for details.
9 | //
10 |
11 | #import
12 |
13 | #import "MTRational.h"
14 |
15 | @interface RationalTest : XCTestCase
16 |
17 | @end
18 |
19 | @implementation RationalTest
20 |
21 | - (void) testCreate
22 | {
23 | MTRational* r = [MTRational rationalWithNumerator:5 denominator:3];
24 | XCTAssertEqual(r.numerator, 5, @"");
25 | XCTAssertEqual(r.denominator, 3u, @"");
26 |
27 | r = [MTRational rationalWithNumerator:-5 denominator:3];
28 | XCTAssertEqual(r.numerator, -5, @"");
29 | XCTAssertEqual(r.denominator, 3u, @"");
30 |
31 | r = [MTRational rationalWithNumerator:5 denominator:-3];
32 | XCTAssertEqual(r.numerator, 5, @"");
33 | XCTAssertEqual(r.denominator, -3, @"");
34 |
35 | r = [MTRational rationalWithNumerator:0 denominator:3];
36 | XCTAssertEqual(r.numerator, 0, @"");
37 | XCTAssertEqual(r.denominator, 3u, @"");
38 |
39 | r = [MTRational rationalWithNumerator:5 denominator:0];
40 | XCTAssertNil(r, @"");
41 | }
42 |
43 | - (void) testNegation
44 | {
45 | MTRational* r = [MTRational rationalWithNumerator:5 denominator:3];
46 | MTRational* rneg = r.negation;
47 | XCTAssertEqual(rneg.numerator, -5, @"");
48 | XCTAssertEqual(rneg.denominator, 3u, @"");
49 |
50 | MTRational* rnegneg = rneg.negation;
51 | XCTAssertEqualObjects(r, rnegneg, @"");
52 | }
53 |
54 | - (void) testReciprocal
55 | {
56 | {
57 | MTRational* r = [MTRational rationalWithNumerator:5 denominator:3];
58 | MTRational* rrec = r.reciprocal;
59 | XCTAssertEqual(rrec.numerator, 3, @"");
60 | XCTAssertEqual(rrec.denominator, 5u, @"");
61 |
62 | MTRational* rrecrec = rrec.reciprocal;
63 | XCTAssertEqualObjects(r, rrecrec, @"");
64 | }
65 |
66 | {
67 | // Test that it preserves the -ve
68 | MTRational* r = [MTRational rationalWithNumerator:-5 denominator:3];
69 | MTRational* rrec = r.reciprocal;
70 | XCTAssertEqual(rrec.numerator, 3, @"");
71 | XCTAssertEqual(rrec.denominator, -5, @"");
72 |
73 | MTRational* rrecrec = rrec.reciprocal;
74 | XCTAssertEqualObjects(r, rrecrec, @"");
75 | }
76 | }
77 |
78 | - (void) testAdd
79 | {
80 | {
81 | MTRational* p = [MTRational rationalWithNumerator:5 denominator:3];
82 | MTRational* q = [MTRational rationalWithNumerator:5 denominator:3];
83 | MTRational* added = [p add:q];
84 | XCTAssertEqual(added.numerator, 10, @"");
85 | XCTAssertEqual(added.denominator, 3u, @"");
86 | MTRational* commute = [q add:p];
87 | XCTAssertEqualObjects(added, commute, @"");
88 | }
89 |
90 | {
91 | // different denominators
92 | MTRational* p = [MTRational rationalWithNumerator:3 denominator:2];
93 | MTRational* q = [MTRational rationalWithNumerator:5 denominator:3];
94 | MTRational* added = [p add:q];
95 | XCTAssertEqual(added.numerator, 19, @"");
96 | XCTAssertEqual(added.denominator, 6u, @"");
97 | MTRational* commute = [q add:p];
98 | XCTAssertEqualObjects(added, commute, @"");
99 | }
100 |
101 | {
102 | // negative rationals
103 | MTRational* p = [MTRational rationalWithNumerator:-1 denominator:2];
104 | MTRational* q = [MTRational rationalWithNumerator:2 denominator:3];
105 | MTRational* added = [p add:q];
106 | XCTAssertEqual(added.numerator, 1, @"");
107 | XCTAssertEqual(added.denominator, 6u, @"");
108 | MTRational* commute = [q add:p];
109 | XCTAssertEqualObjects(added, commute, @"");
110 | }
111 | }
112 |
113 | - (void) testSubtract
114 | {
115 | {
116 | MTRational* p = [MTRational rationalWithNumerator:5 denominator:3];
117 | MTRational* q = [MTRational rationalWithNumerator:5 denominator:3];
118 | MTRational* added = [p subtract:q];
119 | XCTAssertEqual(added.numerator, 0, @"");
120 | XCTAssertEqual(added.denominator, 3u, @"");
121 | }
122 |
123 | {
124 | // different denominators
125 | MTRational* p = [MTRational rationalWithNumerator:3 denominator:2];
126 | MTRational* q = [MTRational rationalWithNumerator:5 denominator:3];
127 | MTRational* added = [p subtract:q];
128 | XCTAssertEqual(added.numerator, -1, @"");
129 | XCTAssertEqual(added.denominator, 6u, @"");
130 | }
131 |
132 | {
133 | // negative rationals
134 | MTRational* p = [MTRational rationalWithNumerator:1 denominator:2];
135 | MTRational* q = [MTRational rationalWithNumerator:-2 denominator:3];
136 | MTRational* added = [p subtract:q];
137 | XCTAssertEqual(added.numerator, 7, @"");
138 | XCTAssertEqual(added.denominator, 6u, @"");
139 | }
140 | }
141 |
142 | - (void) testMultiply
143 | {
144 | {
145 | MTRational* p = [MTRational rationalWithNumerator:2 denominator:3];
146 | MTRational* q = [MTRational rationalWithNumerator:5 denominator:2];
147 | MTRational* mult = [p multiply:q];
148 | XCTAssertEqual(mult.numerator, 10, @"");
149 | XCTAssertEqual(mult.denominator, 6u, @"");
150 | MTRational* commute = [q multiply:p];
151 | XCTAssertEqualObjects(mult, commute, @"");
152 | }
153 |
154 | {
155 | // negative rationals
156 | MTRational* p = [MTRational rationalWithNumerator:-1 denominator:2];
157 | MTRational* q = [MTRational rationalWithNumerator:2 denominator:3];
158 | MTRational* mult = [p multiply:q];
159 | XCTAssertEqual(mult.numerator, -2, @"");
160 | XCTAssertEqual(mult.denominator, 6u, @"");
161 | MTRational* commute = [q multiply:p];
162 | XCTAssertEqualObjects(mult, commute, @"");
163 | }
164 | }
165 |
166 | - (void) testDivideBy
167 | {
168 | {
169 | MTRational* p = [MTRational rationalWithNumerator:2 denominator:3];
170 | MTRational* q = [MTRational rationalWithNumerator:5 denominator:2];
171 | MTRational* mult = [p divideBy:q];
172 | XCTAssertEqual(mult.numerator, 4, @"");
173 | XCTAssertEqual(mult.denominator, 15u, @"");
174 | }
175 |
176 | {
177 | // negative rationals
178 | MTRational* p = [MTRational rationalWithNumerator:-1 denominator:2];
179 | MTRational* q = [MTRational rationalWithNumerator:2 denominator:3];
180 | MTRational* mult = [p divideBy:q];
181 | XCTAssertEqual(mult.numerator, -3, @"");
182 | XCTAssertEqual(mult.denominator, 4u, @"");
183 | }
184 | }
185 |
186 | - (void) testReduced
187 | {
188 | {
189 | // already reduced
190 | MTRational *p = [MTRational rationalWithNumerator:2 denominator:3];
191 | MTRational *reduced = p.reduced;
192 | XCTAssertEqualObjects(p, reduced, @"");
193 | }
194 | {
195 | // negative
196 | MTRational *p = [MTRational rationalWithNumerator:-2 denominator:3];
197 | MTRational *reduced = p.reduced;
198 | XCTAssertEqualObjects(p, reduced, @"");
199 | }
200 | {
201 | // negative denominator
202 | MTRational *p = [MTRational rationalWithNumerator:2 denominator:-3];
203 | MTRational *reduced = p.reduced;
204 | XCTAssertEqual(reduced.numerator, -2, @"");
205 | XCTAssertEqual(reduced.denominator, 3, @"");
206 | }
207 | {
208 | // zero
209 | MTRational *p = [MTRational rationalWithNumerator:0 denominator:3];
210 | MTRational *reduced = p.reduced;
211 | XCTAssertEqual(reduced.numerator, 0, @"");
212 | XCTAssertEqual(reduced.denominator, 1u, @"");
213 | }
214 |
215 | {
216 | // unreduced
217 | MTRational *p = [MTRational rationalWithNumerator:9 denominator:3];
218 | MTRational *reduced = p.reduced;
219 | XCTAssertEqual(reduced.numerator, 3, @"");
220 | XCTAssertEqual(reduced.denominator, 1u, @"");
221 | }
222 |
223 | {
224 | // unreduced -ve
225 | MTRational *p = [MTRational rationalWithNumerator:-10 denominator:20];
226 | MTRational *reduced = p.reduced;
227 | XCTAssertEqual(reduced.numerator, -1, @"");
228 | XCTAssertEqual(reduced.denominator, 2u, @"");
229 | }
230 |
231 | {
232 | // unreduced -ve denominator
233 | MTRational *p = [MTRational rationalWithNumerator:10 denominator:-20];
234 | MTRational *reduced = p.reduced;
235 | XCTAssertEqual(reduced.numerator, -1, @"");
236 | XCTAssertEqual(reduced.denominator, 2u, @"");
237 | }
238 | }
239 |
240 | - (void) testEquivalence
241 | {
242 | {
243 | // already reduced
244 | MTRational *p = [MTRational rationalWithNumerator:2 denominator:3];
245 | MTRational *reduced = p.reduced;
246 | XCTAssertTrue([p isEquivalent:reduced], @"");
247 | XCTAssertTrue([reduced isEquivalent:p], @"");
248 | }
249 | {
250 | // negative
251 | MTRational *p = [MTRational rationalWithNumerator:-2 denominator:3];
252 | MTRational *reduced = p.reduced;
253 | XCTAssertTrue([p isEquivalent:reduced], @"");
254 | XCTAssertTrue([reduced isEquivalent:p], @"");
255 | }
256 | {
257 | // negative denominator
258 | MTRational *p = [MTRational rationalWithNumerator:2 denominator:-3];
259 | MTRational *reduced = p.reduced;
260 | XCTAssertTrue([p isEquivalent:reduced], @"");
261 | XCTAssertTrue([reduced isEquivalent:p], @"");
262 | }
263 | {
264 | // zero
265 | MTRational *p = [MTRational rationalWithNumerator:0 denominator:3];
266 | MTRational *reduced = p.reduced;
267 | XCTAssertTrue([p isEquivalent:reduced], @"");
268 | XCTAssertTrue([reduced isEquivalent:p], @"");
269 | }
270 |
271 | {
272 | // unreduced
273 | MTRational *p = [MTRational rationalWithNumerator:9 denominator:3];
274 | MTRational *reduced = p.reduced;
275 | XCTAssertTrue([p isEquivalent:reduced], @"");
276 | XCTAssertTrue([reduced isEquivalent:p], @"");
277 | }
278 |
279 | {
280 | // unreduced -ve
281 | MTRational *p = [MTRational rationalWithNumerator:-10 denominator:20];
282 | MTRational *reduced = p.reduced;
283 | XCTAssertTrue([p isEquivalent:reduced], @"");
284 | XCTAssertTrue([reduced isEquivalent:p], @"");
285 | }
286 |
287 | {
288 | // unreduced
289 | MTRational *p = [MTRational rationalWithNumerator:6 denominator:8];
290 | MTRational *q = [MTRational rationalWithNumerator:15 denominator:20];
291 | XCTAssertTrue([p isEquivalent:q], @"");
292 | XCTAssertTrue([q isEquivalent:p], @"");
293 | }
294 |
295 | {
296 | // decimals
297 | MTRational *p = [MTRational rationalWithNumerator:1 denominator:3];
298 | MTRational *q = [MTRational rationalFromDecimalRepresentation:@"0.33"];
299 | XCTAssertTrue([p isEquivalent:q], @"");
300 | XCTAssertTrue([q isEquivalent:p], @"");
301 | }
302 | {
303 | // decimals
304 | MTRational *p = [MTRational rationalWithNumerator:2 denominator:3];
305 | MTRational *q = [MTRational rationalFromDecimalRepresentation:@"0.67"];
306 | XCTAssertTrue([p isEquivalent:q], @"");
307 | XCTAssertTrue([q isEquivalent:p], @"");
308 | }
309 | {
310 | // decimals
311 | MTRational *p = [MTRational rationalFromDecimalRepresentation:@"0.103"]; // 0.103
312 | MTRational *q = [MTRational rationalFromDecimalRepresentation:@"0.104"]; // 0.104
313 | XCTAssertTrue([p isEquivalent:q], @"");
314 | XCTAssertTrue([q isEquivalent:p], @"");
315 | }
316 | {
317 | // These round differently
318 | MTRational *p = [MTRational rationalFromDecimalRepresentation:@"0.106"]; // 0.106
319 | MTRational *q = [MTRational rationalFromDecimalRepresentation:@"0.104"]; // 0.104
320 | XCTAssertFalse([p isEquivalent:q], @"");
321 | XCTAssertFalse([q isEquivalent:p], @"");
322 | }
323 | }
324 |
325 | - (void) testCompare
326 | {
327 | {
328 | MTRational* p = [MTRational rationalWithNumerator:2 denominator:3];
329 | MTRational* q = [MTRational rationalWithNumerator:5 denominator:3];
330 | XCTAssertEqual([p compare:q], NSOrderedAscending, @"");
331 | XCTAssertEqual([q compare:p], NSOrderedDescending, @"");
332 | }
333 |
334 | {
335 | // different denominators
336 | MTRational* p = [MTRational rationalWithNumerator:2 denominator:3];
337 | MTRational* q = [MTRational rationalWithNumerator:5 denominator:7];
338 | XCTAssertEqual([p compare:q], NSOrderedAscending, @"");
339 | XCTAssertEqual([q compare:p], NSOrderedDescending, @"");
340 | }
341 |
342 | {
343 | // same
344 | MTRational* p = [MTRational rationalWithNumerator:2 denominator:3];
345 | MTRational* q = [MTRational rationalWithNumerator:8 denominator:12];
346 | XCTAssertEqual([p compare:q], NSOrderedSame, @"");
347 | XCTAssertEqual([q compare:p], NSOrderedSame, @"");
348 | }
349 | }
350 |
351 | - (void) testParseCorrectly
352 | {
353 | {
354 | MTRational* testCase = [MTRational rationalFromDecimalRepresentation:@"5"];
355 | MTRational* expected = [MTRational rationalWithNumber:5];
356 | XCTAssertEqualObjects(testCase, expected, @"");
357 | }
358 |
359 | {
360 | MTRational* testCase = [MTRational rationalFromDecimalRepresentation:@"5."];
361 | MTRational* expected = [MTRational rationalWithNumerator:50 denominator:10];
362 | XCTAssertEqualObjects(testCase, expected, @"");
363 | }
364 |
365 | {
366 | MTRational* testCase = [MTRational rationalFromDecimalRepresentation:@"5.1"];
367 | MTRational* expected = [MTRational rationalWithNumerator:51 denominator:10];
368 | XCTAssertEqualObjects(testCase, expected, @"");
369 | }
370 |
371 | {
372 | MTRational* testCase = [MTRational rationalFromDecimalRepresentation:@"25.144"];
373 | MTRational* expected = [MTRational rationalWithNumerator:25144 denominator:1000];
374 | XCTAssertEqualObjects(testCase, expected, @"");
375 | }
376 |
377 | {
378 | MTRational* testCase = [MTRational rationalFromDecimalRepresentation:@"0.14"];
379 | MTRational* expected = [MTRational rationalWithNumerator:14 denominator:100];
380 | XCTAssertEqualObjects(testCase, expected, @"");
381 | }
382 |
383 | {
384 | MTRational* testCase = [MTRational rationalFromDecimalRepresentation:@".14"];
385 | MTRational* expected = [MTRational rationalWithNumerator:14 denominator:100];
386 | XCTAssertEqualObjects(testCase, expected, @"");
387 | }
388 |
389 | }
390 |
391 | - (void) testParseFail
392 | {
393 | NSArray* testCases = @[@"-5", @"-5.", @"-5.1", @"-0.14", @"-.14", @"", @" ", @" 5", @"5 ", @"5,3", @"5 3", @"5.-3", @"a", @"5.a", @"5.3a", @"5.3 ", @"5. 3"];
394 | for (NSString* str in testCases) {
395 | MTRational* r = [MTRational rationalFromDecimalRepresentation:str];
396 | XCTAssertNil(r, @"%@", str);
397 | }
398 | }
399 |
400 | - (void) testPrint
401 | {
402 | {
403 | MTRational* testCase = [MTRational rationalWithNumerator:2 denominator:3];
404 | XCTAssertEqualObjects(testCase.description, @"2/3", @"");
405 | }
406 | {
407 | MTRational* testCase = [MTRational rationalWithNumerator:2 denominator:3];
408 | XCTAssertEqualObjects(testCase.negation.description, @"-2/3", @"");
409 | }
410 | {
411 | MTRational* testCase = [MTRational rationalWithNumerator:-2 denominator:3];
412 | XCTAssertEqualObjects(testCase.description, @"-2/3", @"");
413 | }
414 | {
415 | MTRational* testCase = [MTRational rationalWithNumber:5];
416 | XCTAssertEqualObjects(testCase.description, @"5", @"");
417 | }
418 | {
419 | MTRational* testCase = [MTRational rationalWithNumber:-5];
420 | XCTAssertEqualObjects(testCase.description, @"-5", @"");
421 | }
422 | {
423 | MTRational* testCase = [MTRational rationalFromDecimalRepresentation:@"4.2"];
424 | XCTAssertEqualObjects(testCase.description, @"4.2", @"");
425 | }
426 | {
427 | MTRational* testCase = [MTRational rationalFromDecimalRepresentation:@".2"];
428 | XCTAssertEqualObjects(testCase.description, @"0.2", @"");
429 | }
430 | {
431 | MTRational* testCase = [MTRational rationalFromDecimalRepresentation:@"4.2"];
432 | XCTAssertEqualObjects(testCase.negation.description, @"-4.2", @"");
433 | }
434 | {
435 | MTRational* testCase = [MTRational rationalFromDecimalRepresentation:@".2"];
436 | XCTAssertEqualObjects(testCase.negation.description, @"-0.2", @"");
437 | }
438 | {
439 | MTRational* testCase = [MTRational rationalWithNumerator:6 denominator:3];
440 | XCTAssertEqualObjects(testCase.description, @"6/3", @"");
441 | }
442 | }
443 | @end
444 |
--------------------------------------------------------------------------------
/MathSolverTests/TokenizerTest.h:
--------------------------------------------------------------------------------
1 | //
2 | // TokenizerTest.h
3 | //
4 | // Created by Kostub Deshmukh on 7/14/13.
5 | // Copyright (c) 2013 Math FX.
6 | //
7 | // This software may be modified and distributed under the terms of the
8 | // MIT license. See the LICENSE file for details.
9 | //
10 |
11 | #import
12 |
13 | @interface TokenizerTest : XCTestCase
14 |
15 |
16 | @end
17 |
--------------------------------------------------------------------------------
/MathSolverTests/TokenizerTest.m:
--------------------------------------------------------------------------------
1 | //
2 | // TokenizerTest.m
3 | //
4 | // Created by Kostub Deshmukh on 7/14/13.
5 | // Copyright (c) 2013 Math FX.
6 | //
7 | // This software may be modified and distributed under the terms of the
8 | // MIT license. See the LICENSE file for details.
9 | //
10 |
11 | #import "TokenizerTest.h"
12 | #import "MTTokenizer.h"
13 | #import "MTSymbol.h"
14 |
15 | @implementation TokenizerTest
16 |
17 | - (void) checkSymbol: (MTSymbol *) symbol type:(enum MTSymbolType) type value:(unichar) value
18 | {
19 | XCTAssertEqual(symbol.type, type, @"Type does not match %d", symbol.type);
20 | XCTAssertEqual(symbol.charValue, value, @"Value does not match %c", symbol.charValue);
21 | }
22 |
23 | - (void) checkSymbol:(MTSymbol *) symbol value:(unsigned int) value
24 | {
25 | XCTAssertEqual(symbol.type, kMTSymbolTypeNumber, @"Type does not match %d", symbol.type);
26 | XCTAssertEqual(symbol.intValue, value, @"Value does not match %d", symbol.intValue);
27 | }
28 |
29 | - (void)setUp
30 | {
31 | [super setUp];
32 |
33 | // Set-up code here.
34 | }
35 |
36 | - (void)tearDown
37 | {
38 | // Tear-down code here.
39 |
40 | [super tearDown];
41 | }
42 |
43 | - (void)testSimpleExpression
44 | {
45 | MTTokenizer *tokenizer = [[MTTokenizer alloc] initWithString:@"x+5"];
46 | MTSymbol *s = [tokenizer getNextToken];
47 | [self checkSymbol:s type:kMTSymbolTypeVariable value:'x'];
48 | s = [tokenizer getNextToken];
49 | [self checkSymbol:s type:kMTSymbolTypeOperator value:'+'];
50 | s = [tokenizer getNextToken];
51 | [self checkSymbol:s value:5];
52 | s = [tokenizer getNextToken];
53 | XCTAssertNil(s, @"Tokens not finshed when expected");
54 | }
55 |
56 | - (void)testLongNumber
57 | {
58 | MTTokenizer *tokenizer = [[MTTokenizer alloc] initWithString:@"513"];
59 | MTSymbol *s = [tokenizer getNextToken];
60 | [self checkSymbol:s value:513];
61 | s = [tokenizer getNextToken];
62 | XCTAssertNil(s, @"Tokens not finshed when expected");
63 | }
64 |
65 | - (void)testSpacesHandledCorrectly
66 | {
67 | MTTokenizer *tokenizer = [[MTTokenizer alloc] initWithString:@"x + 5"];
68 | MTSymbol *s = [tokenizer getNextToken];
69 | [self checkSymbol:s type:kMTSymbolTypeVariable value:'x'];
70 | s = [tokenizer getNextToken];
71 | [self checkSymbol:s type:kMTSymbolTypeOperator value:'+'];
72 | s = [tokenizer getNextToken];
73 | [self checkSymbol:s value:5];
74 | s = [tokenizer getNextToken];
75 | XCTAssertNil(s, @"Tokens not finshed when expected");
76 | }
77 |
78 |
79 | - (void)testSpacesAtBeginningAndEnd
80 | {
81 | MTTokenizer *tokenizer = [[MTTokenizer alloc] initWithString:@" x+5 "];
82 | MTSymbol *s = [tokenizer getNextToken];
83 | [self checkSymbol:s type:kMTSymbolTypeVariable value:'x'];
84 | s = [tokenizer getNextToken];
85 | [self checkSymbol:s type:kMTSymbolTypeOperator value:'+'];
86 | s = [tokenizer getNextToken];
87 | [self checkSymbol:s value:5];
88 | s = [tokenizer getNextToken];
89 | XCTAssertNil(s, @"Tokens not finshed when expected");
90 | }
91 |
92 | - (void)testComplexExpressionWithParens
93 | {
94 | MTTokenizer *tokenizer = [[MTTokenizer alloc] initWithString:@"51x + (12y * 4) / 3"];
95 | MTSymbol *s = [tokenizer getNextToken];
96 | [self checkSymbol:s value:51];
97 | s = [tokenizer getNextToken];
98 | [self checkSymbol:s type:kMTSymbolTypeVariable value:'x'];
99 | s = [tokenizer getNextToken];
100 | [self checkSymbol:s type:kMTSymbolTypeOperator value:'+'];
101 | s = [tokenizer getNextToken];
102 | [self checkSymbol:s type:kMTSymbolTypeOpenParen value:0];
103 | s = [tokenizer getNextToken];
104 | [self checkSymbol:s value:12];
105 | s = [tokenizer getNextToken];
106 | [self checkSymbol:s type:kMTSymbolTypeVariable value:'y'];
107 | s = [tokenizer getNextToken];
108 | [self checkSymbol:s type:kMTSymbolTypeOperator value:'*'];
109 | s = [tokenizer getNextToken];
110 | [self checkSymbol:s value:4];
111 | s = [tokenizer getNextToken];
112 | [self checkSymbol:s type:kMTSymbolTypeClosedParen value:0];
113 | s = [tokenizer getNextToken];
114 | [self checkSymbol:s type:kMTSymbolTypeOperator value:'/'];
115 | s = [tokenizer getNextToken];
116 | [self checkSymbol:s value:3];
117 | s = [tokenizer getNextToken];
118 | XCTAssertNil(s, @"Tokens not finshed when expected");
119 | NSLog(@"done");
120 | }
121 |
122 | @end
123 |
--------------------------------------------------------------------------------
/MathSolverTests/rules/CalculateRuleTest.h:
--------------------------------------------------------------------------------
1 | //
2 | // CalculateRuleTest.h
3 | //
4 | // Created by Kostub Deshmukh on 7/19/13.
5 | // Copyright (c) 2013 Math FX.
6 | //
7 | // This software may be modified and distributed under the terms of the
8 | // MIT license. See the LICENSE file for details.
9 | //
10 |
11 | #import
12 |
13 | @interface CalculateRuleTest : XCTestCase
14 |
15 | @end
16 |
--------------------------------------------------------------------------------
/MathSolverTests/rules/CalculateRuleTest.m:
--------------------------------------------------------------------------------
1 | //
2 | // CalculateRuleTest.m
3 | //
4 | // Created by Kostub Deshmukh on 7/19/13.
5 | // Copyright (c) 2013 Math FX.
6 | //
7 | // This software may be modified and distributed under the terms of the
8 | // MIT license. See the LICENSE file for details.
9 | //
10 |
11 | #import "CalculateRuleTest.h"
12 | #import "MTInfixParser.h"
13 | #import "MTExpression.h"
14 | #import "MTCalculateRule.h"
15 | #import "MTFlattenRule.h"
16 | #import "MTMathListBuilder.h"
17 |
18 | @implementation CalculateRuleTest {
19 | MTCalculateRule* _rule;
20 | }
21 |
22 | - (void)setUp
23 | {
24 | [super setUp];
25 |
26 | // Set-up code here.
27 | _rule = [[MTCalculateRule alloc] init];
28 | }
29 |
30 | static NSDictionary* getTestData() {
31 | return @{
32 | @"5" : @"5",
33 | @"x" : @"x",
34 | @"x+5" : @"(x + 5)",
35 | @"5+3+x" : @"(8 + x)",
36 | @"x+5*3" : @"(x + 15)",
37 | @"5*3x" : @"(15 * x)",
38 | @"x*((1+3)+5)" : @"(x * 9)",
39 | @"(5+3)*(2+1*7+3)" : @"96",
40 | @"x+5/1" : @"(x + 5)",
41 | @"x/1" : @"(x * 1)",
42 | @"x/\\frac{5}{5}" : @"(x * 5/5)",
43 | @"x*(3/1+5)" : @"(x * 8)",
44 | @"3x/3" : @"(x * 3/3)",
45 | @"(5+3)/2*(2+3*4/3+3)": @"216/6",
46 | };
47 |
48 | }
49 |
50 |
51 | - (void)testFlattenedExpression
52 | {
53 | MTInfixParser *parser = [[MTInfixParser alloc] init];
54 | MTFlattenRule *flatten = [[MTFlattenRule alloc] init];
55 | MTExpression* expr = [_rule apply:[flatten apply:[parser parseFromString:@"(x+3)+(x+4+5)"]]];
56 | XCTAssertNotNil(expr, @"Rule returned nil");
57 | XCTAssertEqualObjects(@"(x + x + 12)", expr.stringValue, @"Matching the string representation");
58 | }
59 |
60 | - (void) testRule
61 | {
62 | NSDictionary* dict = getTestData();
63 | for (NSString* testExpr in dict) {
64 | NSString* expected = [dict valueForKey:testExpr];
65 | MTInfixParser *parser = [MTInfixParser new];
66 | MTMathList* ml = [MTMathListBuilder buildFromString:testExpr];
67 | MTExpression* expr = [_rule apply:[parser parseToExpressionFromMathList:ml]];
68 | XCTAssertNotNil(expr, @"Rule returned nil for %@", testExpr);
69 | XCTAssertEqualObjects(expected, expr.stringValue, @"For %@", testExpr);
70 | }
71 | }
72 |
73 | - (void)testApplyInnerMost
74 | {
75 | MTInfixParser *parser = [[MTInfixParser alloc] init];
76 | MTExpression* expr = [_rule applyInnerMost:[parser parseFromString:@"(5+16)*7+2*2"] onlyFirst:false];
77 | XCTAssertNotNil(expr, @"Rule returned nil");
78 | XCTAssertEqualObjects(@"((21 * 7) + 4)", expr.stringValue, @"Matching the string representation");
79 | }
80 |
81 | - (void)testApplyInnerMostFirst
82 | {
83 | MTInfixParser *parser = [[MTInfixParser alloc] init];
84 | MTExpression* expr = [_rule applyInnerMost:[parser parseFromString:@"(5+16)*7+2*2"] onlyFirst:true];
85 | XCTAssertNotNil(expr, @"Rule returned nil");
86 | XCTAssertEqualObjects(@"((21 * 7) + (2 * 2))", expr.stringValue, @"Matching the string representation");
87 | }
88 |
89 | @end
90 |
--------------------------------------------------------------------------------
/MathSolverTests/rules/CancelCommonFactorsRuleTest.m:
--------------------------------------------------------------------------------
1 | //
2 | // CancelCommonFactorsRuleTest.m
3 | //
4 | // Created by Kostub Deshmukh on 7/17/14.
5 | // Copyright (c) 2013 Math FX.
6 | //
7 | // This software may be modified and distributed under the terms of the
8 | // MIT license. See the LICENSE file for details.
9 | //
10 |
11 | #import
12 |
13 | #import "MTCancelCommonFactorsRule.h"
14 | #import "MTInfixParser.h"
15 | #import "MTExpression.h"
16 | #import "MTMathListBuilder.h"
17 | #import "MTCanonicalizer.h"
18 |
19 | @interface CancelCommonFactorsRuleTest : XCTestCase
20 |
21 | @end
22 |
23 | @implementation CancelCommonFactorsRuleTest
24 |
25 | - (void)setUp
26 | {
27 | [super setUp];
28 | // Put setup code here. This method is called before the invocation of each test method in the class.
29 | }
30 |
31 | - (void)tearDown
32 | {
33 | // Put teardown code here. This method is called after the invocation of each test method in the class.
34 | [super tearDown];
35 | }
36 |
37 | static NSDictionary* getTestData() {
38 | return @{
39 | @"5" : @"5",
40 | @"x" : @"x",
41 | @"x+5" : @"(x + 5)",
42 | @"x*5" : @"(x * 5)",
43 | @"x*5*3" : @"(x * 5 * 3)",
44 | @"x+5+y" : @"(x + 5 + y)",
45 | @"5x/5" : @"(x / 1)",
46 | @"(5x/x)" : @"(5 / 1)",
47 | @"x/(5x)" : @"(1 / 5)",
48 | @"2/(2x)" : @"(1 / x)",
49 | @"5xy/(3x)" : @"((5 * y) / 3)",
50 | @"6xy/(3xz)" : @"((6 * y) / (3 * z))",
51 | };
52 |
53 | }
54 |
55 | - (void) testRule
56 | {
57 | MTCancelCommonFactorsRule* rule = [MTCancelCommonFactorsRule rule];
58 | MTExpressionCanonicalizer* canonicalizer = [MTCanonicalizerFactory getExpressionCanonicalizer];
59 | NSDictionary* dict = getTestData();
60 | for (NSString* testExpr in dict) {
61 | NSString* expected = [dict valueForKey:testExpr];
62 | MTInfixParser *parser = [MTInfixParser new];
63 | MTMathList* ml = [MTMathListBuilder buildFromString:testExpr];
64 | MTExpression* expr = [rule apply:[canonicalizer normalize:[parser parseToExpressionFromMathList:ml]]];
65 | XCTAssertNotNil(expr, @"Rule returned nil for %@", testExpr);
66 | XCTAssertEqualObjects(expected, expr.stringValue, @"For %@", testExpr);
67 | }
68 | }
69 |
70 | @end
71 |
--------------------------------------------------------------------------------
/MathSolverTests/rules/CanonicalizerTest.h:
--------------------------------------------------------------------------------
1 | //
2 | // CanonicalizerTest.h
3 | //
4 | // Created by Kostub Deshmukh on 9/10/13.
5 | // Copyright (c) 2013 Math FX.
6 | //
7 | // This software may be modified and distributed under the terms of the
8 | // MIT license. See the LICENSE file for details.
9 | //
10 |
11 | #import
12 |
13 | @interface CanonicalizerTest : XCTestCase
14 |
15 | @end
16 |
--------------------------------------------------------------------------------
/MathSolverTests/rules/CanonicalizerTest.m:
--------------------------------------------------------------------------------
1 | //
2 | // CanonicalizerTest.m
3 | //
4 | // Created by Kostub Deshmukh on 9/10/13.
5 | // Copyright (c) 2013 Math FX.
6 | //
7 | // This software may be modified and distributed under the terms of the
8 | // MIT license. See the LICENSE file for details.
9 | //
10 |
11 | #import "CanonicalizerTest.h"
12 | #import "MTCanonicalizer.h"
13 | #import "MTInfixParser.h"
14 | #import "MTMathListBuilder.h"
15 |
16 | @implementation CanonicalizerTest
17 |
18 | // test expressions
19 | static NSArray* getTestExpressions() {
20 | return @[
21 | @[ @"x", @"x", @"x" ],
22 | @[ @"5", @"5", @"5" ],
23 | @[ @"\\frac13", @"1/3", @"1/3" ],
24 | @[ @"\\frac26", @"2/6", @"1/3" ],
25 | @[ @"5x", @"(5 * x)", @"(5 * x)" ],
26 | @[ @"x+3", @"(x + 3)", @"(x + 3)" ],
27 | @[ @"x - 3", @"(x + -3)", @"(x + -3)"],
28 | @[ @"x+y+4", @"(x + y + 4)", @"(x + y + 4)"],
29 | @[ @"2(x + 3) - 4(x - \\frac12)", @"((2 * (x + 3)) + (-4 * (x + -1/2)))", @"((-2 * x) + 8)"],
30 | @[ @"3 / (3+5)", @"(3 / (3 + 5))", @"3/8"],
31 | @[ @"3x / (3+5)", @"((3 * x) / (3 + 5))", @"(3/8 * x)"],
32 | @[ @"3 / (5x) + 6", @"((3 / (5 * x)) + 6)", @"(((6 * x) + 3/5) / x)"],
33 | @[ @"3 / (5 - 5) + 6", @"((3 / (5 + -5)) + 6)", @"(null)"],
34 | @[ @"3 / (2x - \\frac{x}{2} * 4)", @"(3 / ((2 * x) + (-1 * (x / 2) * 4)))", @"(null)"],
35 | @[ @"(x/3)/x", @"((x / 3) / x)", @"1/3"],
36 | @[ @"5/(2/6)", @"(5 / (2 / 6))", @"15"],
37 | @[ @"x/(3/x)", @"(x / (3 / x))", @"(1/3 * x * x)"],
38 | @[ @"(5/2)/(6/4)", @"((5 / 2) / (6 / 4))", @"5/3"],
39 | @[ @"(x/3)/(x/2)", @"((x / 3) / (x / 2))", @"2/3"],
40 | @[ @"x + 1/x", @"(x + (1 / x))", @"(((x * x) + 1) / x)"],
41 | @[ @"x + 1/x + 2/y", @"(x + (1 / x) + (2 / y))", @"(((x * x * y) + (2 * x) + y) / (x * y))"],
42 | @[ @"1/x + 2/x", @"((1 / x) + (2 / x))", @"(3 / x)"],
43 | @[ @"1/2 * 2", @"((1 / 2) * 2)", @"1"],
44 | @[ @"x * (3/2) * (y/3)", @"(x * (3 / 2) * (y / 3))", @"(1/2 * x * y)"],
45 | @[ @"((x/3) + x)/(2(x+1)) + 2x/(x+1) + (1/y)/(1 + 1/y)",
46 | @"((((x / 3) + x) / (2 * (x + 1))) + ((2 * x) / (x + 1)) + ((1 / y) / (1 + (1 / y))))",
47 | @"(((8/3 * x * y) + (11/3 * x) + 1) / ((x * y) + x + y + 1))"],
48 | @[ @"1/(-x)", @"(1 / (-1 * x))", @"(-1 / x)"],
49 | ];
50 | }
51 |
52 | - (void) testExpressionCanonicalizer
53 | {
54 | MTInfixParser *parser = [MTInfixParser new];
55 | MTExpressionCanonicalizer* canonicalizer = [MTCanonicalizerFactory getExpressionCanonicalizer];
56 | NSArray* array = getTestExpressions();
57 | for (NSArray* testCase in array) {
58 | NSString* testExpr = testCase[0];
59 | NSString* desc = [NSString stringWithFormat:@"Error for %@", testExpr];
60 | MTMathList* ml = [MTMathListBuilder buildFromString:testExpr];
61 | MTExpression* expr = [parser parseToExpressionFromMathList:ml];
62 |
63 | // normalize
64 | MTExpression* normalized = [canonicalizer normalize:expr];
65 | MTExpression* normalForm = [canonicalizer normalForm:normalized];
66 | XCTAssertEqualObjects(normalized.stringValue, testCase[1], @"%@", desc);
67 | XCTAssertEqualObjects(normalForm.stringValue, testCase[2], @"%@", desc);
68 | }
69 | }
70 |
71 | // test expressions
72 | static NSArray* getTestEquations() {
73 | return @[
74 | @[ @"x = 0", @"x = 0", @"x = 0" ],
75 | @[ @"x = 3", @"x = 3", @"(x + -3) = 0" ],
76 | @[ @"5x - 3 = 4x - 1", @"((5 * x) + -3) = ((4 * x) + -1)", @"(x + -2) = 0"],
77 | @[ @"3x = 0", @"(3 * x) = 0", @"x = 0" ],
78 | @[ @"3x + 5 = 2", @"((3 * x) + 5) = 2", @"(x + 1) = 0"],
79 | @[ @"3x - 5 = x - 1", @"((3 * x) + -5) = (x + -1)", @"(x + -2) = 0"],
80 | @[ @"(x + 3)(2x - 1) = 2x - 5", @"((x + 3) * ((2 * x) + -1)) = ((2 * x) + -5)", @"((x * x) + (3/2 * x) + 1) = 0"],
81 | @[ @"x(3y + 2z) = 3", @"(x * ((3 * y) + (2 * z))) = 3", @"((x * y) + (2/3 * x * z) + -1) = 0"],
82 | @[ @"3 = 2", @"3 = 2", @"1 = 0" ],
83 | @[ @"x/0 + 2 = 1", @"((x / 0) + 2) = 1", @"(null) = 0"],
84 | @[ @"x + 1/x = 2", @"(x + (1 / x)) = 2", @"((x * x) + (-2 * x) + 1) = 0"],
85 | @[ @"(5x + 8(x+1))/(2x + 3) = 9", @"(((5 * x) + (8 * (x + 1))) / ((2 * x) + 3)) = 9", @"(x + 19/5) = 0" ],
86 | ];
87 | }
88 | - (void) testEquationCanonicalizer
89 | {
90 | MTInfixParser *parser = [MTInfixParser new];
91 | MTEquationCanonicalizer* canonicalizer = [MTCanonicalizerFactory getEquationCanonicalizer];
92 | NSArray* array = getTestEquations();
93 | for (NSArray* testCase in array) {
94 | NSString* testExpr = testCase[0];
95 | NSString* desc = [NSString stringWithFormat:@"Error for %@", testExpr];
96 | MTMathList* ml = [MTMathListBuilder buildFromString:testExpr];
97 | MTEquation* eq = [parser parseToEquationFromMathList:ml];
98 | XCTAssertNotNil(eq, @"%@", desc);
99 |
100 | // normalize
101 | MTEquation* normalized = [canonicalizer normalize:eq];
102 | MTEquation* normalForm = [canonicalizer normalForm:normalized];
103 | XCTAssertEqualObjects(normalized.stringValue, testCase[1], @"%@", desc);
104 | XCTAssertEqualObjects(normalForm.stringValue, testCase[2], @"%@", desc);
105 | }
106 | }
107 | @end
108 |
--------------------------------------------------------------------------------
/MathSolverTests/rules/CollectLikeTermsRuleTest.h:
--------------------------------------------------------------------------------
1 | //
2 | // CombineLikeTermsRuleTest.h
3 | //
4 | // Created by Kostub Deshmukh on 7/20/13.
5 | // Copyright (c) 2013 Math FX.
6 | //
7 | // This software may be modified and distributed under the terms of the
8 | // MIT license. See the LICENSE file for details.
9 | //
10 |
11 | #import
12 |
13 | @interface CollectLikeTermsRuleTest : XCTestCase
14 |
15 | @end
16 |
--------------------------------------------------------------------------------
/MathSolverTests/rules/CollectLikeTermsRuleTest.m:
--------------------------------------------------------------------------------
1 | //
2 | // CombineLikeTermsRuleTest.m
3 | //
4 | // Created by Kostub Deshmukh on 7/20/13.
5 | // Copyright (c) 2013 Math FX.
6 | //
7 | // This software may be modified and distributed under the terms of the
8 | // MIT license. See the LICENSE file for details.
9 | //
10 |
11 | #import "CollectLikeTermsRuleTest.h"
12 | #import "MTInfixParser.h"
13 | #import "MTExpression.h"
14 | #import "MTCollectLikeTermsRule.h"
15 | #import "MTFlattenRule.h"
16 | #import "MTDistributionRule.h"
17 | #import "MTCanonicalizer.h"
18 |
19 | @implementation CollectLikeTermsRuleTest {
20 | MTCollectLikeTermsRule* _rule;
21 | MTExpressionCanonicalizer* _canonicalizer;
22 | }
23 |
24 | - (void)setUp
25 | {
26 | [super setUp];
27 |
28 | // Set-up code here.
29 | _rule = [[MTCollectLikeTermsRule alloc] init];
30 | _canonicalizer = [MTCanonicalizerFactory getExpressionCanonicalizer];
31 | }
32 |
33 |
34 | static NSDictionary* getTestData() {
35 | return @{
36 | @"5" : @"5",
37 | @"x" : @"x",
38 | @"x+5" : @"(x + 5)",
39 | @"5+3+x" : @"((5 + 3) + x)", // does not trigger CLT
40 | @"x+5*3" : @"(x + (5 * 3))",
41 | @"5*3x" : @"((5 * 3) * x)",
42 | @"(x+5)*3" : @"((x + 5) * 3)",
43 | @"4x + 3x" : @"(7 * x)",
44 | @"4x + 3" : @"((4 * x) + 3)",
45 | @"4(x+1) + 3x" : @"((4 * (x + 1)) + (3 * x))",
46 | @"4(x+1) + 3(x+1)" : @"((4 * (x + 1)) + (3 * (x + 1)))",
47 | @"2*(2x + 5x)" : @"(2 * (7 * x))",
48 | @"x + 2x + 3x" : @"(6 * x)",
49 | };
50 |
51 | }
52 |
53 | static NSDictionary* getTestDataForFlatten() {
54 | return @{
55 | @"5+3+x" : @"(5 + 3 + x)", // does not trigger CLT
56 | @"3*(x + 2x + 3)": @"(3 * (3 + (3 * x)))",
57 | @"x + 2x + 3 + 5": @"(8 + (3 * x))",
58 | @"2*3*x + 2*x": @"((2 * 3 * x) + (2 * x))",
59 | @"2x + 5y + 3x*y + 2x*x + y*x + 3y*y + 2y + 4 + 3x*x + 5 + 3y*y + x": @"(9 + (3 * x) + (4 * x * y) + (7 * y) + (5 * x * x) + (6 * y * y))",
60 | @"4x - 2x" : @"(2 * x)",
61 | @"-4x + 3x" : @"(-1 * x)",
62 | @"4x - 5x" : @"(-1 * x)",
63 | @"4xy - 3xy" : @"(1 * x * y)",
64 | @"4xy - 5xy" : @"(-1 * x * y)",
65 | @"4xy - xy" : @"(3 * x * y)",
66 | };
67 |
68 | }
69 |
70 | - (void) testRule
71 | {
72 | NSDictionary* dict = getTestData();
73 | for (NSString* testExpr in dict) {
74 | NSString* expected = [dict valueForKey:testExpr];
75 | MTInfixParser *parser = [MTInfixParser new];
76 | MTExpression* expr = [_rule apply:[parser parseFromString:testExpr]];
77 | XCTAssertNotNil(expr, @"Rule returned nil");
78 | XCTAssertEqualObjects(expected, expr.stringValue, @"Matching the string representation");
79 | }
80 | }
81 |
82 |
83 | - (void)testRuleAfterNormalize
84 | {
85 | NSDictionary* dict = getTestDataForFlatten();
86 | for (NSString* testExpr in dict) {
87 | NSString* expected = [dict valueForKey:testExpr];
88 | MTInfixParser *parser = [MTInfixParser new];
89 | MTExpression* expr = [_rule apply:[_canonicalizer normalize:[parser parseFromString:testExpr]]];
90 | XCTAssertNotNil(expr, @"Rule returned nil");
91 | XCTAssertEqualObjects(expected, expr.stringValue, @"Matching the string representation");
92 | }
93 | }
94 |
95 | - (void)testRuleAfterDistribution
96 | {
97 | MTInfixParser *parser = [[MTInfixParser alloc] init];
98 | MTDistributionRule *distribute = [[MTDistributionRule alloc] init];
99 | MTFlattenRule *flatten = [[MTFlattenRule alloc] init];
100 | MTExpression* expr = [_rule apply:[flatten apply:[distribute apply:[parser parseFromString:@"x(x+1) + x(x+1) + 3x + 1"]]]];
101 | XCTAssertNotNil(expr, @"Rule returned nil");
102 | XCTAssertEqualObjects(@"(1 + (5 * x) + (2 * x * x))", expr.stringValue, @"Matching the string representation");
103 | }
104 |
105 | @end
106 |
--------------------------------------------------------------------------------
/MathSolverTests/rules/DistributionRuleTest.h:
--------------------------------------------------------------------------------
1 | //
2 | // DistributionRuleTest.h
3 | //
4 | // Created by Kostub Deshmukh on 7/19/13.
5 | // Copyright (c) 2013 Math FX.
6 | //
7 | // This software may be modified and distributed under the terms of the
8 | // MIT license. See the LICENSE file for details.
9 | //
10 |
11 | #import
12 |
13 | @interface DistributionRuleTest : XCTestCase
14 |
15 | @end
16 |
--------------------------------------------------------------------------------
/MathSolverTests/rules/DistributionRuleTest.m:
--------------------------------------------------------------------------------
1 | //
2 | // DistributionRuleTest.m
3 | //
4 | // Created by Kostub Deshmukh on 7/19/13.
5 | // Copyright (c) 2013 Math FX.
6 | //
7 | // This software may be modified and distributed under the terms of the
8 | // MIT license. See the LICENSE file for details.
9 | //
10 |
11 | #import "DistributionRuleTest.h"
12 | #import "MTInfixParser.h"
13 | #import "MTExpression.h"
14 | #import "MTDistributionRule.h"
15 | #import "MTFlattenRule.h"
16 |
17 | @implementation DistributionRuleTest {
18 | MTDistributionRule* _rule;
19 | }
20 |
21 | - (void)setUp
22 | {
23 | [super setUp];
24 |
25 | // Set-up code here.
26 | _rule = [[MTDistributionRule alloc] init];
27 | }
28 |
29 |
30 | static NSDictionary* getTestData() {
31 | return @{
32 | @"5" : @"5",
33 | @"x" : @"x",
34 | @"x+5" : @"(x + 5)",
35 | @"5+3+x" : @"((5 + 3) + x)",
36 | @"x+5*3" : @"(x + (5 * 3))",
37 | @"5*3x" : @"((5 * 3) * x)",
38 | @"(x+5)*3" : @"((x * 3) + (5 * 3))",
39 | @"x*((1+3)+5)" : @"((x * (1 + 3)) + (x * 5))",
40 | @"x(3+x)" : @"((x * 3) + (x * x))",
41 | @"5(3+x)" : @"((5 * 3) + (5 * x))",
42 | @"(x+1)(3+x)" : @"((x * (3 + x)) + (1 * (3 + x)))",
43 | @"3(1 + x) + 2(x + 2)": @"(((3 * 1) + (3 * x)) + ((2 * x) + (2 * 2)))",
44 | @"3(1 + 2(1 + x))": @"((3 * 1) + (3 * ((2 * 1) + (2 * x))))"
45 | };
46 |
47 | }
48 |
49 | static NSDictionary* getTestDataForFlatten() {
50 | return @{
51 | @"3*(1+2+3)": @"((3 * 1) + (3 * 2) + (3 * 3))",
52 | @"(1+2+3)x": @"((1 * x) + (2 * x) + (3 * x))",
53 | @"3 * (2 + x) * 5": @"((3 * 2 * 5) + (3 * x * 5))",
54 | @"3(2 + x)(1 + x)": @"((3 * 2 * (1 + x)) + (3 * x * (1 + x)))",
55 | };
56 |
57 | }
58 |
59 | - (void) testRule
60 | {
61 | NSDictionary* dict = getTestData();
62 | for (NSString* testExpr in dict) {
63 | NSString* expected = [dict valueForKey:testExpr];
64 | MTInfixParser *parser = [[MTInfixParser alloc] init];
65 | MTExpression* expr = [_rule apply:[parser parseFromString:testExpr]];
66 | XCTAssertNotNil(expr, @"Rule returned nil");
67 | XCTAssertEqualObjects(expected, expr.stringValue, @"Matching the string representation");
68 | }
69 | }
70 |
71 |
72 | - (void)testRuleAfterFlatten
73 | {
74 | NSDictionary* dict = getTestDataForFlatten();
75 | for (NSString* testExpr in dict) {
76 | NSString* expected = [dict valueForKey:testExpr];
77 | MTInfixParser *parser = [[MTInfixParser alloc] init];
78 | MTFlattenRule *flatten = [[MTFlattenRule alloc] init];
79 | MTExpression* expr = [_rule apply:[flatten apply:[parser parseFromString:testExpr]]];
80 | XCTAssertNotNil(expr, @"Rule returned nil");
81 | XCTAssertEqualObjects(expected, expr.stringValue, @"Matching the string representation");
82 | }
83 | }
84 |
85 | @end
86 |
--------------------------------------------------------------------------------
/MathSolverTests/rules/DivisionIdentityRuleTest.m:
--------------------------------------------------------------------------------
1 | //
2 | // DivisionIdentityRuleTest.m
3 | //
4 | // Created by Kostub Deshmukh on 7/17/14.
5 | // Copyright (c) 2013 Math FX.
6 | //
7 | // This software may be modified and distributed under the terms of the
8 | // MIT license. See the LICENSE file for details.
9 | //
10 |
11 | #import
12 |
13 | #import "MTDivisionIdentityRule.h"
14 | #import "MTInfixParser.h"
15 | #import "MTExpression.h"
16 | #import "MTMathListBuilder.h"
17 |
18 | @interface DivisionIdentityRuleTest : XCTestCase
19 |
20 | @end
21 |
22 | @implementation DivisionIdentityRuleTest
23 |
24 | - (void)setUp
25 | {
26 | [super setUp];
27 | // Put setup code here. This method is called before the invocation of each test method in the class.
28 | }
29 |
30 | - (void)tearDown
31 | {
32 | // Put teardown code here. This method is called after the invocation of each test method in the class.
33 | [super tearDown];
34 | }
35 |
36 | static NSDictionary* getTestData() {
37 | return @{
38 | @"5" : @"5",
39 | @"x" : @"x",
40 | @"x+5" : @"(x + 5)",
41 | @"x*5" : @"(x * 5)",
42 | @"(5/2)" : @"(5 / 2)",
43 | @"(5/1)" : @"5",
44 | @"1/x" : @"(1 / x)",
45 | @"(x/1)" : @"x",
46 | @"(x + 1) / 1" : @"(x + 1)",
47 | };
48 |
49 | }
50 |
51 | - (void) testRule
52 | {
53 | MTDivisionIdentityRule* rule = [MTDivisionIdentityRule rule];
54 | NSDictionary* dict = getTestData();
55 | for (NSString* testExpr in dict) {
56 | NSString* expected = [dict valueForKey:testExpr];
57 | MTInfixParser *parser = [MTInfixParser new];
58 | MTMathList* ml = [MTMathListBuilder buildFromString:testExpr];
59 | MTExpression* expr = [rule apply:[parser parseToExpressionFromMathList:ml]];
60 | XCTAssertNotNil(expr, @"Rule returned nil for %@", testExpr);
61 | XCTAssertEqualObjects(expected, expr.stringValue, @"For %@", testExpr);
62 | }
63 | }
64 | @end
65 |
--------------------------------------------------------------------------------
/MathSolverTests/rules/FlattenRuleTest.h:
--------------------------------------------------------------------------------
1 | //
2 | // FlattenRuleTest.h
3 | //
4 | // Created by Kostub Deshmukh on 7/19/13.
5 | // Copyright (c) 2013 Math FX.
6 | //
7 | // This software may be modified and distributed under the terms of the
8 | // MIT license. See the LICENSE file for details.
9 | //
10 |
11 | #import
12 |
13 | @interface FlattenRuleTest : XCTestCase
14 |
15 | @end
16 |
--------------------------------------------------------------------------------
/MathSolverTests/rules/FlattenRuleTest.m:
--------------------------------------------------------------------------------
1 | //
2 | // FlattenRuleTest.m
3 | //
4 | // Created by Kostub Deshmukh on 7/19/13.
5 | // Copyright (c) 2013 Math FX.
6 | //
7 | // This software may be modified and distributed under the terms of the
8 | // MIT license. See the LICENSE file for details.
9 | //
10 |
11 | #import "FlattenRuleTest.h"
12 | #import "MTInfixParser.h"
13 | #import "MTExpression.h"
14 | #import "MTFlattenRule.h"
15 |
16 | @implementation FlattenRuleTest {
17 | MTFlattenRule* _rule;
18 | }
19 |
20 | - (void)setUp
21 | {
22 | [super setUp];
23 |
24 | // Set-up code here.
25 | _rule = [[MTFlattenRule alloc] init];
26 | }
27 |
28 |
29 | static NSDictionary* getTestData() {
30 | return @{
31 | @"5" : @"5",
32 | @"x" : @"x",
33 | @"x+5" : @"(x + 5)",
34 | @"x+5+3" : @"(x + 5 + 3)",
35 | @"x+5*3" : @"(x + (5 * 3))",
36 | @"5x*3" : @"(5 * x * 3)",
37 | @"x*((1+3)+5)" : @"(x * (1 + 3 + 5))",
38 | @"(x+3)+(x+4+5)" : @"(x + 3 + x + 4 + 5)",
39 | @"5/3/4" : @"((5 / 3) / 4)", // division doesn't flatten
40 | };
41 |
42 | }
43 |
44 | - (void) testRule
45 | {
46 | NSDictionary* dict = getTestData();
47 | for (NSString* testExpr in dict) {
48 | NSString* expected = [dict valueForKey:testExpr];
49 | MTInfixParser *parser = [[MTInfixParser alloc] init];
50 | MTExpression* expr = [_rule apply:[parser parseFromString:testExpr]];
51 | XCTAssertNotNil(expr, @"Rule returned nil");
52 | XCTAssertEqualObjects(expected, expr.stringValue, @"Matching the string representation");
53 | }
54 | }
55 |
56 | @end
57 |
--------------------------------------------------------------------------------
/MathSolverTests/rules/IdentityRuleTest.h:
--------------------------------------------------------------------------------
1 | //
2 | // IdentityRuleTest.h
3 | //
4 | // Created by Kostub Deshmukh on 7/20/13.
5 | // Copyright (c) 2013 Math FX.
6 | //
7 | // This software may be modified and distributed under the terms of the
8 | // MIT license. See the LICENSE file for details.
9 | //
10 |
11 | #import
12 |
13 | @interface IdentityRuleTest : XCTestCase
14 |
15 | @end
16 |
--------------------------------------------------------------------------------
/MathSolverTests/rules/IdentityRuleTest.m:
--------------------------------------------------------------------------------
1 | //
2 | // IdentityRuleTest.m
3 | //
4 | // Created by Kostub Deshmukh on 7/20/13.
5 | // Copyright (c) 2013 Math FX.
6 | //
7 | // This software may be modified and distributed under the terms of the
8 | // MIT license. See the LICENSE file for details.
9 | //
10 |
11 | #import "IdentityRuleTest.h"
12 | #import "MTInfixParser.h"
13 | #import "MTExpression.h"
14 | #import "MTIdentityRule.h"
15 | #import "MTFlattenRule.h"
16 |
17 | @implementation IdentityRuleTest {
18 | MTIdentityRule* _rule;
19 | }
20 |
21 | - (void)setUp
22 | {
23 | [super setUp];
24 |
25 | // Set-up code here.
26 | _rule = [[MTIdentityRule alloc] init];
27 | }
28 |
29 |
30 | static NSDictionary* getTestData() {
31 | return @{
32 | @"5" : @"5",
33 | @"x" : @"x",
34 | @"x+5" : @"(x + 5)",
35 | @"x+0" : @"x",
36 | @"0+x" : @"x",
37 | @"0+3+x" : @"(3 + x)",
38 | @"x+3+0" : @"(x + 3)",
39 | @"x+5*3" : @"(x + (5 * 3))",
40 | @"1x" : @"x",
41 | @"1*3x" : @"(3 * x)",
42 | @"x*((0+3)+5)" : @"(x * (3 + 5))",
43 | };
44 |
45 | }
46 |
47 | static NSDictionary* getTestDataForFlatten() {
48 | return @{
49 | @"3*(1+0+3)": @"(3 * (1 + 3))",
50 | @"(1*2*3) + x": @"((2 * 3) + x)",
51 | };
52 |
53 | }
54 |
55 | - (void) testRule
56 | {
57 | NSDictionary* dict = getTestData();
58 | for (NSString* testExpr in dict) {
59 | NSString* expected = [dict valueForKey:testExpr];
60 | MTInfixParser *parser = [MTInfixParser new];
61 | MTExpression* expr = [_rule apply:[parser parseFromString:testExpr]];
62 | XCTAssertNotNil(expr, @"Rule returned nil");
63 | XCTAssertEqualObjects(expected, expr.stringValue, @"Matching the string representation");
64 | }
65 | }
66 |
67 |
68 | - (void)testRuleAfterFlatten
69 | {
70 | NSDictionary* dict = getTestDataForFlatten();
71 | for (NSString* testExpr in dict) {
72 | NSString* expected = [dict valueForKey:testExpr];
73 | MTInfixParser *parser = [MTInfixParser new];
74 | MTFlattenRule *flatten = [[MTFlattenRule alloc] init];
75 | MTExpression* expr = [_rule apply:[flatten apply:[parser parseFromString:testExpr]]];
76 | XCTAssertNotNil(expr, @"Rule returned nil");
77 | XCTAssertEqualObjects(expected, expr.stringValue, @"Matching the string representation");
78 | }
79 | }
80 |
81 | @end
82 |
--------------------------------------------------------------------------------
/MathSolverTests/rules/NestedDivisionRuleTest.m:
--------------------------------------------------------------------------------
1 | //
2 | // NestedDivisionRuleTest.m
3 | //
4 | // Created by Kostub Deshmukh on 7/16/14.
5 | // Copyright (c) 2013 Math FX.
6 | //
7 | // This software may be modified and distributed under the terms of the
8 | // MIT license. See the LICENSE file for details.
9 | //
10 |
11 | #import
12 |
13 | #import "MTNestedDivisionRule.h"
14 | #import "MTInfixParser.h"
15 | #import "MTExpression.h"
16 | #import "MTMathListBuilder.h"
17 |
18 |
19 | @interface NestedDivisionRuleTest : XCTestCase
20 |
21 | @end
22 |
23 | @implementation NestedDivisionRuleTest {
24 | MTNestedDivisionRule* _rule;
25 | }
26 |
27 | - (void)setUp
28 | {
29 | [super setUp];
30 |
31 | // Set-up code here.
32 | _rule = [MTNestedDivisionRule rule];
33 | }
34 |
35 | static NSDictionary* getTestData() {
36 | return @{
37 | @"5" : @"5",
38 | @"x" : @"x",
39 | @"x+5" : @"(x + 5)",
40 | @"x*5" : @"(x * 5)",
41 | @"(5/2)/6" : @"(5 / (2 * 6))",
42 | @"(x/3)/x" : @"(x / (3 * x))",
43 | @"\\frac{3}{5}/2" : @"(3/5 / 2)",
44 | @"\\frac{3}{5}/2/5" : @"(3/5 / (2 * 5))",
45 | @"5/(2/6)" : @"((5 * 6) / 2)",
46 | @"x/(3/x)" : @"((x * x) / 3)",
47 | @"2/\\frac{3}{5}" : @"(2 / 3/5)",
48 | @"\\frac{3}{5}/(2/5)" : @"((3/5 * 5) / 2)",
49 | // double
50 | @"(5/2)/(6/4)" : @"(5 / (2 * (6 / 4)))",
51 | @"(x/3)/(x/2)" : @"(x / (3 * (x / 2)))",
52 | @"\\frac{3}{5}/\\frac{2}{3}" : @"(3/5 / 2/3)",
53 | @"\\frac{3}{5}/2/(5/4)" : @"(3/5 / (2 * (5 / 4)))",
54 | };
55 |
56 | }
57 |
58 | - (void) testRule
59 | {
60 | NSDictionary* dict = getTestData();
61 | for (NSString* testExpr in dict) {
62 | NSString* expected = [dict valueForKey:testExpr];
63 | MTInfixParser *parser = [MTInfixParser new];
64 | MTMathList* ml = [MTMathListBuilder buildFromString:testExpr];
65 | MTExpression* expr = [_rule apply:[parser parseToExpressionFromMathList:ml]];
66 | XCTAssertNotNil(expr, @"Rule returned nil for %@", testExpr);
67 | XCTAssertEqualObjects(expected, expr.stringValue, @"For %@", testExpr);
68 | }
69 | }
70 |
71 |
72 | @end
73 |
--------------------------------------------------------------------------------
/MathSolverTests/rules/NullRuleTest.m:
--------------------------------------------------------------------------------
1 | //
2 | // NullRuleTest.m
3 | //
4 | // Created by Kostub Deshmukh on 7/15/14.
5 | // Copyright (c) 2013 Math FX.
6 | //
7 | // This software may be modified and distributed under the terms of the
8 | // MIT license. See the LICENSE file for details.
9 | //
10 |
11 | #import
12 |
13 | #import "MTNullRule.h"
14 | #import "MTExpression.h"
15 | #import "MTInfixParser.h"
16 |
17 | @interface NullRuleTest : XCTestCase
18 |
19 | @end
20 |
21 | @implementation NullRuleTest{
22 | MTNullRule* _rule;
23 | }
24 |
25 | - (void)setUp
26 | {
27 | [super setUp];
28 |
29 | // Set-up code here.
30 | _rule = [MTNullRule rule];
31 | }
32 |
33 |
34 | static NSDictionary* getTestData() {
35 | return @{
36 | @"5" : @"5",
37 | @"x" : @"x",
38 | @"x+5" : @"(x + 5)",
39 | @"x+5*3" : @"(x + (5 * 3))",
40 | };
41 |
42 | }
43 |
44 | - (void) testRule
45 | {
46 | NSDictionary* dict = getTestData();
47 | for (NSString* testExpr in dict) {
48 | NSString* expected = [dict valueForKey:testExpr];
49 | MTInfixParser *parser = [[MTInfixParser alloc] init];
50 | MTExpression* expr = [_rule apply:[parser parseFromString:testExpr]];
51 | XCTAssertNotNil(expr, @"Rule returned nil");
52 | XCTAssertEqualObjects(expected, expr.stringValue, @"Matching the string representation");
53 | }
54 | }
55 |
56 | - (void) testRuleWithNull
57 | {
58 | MTOperator* op = [MTOperator operatorWithType:kMTAddition args:[MTNull null] :[MTVariable variableWithName:'x']];
59 | MTExpression* expr = [_rule apply:op];
60 | XCTAssertNotNil(expr, @"Rule returned nil");
61 | XCTAssertEqualObjects([MTNull null], expr, @"Expected null");
62 | }
63 | @end
64 |
--------------------------------------------------------------------------------
/MathSolverTests/rules/RationalAdditionRuleTest.m:
--------------------------------------------------------------------------------
1 | //
2 | // RationalAdditionRuleTest.m
3 | //
4 | // Created by Kostub Deshmukh on 7/16/14.
5 | // Copyright (c) 2014 Math FX.
6 | //
7 | // This software may be modified and distributed under the terms of the
8 | // MIT license. See the LICENSE file for details.
9 | //
10 |
11 | #import
12 |
13 | #import "MTRationalAdditionRule.h"
14 | #import "MTInfixParser.h"
15 | #import "MTExpression.h"
16 | #import "MTMathListBuilder.h"
17 | #import "MTCanonicalizer.h"
18 |
19 |
20 | @interface RationalAdditionRuleTest : XCTestCase
21 |
22 | @end
23 |
24 | @implementation RationalAdditionRuleTest
25 |
26 | - (void)setUp
27 | {
28 | [super setUp];
29 | // Put setup code here. This method is called before the invocation of each test method in the class.
30 | }
31 |
32 | - (void)tearDown
33 | {
34 | // Put teardown code here. This method is called after the invocation of each test method in the class.
35 | [super tearDown];
36 | }
37 |
38 |
39 | static NSDictionary* getTestData() {
40 | return @{
41 | @"5" : @"5",
42 | @"x" : @"x",
43 | @"x+5" : @"(x + 5)",
44 | @"x*5" : @"(x * 5)",
45 | @"x*5*3" : @"(x * 5 * 3)",
46 | @"x+5+y" : @"(x + 5 + y)",
47 | @"x + 1/x" : @"((1 + (x * x)) / x)",
48 | @"x + 1/x + 2/y": @"((1 + (x * (x + (2 / y)))) / x)",
49 | @"1/x + 2/x" : @"((1 + (x * (2 / x))) / x)",
50 | @"1/x + 2/(x+1)" : @"((1 + (x * (2 / (x + 1)))) / x)",
51 | };
52 |
53 | }
54 |
55 | - (void) testRule
56 | {
57 | MTRationalAdditionRule* rule = [MTRationalAdditionRule rule];
58 | MTExpressionCanonicalizer* canonicalizer = [MTCanonicalizerFactory getExpressionCanonicalizer];
59 | NSDictionary* dict = getTestData();
60 | for (NSString* testExpr in dict) {
61 | NSString* expected = [dict valueForKey:testExpr];
62 | MTInfixParser *parser = [MTInfixParser new];
63 | MTMathList* ml = [MTMathListBuilder buildFromString:testExpr];
64 | MTExpression* expr = [rule apply:[canonicalizer normalize:[parser parseToExpressionFromMathList:ml]]];
65 | XCTAssertNotNil(expr, @"Rule returned nil for %@", testExpr);
66 | XCTAssertEqualObjects(expected, expr.stringValue, @"For %@", testExpr);
67 | }
68 | }
69 |
70 | @end
71 |
--------------------------------------------------------------------------------
/MathSolverTests/rules/RationalMultiplicationRuleTest.m:
--------------------------------------------------------------------------------
1 | //
2 | // RationalMultiplicationRuleTest.m
3 | //
4 | // Created by Kostub Deshmukh on 7/16/14.
5 | // Copyright (c) 2014 Math FX.
6 | //
7 | // This software may be modified and distributed under the terms of the
8 | // MIT license. See the LICENSE file for details.
9 | //
10 |
11 | #import
12 |
13 | #import "MTRationalMultiplicationRule.h"
14 | #import "MTInfixParser.h"
15 | #import "MTExpression.h"
16 | #import "MTMathListBuilder.h"
17 | #import "MTCanonicalizer.h"
18 |
19 |
20 | @interface RationalMultiplicationRuleTest : XCTestCase
21 |
22 | @end
23 |
24 | @implementation RationalMultiplicationRuleTest
25 |
26 | - (void)setUp
27 | {
28 | [super setUp];
29 | // Put setup code here. This method is called before the invocation of each test method in the class.
30 | }
31 |
32 | - (void)tearDown
33 | {
34 | // Put teardown code here. This method is called after the invocation of each test method in the class.
35 | [super tearDown];
36 | }
37 |
38 |
39 | static NSDictionary* getTestData() {
40 | return @{
41 | @"5" : @"5",
42 | @"x" : @"x",
43 | @"x+5" : @"(x + 5)",
44 | @"x*5" : @"(x * 5)",
45 | @"x*5*3" : @"(x * 5 * 3)",
46 | @"1/2 * 2" :@"((1 * 2) / 2)",
47 | @"x/2 * y" : @"((x * y) / 2)",
48 | @"x * (3/2) * y" : @"((x * 3 * y) / 2)",
49 | @"x * (3/2) * (y/3)" : @"((x * 3 * y) / (2 * 3))",
50 | };
51 |
52 | }
53 |
54 | - (void) testRule
55 | {
56 | MTRationalMultiplicationRule* rule = [MTRationalMultiplicationRule rule];
57 | MTExpressionCanonicalizer* canonicalizer = [MTCanonicalizerFactory getExpressionCanonicalizer];
58 | NSDictionary* dict = getTestData();
59 | for (NSString* testExpr in dict) {
60 | NSString* expected = [dict valueForKey:testExpr];
61 | MTInfixParser *parser = [MTInfixParser new];
62 | MTMathList* ml = [MTMathListBuilder buildFromString:testExpr];
63 | MTExpression* expr = [rule apply:[canonicalizer normalize:[parser parseToExpressionFromMathList:ml]]];
64 | XCTAssertNotNil(expr, @"Rule returned nil for %@", testExpr);
65 | XCTAssertEqualObjects(expected, expr.stringValue, @"For %@", testExpr);
66 | }
67 | }
68 |
69 | @end
70 |
--------------------------------------------------------------------------------
/MathSolverTests/rules/RemoveNegativesRuleTest.h:
--------------------------------------------------------------------------------
1 | //
2 | // RemoveNegativesRuleTest.h
3 | //
4 | // Created by Kostub Deshmukh on 7/18/13.
5 | // Copyright (c) 2013 Math FX.
6 | //
7 | // This software may be modified and distributed under the terms of the
8 | // MIT license. See the LICENSE file for details.
9 | //
10 |
11 | #import
12 |
13 | @interface RemoveNegativesRuleTest : XCTestCase
14 |
15 | @end
16 |
--------------------------------------------------------------------------------
/MathSolverTests/rules/RemoveNegativesRuleTest.m:
--------------------------------------------------------------------------------
1 | //
2 | // RemoveNegativesRuleTest.m
3 | //
4 | // Created by Kostub Deshmukh on 7/18/13.
5 | // Copyright (c) 2013 Math FX.
6 | //
7 | // This software may be modified and distributed under the terms of the
8 | // MIT license. See the LICENSE file for details.
9 | //
10 |
11 | #import "RemoveNegativesRuleTest.h"
12 | #import "MTInfixParser.h"
13 | #import "MTExpression.h"
14 | #import "MTRemoveNegativesRule.h"
15 |
16 | @implementation RemoveNegativesRuleTest {
17 | MTRemoveNegativesRule* _rule;
18 | }
19 |
20 | - (void)setUp
21 | {
22 | [super setUp];
23 |
24 | // Set-up code here.
25 | _rule = [[MTRemoveNegativesRule alloc] init];
26 | }
27 |
28 | static NSDictionary* getTestData() {
29 | return @{
30 | @"5" : @"5",
31 | @"x" : @"x",
32 | @"x+5" : @"(x + 5)",
33 | @"x+5+3" : @"((x + 5) + 3)",
34 | @"x+5*3" : @"(x + (5 * 3))",
35 | @"5x*3" : @"((5 * x) * 3)",
36 | @"x*((1/3)+5)" : @"(x * ((1 / 3) + 5))",
37 | @"x-3" : @"(x + -3)",
38 | @"3-x" : @"(3 + (-1 * x))",
39 | @"x-3+x" : @"((x + -3) + x)",
40 | @"x-3x" : @"(x + (-3 * x))",
41 | @"-3" : @"-3",
42 | @"-x" : @"(-1 * x)",
43 | @"-3 + x" : @"(-3 + x)",
44 | @"-3x" : @"(-3 * x)",
45 | @"x + (-3)" : @"(x + -3)",
46 | @"x * -3" : @"(x * -3)",
47 | @"-(x+3)" : @"(-1 * (x + 3))",
48 | @"-xy" : @"((-1 * x) * y)",
49 | @"-3xy" : @"((-3 * x) * y)",
50 | @"x - 3xy" : @"(x + ((-3 * x) * y))",
51 | @"x - xy" : @"(x + (-1 * (x * y)))",
52 | @"3-(2+x)x" : @"(3 + (-1 * ((2 + x) * x)))",
53 | @"(5x - 3)/(2x + 1) - (-3x - 3)(1-(-2x))" : @"((((5 * x) + -3) / ((2 * x) + 1)) + (-1 * (((-3 * x) + -3) * (1 + (2 * x)))))",
54 | };
55 |
56 | }
57 |
58 | - (void) testRule
59 | {
60 | NSDictionary* dict = getTestData();
61 | for (NSString* testExpr in dict) {
62 | NSString* expected = [dict valueForKey:testExpr];
63 | MTInfixParser *parser = [MTInfixParser new];
64 | MTExpression* expr = [_rule apply:[parser parseFromString:testExpr]];
65 | XCTAssertNotNil(expr, @"Rule returned nil");
66 | XCTAssertEqualObjects(expected, expr.stringValue, @"Matching the string representation");
67 | }
68 | }
69 |
70 |
71 | @end
72 |
--------------------------------------------------------------------------------
/MathSolverTests/rules/ReorderTermsRuleTest.h:
--------------------------------------------------------------------------------
1 | //
2 | // ReorderTermsRuleTest.h
3 | //
4 | // Created by Kostub Deshmukh on 7/21/13.
5 | // Copyright (c) 2013 Math FX.
6 | //
7 | // This software may be modified and distributed under the terms of the
8 | // MIT license. See the LICENSE file for details.
9 | //
10 |
11 | #import
12 |
13 | @interface ReorderTermsRuleTest : XCTestCase
14 |
15 | @end
16 |
--------------------------------------------------------------------------------
/MathSolverTests/rules/ReorderTermsRuleTest.m:
--------------------------------------------------------------------------------
1 | //
2 | // ReorderTermsRuleTest.m
3 | //
4 | // Created by Kostub Deshmukh on 7/21/13.
5 | // Copyright (c) 2013 Math FX.
6 | //
7 | // This software may be modified and distributed under the terms of the
8 | // MIT license. See the LICENSE file for details.
9 | //
10 |
11 | #import "ReorderTermsRuleTest.h"
12 | #import "MTInfixParser.h"
13 | #import "MTExpression.h"
14 | #import "MTFlattenRule.h"
15 | #import "MTReorderTermsRule.h"
16 |
17 | @implementation ReorderTermsRuleTest
18 |
19 | static NSDictionary* getTestData() {
20 | return @{
21 | @"5" : @"5",
22 | @"x" : @"x",
23 | @"x+5" : @"(x + 5)",
24 | @"5+x" : @"(x + 5)",
25 | @"3 + x + 2" : @"(x + 2 + 3)",
26 | @"3 + 2*3" : @"(3 + (2 * 3))",
27 | @"2*3 + 3" : @"(3 + (2 * 3))",
28 | @"x + 3 + 2*3" : @"(x + 3 + (2 * 3))",
29 | @"2*3 + 3 + x" : @"(x + 3 + (2 * 3))",
30 | @"3x" : @"(3 * x)",
31 | @"x*3" : @"(3 * x)",
32 | @"x + y" : @"(x + y)",
33 | @"y + x" : @"(x + y)",
34 | @"1 + y + x": @"(x + y + 1)",
35 | @"x + 2y" : @"(x + (2 * y))",
36 | @"2y + x" : @"(x + (2 * y))",
37 | @"2x + y" : @"((2 * x) + y)",
38 | @"y + 2x" : @"((2 * x) + y)",
39 | @"y + (x*3)" : @"((3 * x) + y)",
40 | @"5xy" : @"(5 * x * y)",
41 | @"5yx" : @"(5 * x * y)",
42 | @"yx*5" : @"(5 * x * y)",
43 | @"5xx" : @"(5 * x * x)",
44 | @"1 + 2y + 3x" : @"((3 * x) + (2 * y) + 1)",
45 | @"3x + 1 + 2y" : @"((3 * x) + (2 * y) + 1)",
46 | @"3x + 3xy + 2y + 3xx + yy" : @"((3 * x * x) + (3 * x * y) + (y * y) + (3 * x) + (2 * y))",
47 | @"yy + 3yx + 3x + 2y + 3xx" : @"((3 * x * x) + (3 * x * y) + (y * y) + (3 * x) + (2 * y))",
48 | @"yyx + 3zzx + 3xxz + 2xxx + 3yyz + zzz" : @"((2 * x * x * x) + (3 * x * x * z) + (x * y * y) + (3 * x * z * z) + (3 * y * y * z) + (z * z * z))",
49 | // These are weird ones and should never occur in practice
50 | @"5(x+1)x" : @"(5 * x * (x + 1))",
51 | @"5(x+1)x + xx" : @"((x * x) + (5 * x * (x + 1)))",
52 | @"xx + 5(x+1)x" : @"((x * x) + (5 * x * (x + 1)))",
53 | @"5(x+1)x + x(x + 2)" : @"((5 * x * (x + 1)) + (x * (x + 2)))",
54 | };
55 |
56 | }
57 |
58 | - (void)testRule
59 | {
60 | MTReorderTermsRule* rule = [[MTReorderTermsRule alloc] init];
61 | MTFlattenRule *flatten = [[MTFlattenRule alloc] init];
62 | NSDictionary* dict = getTestData();
63 | for (NSString* testExpr in dict) {
64 | NSString* expected = [dict valueForKey:testExpr];
65 | MTInfixParser *parser = [MTInfixParser new];
66 | MTExpression* expr = [rule apply:[flatten apply:[parser parseFromString:testExpr]]];
67 | XCTAssertNotNil(expr, @"Rule returned nil");
68 | XCTAssertEqualObjects(expr.stringValue, expected, @"For expression %@", testExpr);
69 | }
70 | }
71 |
72 | @end
73 |
--------------------------------------------------------------------------------
/MathSolverTests/rules/ZeroRuleTest.h:
--------------------------------------------------------------------------------
1 | //
2 | // ZeroRuleTest.h
3 | //
4 | // Created by Kostub Deshmukh on 7/20/13.
5 | // Copyright (c) 2013 Math FX.
6 | //
7 | // This software may be modified and distributed under the terms of the
8 | // MIT license. See the LICENSE file for details.
9 | //
10 |
11 | #import
12 |
13 | @interface ZeroRuleTest : XCTestCase
14 |
15 | @end
16 |
--------------------------------------------------------------------------------
/MathSolverTests/rules/ZeroRuleTest.m:
--------------------------------------------------------------------------------
1 | //
2 | // ZeroRuleTest.m
3 | //
4 | // Created by Kostub Deshmukh on 7/20/13.
5 | // Copyright (c) 2013 Math FX.
6 | //
7 | // This software may be modified and distributed under the terms of the
8 | // MIT license. See the LICENSE file for details.
9 | //
10 |
11 | #import "ZeroRuleTest.h"
12 | #import "MTInfixParser.h"
13 | #import "MTExpression.h"
14 | #import "MTZeroRule.h"
15 |
16 | @implementation ZeroRuleTest {
17 | MTZeroRule* _rule;
18 | }
19 |
20 | - (void)setUp
21 | {
22 | [super setUp];
23 |
24 | // Set-up code here.
25 | _rule = [[MTZeroRule alloc] init];
26 | }
27 |
28 |
29 | static NSDictionary* getTestData() {
30 | return @{
31 | @"5" : @"5",
32 | @"x" : @"x",
33 | @"x+5" : @"(x + 5)",
34 | @"x+5*3" : @"(x + (5 * 3))",
35 | @"x+5*0" : @"(x + 0)",
36 | @"0x" : @"0",
37 | @"0*3x" : @"0",
38 | @"x*((0*3)+5)" : @"(x * (0 + 5))",
39 | };
40 |
41 | }
42 |
43 | - (void) testRule
44 | {
45 | NSDictionary* dict = getTestData();
46 | for (NSString* testExpr in dict) {
47 | NSString* expected = [dict valueForKey:testExpr];
48 | MTInfixParser *parser = [[MTInfixParser alloc] init];
49 | MTExpression* expr = [_rule apply:[parser parseFromString:testExpr]];
50 | XCTAssertNotNil(expr, @"Rule returned nil");
51 | XCTAssertEqualObjects(expected, expr.stringValue, @"Matching the string representation");
52 | }
53 | }
54 |
55 |
56 | @end
57 |
--------------------------------------------------------------------------------
/Podfile:
--------------------------------------------------------------------------------
1 | target 'MathSolver' do
2 | pod 'iosMath', '~> 0.7.2'
3 | end
4 | target 'MathSolverTests' do
5 | pod 'MathSolver', :path => './'
6 | end
7 |
--------------------------------------------------------------------------------
/Podfile.lock:
--------------------------------------------------------------------------------
1 | PODS:
2 | - iosMath (0.7.2)
3 | - MathSolver (0.2.0):
4 | - iosMath (~> 0.7.2)
5 |
6 | DEPENDENCIES:
7 | - iosMath (~> 0.7.2)
8 | - MathSolver (from `./`)
9 |
10 | EXTERNAL SOURCES:
11 | MathSolver:
12 | :path: ./
13 |
14 | SPEC CHECKSUMS:
15 | iosMath: b0907041e16ff69b5da8833dd47126ac68d987ae
16 | MathSolver: 5556263e656a5cb5848bc2eeb536e75d46e4d39a
17 |
18 | PODFILE CHECKSUM: d6e15b39ef4bf22460ed27a3d3bfe4ef34622367
19 |
20 | COCOAPODS: 1.0.0
21 |
--------------------------------------------------------------------------------