├── .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 | --------------------------------------------------------------------------------