├── .gitignore ├── .slather.yml ├── .swiftformat ├── CHANGELOG.md ├── CODEOWNERS ├── Documentation └── INSTALLATION_GUIDE.md ├── Gemfile ├── Gemfile.lock ├── LICENSE ├── MathExpression.podspec ├── MathExpression.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist ├── xcshareddata │ ├── xcbaselines │ │ └── 9F153DA0231E87A4005A6966.xcbaseline │ │ │ ├── 93FBB218-913E-4092-9842-36EF602C8886.plist │ │ │ ├── A2BA1546-25D3-486D-8D64-9A8BE5867597.plist │ │ │ └── Info.plist │ └── xcschemes │ │ ├── MathExpression-iOS.xcscheme │ │ ├── MathExpression-macOS.xcscheme │ │ ├── MathExpression-tvOS.xcscheme │ │ ├── MathExpressionExample.xcscheme │ │ └── PerformanceTests.xcscheme └── xcuserdata │ └── peredaniel.xcuserdatad │ ├── xcdebugger │ └── Breakpoints_v2.xcbkptlist │ └── xcschemes │ └── xcschememanagement.plist ├── MathExpression ├── Info.plist ├── MathExpression.h └── Source │ ├── Extensions │ ├── FloatingPoint+Extensions.swift │ └── Formatter+Extensions.swift │ ├── Models │ ├── MathBrackets.swift │ └── MathOperator.swift │ └── Parser │ ├── MathExpression.swift │ └── MathFormula.swift ├── MathExpressionExample ├── Assets.xcassets │ ├── AccentColor.colorset │ │ └── Contents.json │ ├── AppIcon.appiconset │ │ ├── Contents.json │ │ ├── arithmetic-1024.png │ │ ├── arithmetic-120.png │ │ ├── arithmetic-121.png │ │ ├── arithmetic-152.png │ │ ├── arithmetic-167.png │ │ ├── arithmetic-180.png │ │ ├── arithmetic-20.png │ │ ├── arithmetic-29.png │ │ ├── arithmetic-40.png │ │ ├── arithmetic-41.png │ │ ├── arithmetic-42.png │ │ ├── arithmetic-58.png │ │ ├── arithmetic-59.png │ │ ├── arithmetic-60.png │ │ ├── arithmetic-76.png │ │ ├── arithmetic-80.png │ │ ├── arithmetic-81.png │ │ └── arithmetic-87.png │ └── Contents.json ├── ExampleApp.swift ├── Extensions │ ├── Formatter+MathExpression.swift │ └── Int+Factorial.swift ├── Models │ ├── MathTransformation.swift │ └── ValidationError.swift ├── Preview Content │ └── Preview Assets.xcassets │ │ └── Contents.json └── Screens │ ├── Calculator │ ├── CalculatorView.swift │ ├── CalculatorViewModel.swift │ └── Supporting views │ │ └── CalculatorKeyboardView.swift │ ├── Evaluator │ ├── EvaluatorView.swift │ └── EvaluatorViewModel.swift │ └── MenuView.swift ├── MathExpressionPerformanceTests ├── Info.plist └── Source │ ├── (Q - {0}, *) group tests │ ├── AbelianMultiplicativeGroupAxiomTests.swift │ ├── DivisionPerformanceTests.swift │ ├── ProductAndDivisionPerformanceTests.swift │ └── ProductPerformanceTests.swift │ ├── (Q, +) group tests │ ├── AbelianAdditiveGroupAxiomsPerformanceTests.swift │ ├── AdditionAndSubtractionPerformanceTests.swift │ ├── AdditionPerformanceTests.swift │ └── SubtractionPerformanceTests.swift │ ├── (Q, +, *) field tests │ ├── CombinedOperationsPerformanceTest.swift │ └── FieldAxiomPerformanceTests.swift │ ├── Non-trivial transformation tests │ ├── CountTransformationPerformanceTests.swift │ └── FactorialTransformationPerformanceTests.swift │ ├── Stress tests │ └── StressPerformanceTests.swift │ └── Validation tests │ └── ValidationPerformanceTests.swift ├── MathExpressionTestHelpers ├── Extensions │ ├── Double+RoundedToPlaces.swift │ ├── Numeric+Random.swift │ ├── String+Random.swift │ └── XCTestCase+AssertError.swift └── Helpers │ ├── Formulae.swift │ ├── Operation.swift │ └── RandomExpressionGenerator.swift ├── MathExpressionTests ├── Info.plist └── Source │ ├── (Q - {0}, *) group tests │ ├── AbelianMultiplicativeGroupAxiomTests.swift │ ├── DivisionTests.swift │ ├── ProductAndDivisionTests.swift │ └── ProductTests.swift │ ├── (Q, +) group tests │ ├── AbelianAdditiveGroupAxiomsTests.swift │ ├── AdditionAndSubtractionTests.swift │ ├── AdditionTests.swift │ └── SubtractionTests.swift │ ├── (Q, +, *) field tests │ ├── CombinedOperationsTest.swift │ └── FieldAxiomTests.swift │ ├── Non-trivial transformation tests │ ├── CountTransformationTests.swift │ ├── ExponentialTransformationTests.swift │ └── FactorialTransformationTests.swift │ └── Validation tests │ └── ValidationTests.swift ├── Package.swift ├── README.md └── fastlane ├── Fastfile └── README.md /.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 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata/ 19 | 20 | ## Other 21 | *.moved-aside 22 | *.xccheckout 23 | *.xcscmblueprint 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | *.ipa 28 | *.dSYM.zip 29 | *.dSYM 30 | 31 | ## Playgrounds 32 | timeline.xctimeline 33 | playground.xcworkspace 34 | 35 | # Swift Package Manager 36 | # 37 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 38 | # Packages/ 39 | # Package.pins 40 | # Package.resolved 41 | .build/ 42 | 43 | # CocoaPods 44 | # 45 | # We recommend against adding the Pods directory to your .gitignore. However 46 | # you should judge for yourself, the pros and cons are mentioned at: 47 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 48 | # 49 | # Pods/ 50 | 51 | # Carthage 52 | # 53 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 54 | # Carthage/Checkouts 55 | 56 | Carthage/Build 57 | 58 | # fastlane 59 | # 60 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 61 | # screenshots whenever they are needed. 62 | # For more information about the recommended setup visit: 63 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 64 | 65 | fastlane/report.xml 66 | fastlane/report.html 67 | fastlane/report.junit 68 | fastlane/Preview.html 69 | fastlane/screenshots/**/*.png 70 | fastlane/test_output 71 | 72 | .swiftpm/* 73 | -------------------------------------------------------------------------------- /.slather.yml: -------------------------------------------------------------------------------- 1 | coverage_service: coveralls 2 | xcodeproj: MathExpression.xcodeproj 3 | scheme: MathExpression-iOS 4 | source_directory: MathExpression/* 5 | ignore: 6 | - MathExpressionPerformanceTests/* 7 | - MathExpressionTestHelpers/* 8 | - MathExpressionTests/* -------------------------------------------------------------------------------- /.swiftformat: -------------------------------------------------------------------------------- 1 | --allman false 2 | --binarygrouping none 3 | --closingparen balanced 4 | --commas inline 5 | --conflictmarkers reject 6 | --decimalgrouping none 7 | --elseposition same-line 8 | --empty void 9 | --exponentcase lowercase 10 | --exponentgrouping disabled 11 | --fractiongrouping disabled 12 | --fragment false 13 | --header ignore 14 | --hexgrouping none 15 | --hexliteralcase uppercase 16 | --ifdef indent 17 | --importgrouping alphabetized 18 | --indent 4 19 | --indentcase false 20 | --linebreaks lf 21 | --octalgrouping none 22 | --operatorfunc spaced 23 | --patternlet inline 24 | --ranges no-space 25 | --self remove 26 | --selfrequired 27 | --semicolons never 28 | --stripunusedargs always 29 | --trailingclosures 30 | --trimwhitespace always 31 | --wraparguments before-first 32 | --wrapcollections before-first 33 | --xcodeindentation disabled 34 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ### 1.3.0 2 | 3 | * Bumped minimum Xcode version to 13.0+. 4 | * Bumped Swift version to 5.0 in `Package.swift`. 5 | * Bumped target platform minimum version to 12.0+ (iOS and tvOS) and 10.14 (macOS). 6 | * Improved errors thrown when validation fails due to invalid consecutive operators (on some cases the error was thrown but was of different type). 7 | * Improved tests implementation to check for correct error being thrown. 8 | * Removed Travis CI integration due to Open Source projects support being dropped. 9 | * Reimplemented example app using SwiftUI. 10 | * Improved documentation and added step-by-step installation guide. 11 | 12 | #### Breaking changes 13 | 14 | * `MathExpression.ValidationError.consecutiveMultiplicativeOperators(String)` has been renamed to `MathExpression.ValidationError.invalidConsecutiveOperators(String)`. 15 | 16 | ### 1.2.0 17 | 18 | * Added support for [Swift Package Manager](https://swift.org/package-manager/). 19 | * Added example app. 20 | * Improved documentation. 21 | 22 | ### 1.1.1 23 | 24 | * Fixed issue in operation priority. 25 | * Added further tests. 26 | 27 | ### 1.1.0 28 | 29 | * Added support for tvOS 10 or higher. 30 | * Addes support for macOS 10.10 or higher. 31 | * Improved CI setup. 32 | 33 | ### 1.0.1 34 | 35 | * Improved parentheses validation algorithm (validation between 20% and 80% faster). 36 | * Improved parentheses decomposition algorithm (evaluation between 15% and 85% faster). 37 | * Added stress performance tests. 38 | 39 | ### 1.0.0 40 | 41 | Initial Stable Release - Xcode 10.0+, Swift 4.2+, iOS 10+ 42 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @peredaniel -------------------------------------------------------------------------------- /Documentation/INSTALLATION_GUIDE.md: -------------------------------------------------------------------------------- 1 | In this documentation we provide a step-by-step installation guide, with screenshots and further details than the [general documentation](../README.md). Because no one starts app development by knowing how to do everything! 2 | 3 | We don't include step-by-step guide for Carthage since most developers using this package manager (usually do already know how to solve issues. In case you encounter an issue with Carthage, please open an Issue here in GitHub and we'll take a look. 4 | 5 | ## Using Cocoapods 6 | 7 | First you need to install Cocoapods using the terminal, create a `Podfile` inside your project and declare the module. Your Podfile should look like this: 8 | 9 | ```ruby 10 | # Uncomment the next line to define a global platform for your project 11 | platform :ios, '14.0' 12 | source 'https://cdn.cocoapods.org/' 13 | 14 | # Comment the next line if you don't want to use dynamic frameworks 15 | use_frameworks! 16 | 17 | target 'MyProject' do 18 | pod 'MathExpression' 19 | end 20 | ``` 21 | 22 | Maybe specifying a version. Then you execute `pod install --repo-update` on the project root folder (where the Podfile is located). If everything goes right (no error), from now on you must use the `MyProject.xcworkspace` file to open your project instead of the `MyProject.xcodeproj` (in Finder, it's the one with the white icon instead of the one with the blue icon). This is due to the management from Cocoapods. If you open the `xcodeproj` file you'll have an empty `Pods` folder. 23 | 24 | ## Swift Package Manager 25 | 26 | Since Xcode 11, Swift Package Manager is embedded into Xcode, so we strongly recommend using the Xcode interface to add packages. It's just a few steps: 27 | 1. Open your project on Xcode. 28 | 2. Open `File` in the top menu, then `Add Packages...`, and you'll see a screen like this: 29 | 30 | ![Captura de Pantalla 2022-03-30 a las 8 42 19](https://user-images.githubusercontent.com/40358007/160768235-b9acb1b5-256a-4cf2-ad74-a9b68d3ac267.png) 31 | 32 | 3. On this new screen there is a search bar in the top-right corner. Type-in or paste the URL for this repository (`https://github.com/peredaniel/MathExpression.git`) and it will change to this: 33 | 34 | ![Captura de Pantalla 2022-03-30 a las 8 45 52](https://user-images.githubusercontent.com/40358007/160768505-92340b16-9754-4848-8537-16006db93752.png) 35 | 36 | 4. Change "Dependency Rule" to "Up to Next Major Version": 37 | 38 | ![Captura de Pantalla 2022-03-30 a las 8 47 06](https://user-images.githubusercontent.com/40358007/160768691-7ec669e0-0ea4-4377-b30f-1074b5c0ebc8.png) 39 | 40 | 5. Finally tap "Add Package" on the bottom-right corner of the screen. 41 | 6. This will fetch the `Package.swift` file in this repository, and a confirmation screen appears: 42 | 43 | ![Captura de Pantalla 2022-03-30 a las 8 48 11](https://user-images.githubusercontent.com/40358007/160768895-ceac054b-f290-4ea7-b794-bd3a1e299a4e.png) 44 | 45 | 7. Unless you are managing more than a single target (usually you don't), simply click "Add Package". Xcode will now fetch the files in the repository and add them to your project: 46 | 47 | ![Captura de Pantalla 2022-03-30 a las 8 49 44](https://user-images.githubusercontent.com/40358007/160769124-045001ae-27de-4343-a564-c3130d3d4eac.png) 48 | 49 | From then, you can use `import MathExpression` in any Swift file belonging to the same target. 50 | 51 | ### Manual installation 52 | 53 | Clone this repository into your hard-drive, or download it as a ZIP file and uncompress it. Then drag and drop the `Sources` folder into any group inside your Project **in Xcode**. The following screen should appears: 54 | 55 | ![Captura de Pantalla 2022-03-30 a las 8 53 21](https://user-images.githubusercontent.com/40358007/160769769-581abf5a-9a80-4f66-bab7-c5278260cfeb.png) 56 | 57 | Make sure to check "Copy items if needed". We also recommend using "Create groups" instead of "Create folder references", but this is a personal choice. Your project structure should now look like this: 58 | 59 | ![Captura de Pantalla 2022-03-30 a las 8 54 59](https://user-images.githubusercontent.com/40358007/160770022-08d22638-a1a0-41db-8090-6285f8a651ce.png) 60 | 61 | Now you can use any class, struct or extension as if they were defined in your project (since, in fact, they are!), so no need to add `import MathExpression` when you want to use it. In fact, the module does not exist: the files are treated as your own in your project, in the same way as any other file you add to it. 62 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gem 'fastlane', '~>2.207.0' 4 | gem 'cocoapods', '~>1.11.0' 5 | gem 'slather', '~> 2.7.0' 6 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | CFPropertyList (3.0.5) 5 | rexml 6 | activesupport (6.1.7.3) 7 | concurrent-ruby (~> 1.0, >= 1.0.2) 8 | i18n (>= 1.6, < 2) 9 | minitest (>= 5.1) 10 | tzinfo (~> 2.0) 11 | zeitwerk (~> 2.3) 12 | addressable (2.8.0) 13 | public_suffix (>= 2.0.2, < 5.0) 14 | algoliasearch (1.27.5) 15 | httpclient (~> 2.8, >= 2.8.3) 16 | json (>= 1.5.1) 17 | artifactory (3.0.15) 18 | atomos (0.1.3) 19 | aws-eventstream (1.2.0) 20 | aws-partitions (1.602.0) 21 | aws-sdk-core (3.131.2) 22 | aws-eventstream (~> 1, >= 1.0.2) 23 | aws-partitions (~> 1, >= 1.525.0) 24 | aws-sigv4 (~> 1.1) 25 | jmespath (~> 1, >= 1.6.1) 26 | aws-sdk-kms (1.57.0) 27 | aws-sdk-core (~> 3, >= 3.127.0) 28 | aws-sigv4 (~> 1.1) 29 | aws-sdk-s3 (1.114.0) 30 | aws-sdk-core (~> 3, >= 3.127.0) 31 | aws-sdk-kms (~> 1) 32 | aws-sigv4 (~> 1.4) 33 | aws-sigv4 (1.5.0) 34 | aws-eventstream (~> 1, >= 1.0.2) 35 | babosa (1.0.4) 36 | claide (1.1.0) 37 | clamp (1.3.2) 38 | cocoapods (1.11.3) 39 | addressable (~> 2.8) 40 | claide (>= 1.0.2, < 2.0) 41 | cocoapods-core (= 1.11.3) 42 | cocoapods-deintegrate (>= 1.0.3, < 2.0) 43 | cocoapods-downloader (>= 1.4.0, < 2.0) 44 | cocoapods-plugins (>= 1.0.0, < 2.0) 45 | cocoapods-search (>= 1.0.0, < 2.0) 46 | cocoapods-trunk (>= 1.4.0, < 2.0) 47 | cocoapods-try (>= 1.1.0, < 2.0) 48 | colored2 (~> 3.1) 49 | escape (~> 0.0.4) 50 | fourflusher (>= 2.3.0, < 3.0) 51 | gh_inspector (~> 1.0) 52 | molinillo (~> 0.8.0) 53 | nap (~> 1.0) 54 | ruby-macho (>= 1.0, < 3.0) 55 | xcodeproj (>= 1.21.0, < 2.0) 56 | cocoapods-core (1.11.3) 57 | activesupport (>= 5.0, < 7) 58 | addressable (~> 2.8) 59 | algoliasearch (~> 1.0) 60 | concurrent-ruby (~> 1.1) 61 | fuzzy_match (~> 2.0.4) 62 | nap (~> 1.0) 63 | netrc (~> 0.11) 64 | public_suffix (~> 4.0) 65 | typhoeus (~> 1.0) 66 | cocoapods-deintegrate (1.0.5) 67 | cocoapods-downloader (1.6.3) 68 | cocoapods-plugins (1.0.0) 69 | nap 70 | cocoapods-search (1.0.1) 71 | cocoapods-trunk (1.6.0) 72 | nap (>= 0.8, < 2.0) 73 | netrc (~> 0.11) 74 | cocoapods-try (1.2.0) 75 | colored (1.2) 76 | colored2 (3.1.2) 77 | commander (4.6.0) 78 | highline (~> 2.0.0) 79 | concurrent-ruby (1.2.2) 80 | declarative (0.0.20) 81 | digest-crc (0.6.4) 82 | rake (>= 12.0.0, < 14.0.0) 83 | domain_name (0.5.20190701) 84 | unf (>= 0.0.5, < 1.0.0) 85 | dotenv (2.7.6) 86 | emoji_regex (3.2.3) 87 | escape (0.0.4) 88 | ethon (0.15.0) 89 | ffi (>= 1.15.0) 90 | excon (0.92.3) 91 | faraday (1.10.0) 92 | faraday-em_http (~> 1.0) 93 | faraday-em_synchrony (~> 1.0) 94 | faraday-excon (~> 1.1) 95 | faraday-httpclient (~> 1.0) 96 | faraday-multipart (~> 1.0) 97 | faraday-net_http (~> 1.0) 98 | faraday-net_http_persistent (~> 1.0) 99 | faraday-patron (~> 1.0) 100 | faraday-rack (~> 1.0) 101 | faraday-retry (~> 1.0) 102 | ruby2_keywords (>= 0.0.4) 103 | faraday-cookie_jar (0.0.7) 104 | faraday (>= 0.8.0) 105 | http-cookie (~> 1.0.0) 106 | faraday-em_http (1.0.0) 107 | faraday-em_synchrony (1.0.0) 108 | faraday-excon (1.1.0) 109 | faraday-httpclient (1.0.1) 110 | faraday-multipart (1.0.4) 111 | multipart-post (~> 2) 112 | faraday-net_http (1.0.1) 113 | faraday-net_http_persistent (1.2.0) 114 | faraday-patron (1.0.0) 115 | faraday-rack (1.0.0) 116 | faraday-retry (1.0.3) 117 | faraday_middleware (1.2.0) 118 | faraday (~> 1.0) 119 | fastimage (2.2.6) 120 | fastlane (2.207.0) 121 | CFPropertyList (>= 2.3, < 4.0.0) 122 | addressable (>= 2.8, < 3.0.0) 123 | artifactory (~> 3.0) 124 | aws-sdk-s3 (~> 1.0) 125 | babosa (>= 1.0.3, < 2.0.0) 126 | bundler (>= 1.12.0, < 3.0.0) 127 | colored 128 | commander (~> 4.6) 129 | dotenv (>= 2.1.1, < 3.0.0) 130 | emoji_regex (>= 0.1, < 4.0) 131 | excon (>= 0.71.0, < 1.0.0) 132 | faraday (~> 1.0) 133 | faraday-cookie_jar (~> 0.0.6) 134 | faraday_middleware (~> 1.0) 135 | fastimage (>= 2.1.0, < 3.0.0) 136 | gh_inspector (>= 1.1.2, < 2.0.0) 137 | google-apis-androidpublisher_v3 (~> 0.3) 138 | google-apis-playcustomapp_v1 (~> 0.1) 139 | google-cloud-storage (~> 1.31) 140 | highline (~> 2.0) 141 | json (< 3.0.0) 142 | jwt (>= 2.1.0, < 3) 143 | mini_magick (>= 4.9.4, < 5.0.0) 144 | multipart-post (~> 2.0.0) 145 | naturally (~> 2.2) 146 | optparse (~> 0.1.1) 147 | plist (>= 3.1.0, < 4.0.0) 148 | rubyzip (>= 2.0.0, < 3.0.0) 149 | security (= 0.1.3) 150 | simctl (~> 1.6.3) 151 | terminal-notifier (>= 2.0.0, < 3.0.0) 152 | terminal-table (>= 1.4.5, < 2.0.0) 153 | tty-screen (>= 0.6.3, < 1.0.0) 154 | tty-spinner (>= 0.8.0, < 1.0.0) 155 | word_wrap (~> 1.0.0) 156 | xcodeproj (>= 1.13.0, < 2.0.0) 157 | xcpretty (~> 0.3.0) 158 | xcpretty-travis-formatter (>= 0.0.3) 159 | ffi (1.15.5) 160 | fourflusher (2.3.1) 161 | fuzzy_match (2.0.4) 162 | gh_inspector (1.1.3) 163 | google-apis-androidpublisher_v3 (0.23.0) 164 | google-apis-core (>= 0.6, < 2.a) 165 | google-apis-core (0.7.0) 166 | addressable (~> 2.5, >= 2.5.1) 167 | googleauth (>= 0.16.2, < 2.a) 168 | httpclient (>= 2.8.1, < 3.a) 169 | mini_mime (~> 1.0) 170 | representable (~> 3.0) 171 | retriable (>= 2.0, < 4.a) 172 | rexml 173 | webrick 174 | google-apis-iamcredentials_v1 (0.12.0) 175 | google-apis-core (>= 0.6, < 2.a) 176 | google-apis-playcustomapp_v1 (0.9.0) 177 | google-apis-core (>= 0.6, < 2.a) 178 | google-apis-storage_v1 (0.16.0) 179 | google-apis-core (>= 0.6, < 2.a) 180 | google-cloud-core (1.6.0) 181 | google-cloud-env (~> 1.0) 182 | google-cloud-errors (~> 1.0) 183 | google-cloud-env (1.6.0) 184 | faraday (>= 0.17.3, < 3.0) 185 | google-cloud-errors (1.2.0) 186 | google-cloud-storage (1.37.0) 187 | addressable (~> 2.8) 188 | digest-crc (~> 0.4) 189 | google-apis-iamcredentials_v1 (~> 0.1) 190 | google-apis-storage_v1 (~> 0.1) 191 | google-cloud-core (~> 1.6) 192 | googleauth (>= 0.16.2, < 2.a) 193 | mini_mime (~> 1.0) 194 | googleauth (1.2.0) 195 | faraday (>= 0.17.3, < 3.a) 196 | jwt (>= 1.4, < 3.0) 197 | memoist (~> 0.16) 198 | multi_json (~> 1.11) 199 | os (>= 0.9, < 2.0) 200 | signet (>= 0.16, < 2.a) 201 | highline (2.0.3) 202 | http-cookie (1.0.5) 203 | domain_name (~> 0.5) 204 | httpclient (2.8.3) 205 | i18n (1.12.0) 206 | concurrent-ruby (~> 1.0) 207 | jmespath (1.6.1) 208 | json (2.6.2) 209 | jwt (2.4.1) 210 | memoist (0.16.2) 211 | mini_magick (4.11.0) 212 | mini_mime (1.1.2) 213 | mini_portile2 (2.8.1) 214 | minitest (5.18.0) 215 | molinillo (0.8.0) 216 | multi_json (1.15.0) 217 | multipart-post (2.0.0) 218 | nanaimo (0.3.0) 219 | nap (1.1.0) 220 | naturally (2.2.1) 221 | netrc (0.11.0) 222 | nokogiri (1.14.3) 223 | mini_portile2 (~> 2.8.0) 224 | racc (~> 1.4) 225 | optparse (0.1.1) 226 | os (1.1.4) 227 | plist (3.6.0) 228 | public_suffix (4.0.7) 229 | racc (1.6.2) 230 | rake (13.0.6) 231 | representable (3.2.0) 232 | declarative (< 0.1.0) 233 | trailblazer-option (>= 0.1.1, < 0.2.0) 234 | uber (< 0.2.0) 235 | retriable (3.1.2) 236 | rexml (3.2.5) 237 | rouge (2.0.7) 238 | ruby-macho (2.5.1) 239 | ruby2_keywords (0.0.5) 240 | rubyzip (2.3.2) 241 | security (0.1.3) 242 | signet (0.17.0) 243 | addressable (~> 2.8) 244 | faraday (>= 0.17.5, < 3.a) 245 | jwt (>= 1.5, < 3.0) 246 | multi_json (~> 1.10) 247 | simctl (1.6.8) 248 | CFPropertyList 249 | naturally 250 | slather (2.7.2) 251 | CFPropertyList (>= 2.2, < 4) 252 | activesupport 253 | clamp (~> 1.3) 254 | nokogiri (~> 1.12) 255 | xcodeproj (~> 1.21) 256 | terminal-notifier (2.0.0) 257 | terminal-table (1.8.0) 258 | unicode-display_width (~> 1.1, >= 1.1.1) 259 | trailblazer-option (0.1.2) 260 | tty-cursor (0.7.1) 261 | tty-screen (0.8.1) 262 | tty-spinner (0.9.3) 263 | tty-cursor (~> 0.7) 264 | typhoeus (1.4.0) 265 | ethon (>= 0.9.0) 266 | tzinfo (2.0.6) 267 | concurrent-ruby (~> 1.0) 268 | uber (0.1.0) 269 | unf (0.1.4) 270 | unf_ext 271 | unf_ext (0.0.8.2) 272 | unicode-display_width (1.8.0) 273 | webrick (1.7.0) 274 | word_wrap (1.0.0) 275 | xcodeproj (1.22.0) 276 | CFPropertyList (>= 2.3.3, < 4.0) 277 | atomos (~> 0.1.3) 278 | claide (>= 1.0.2, < 2.0) 279 | colored2 (~> 3.1) 280 | nanaimo (~> 0.3.0) 281 | rexml (~> 3.2.4) 282 | xcpretty (0.3.0) 283 | rouge (~> 2.0.7) 284 | xcpretty-travis-formatter (1.0.1) 285 | xcpretty (~> 0.2, >= 0.0.7) 286 | zeitwerk (2.6.7) 287 | 288 | PLATFORMS 289 | ruby 290 | 291 | DEPENDENCIES 292 | cocoapods (~> 1.11.0) 293 | fastlane (~> 2.207.0) 294 | slather (~> 2.7.0) 295 | 296 | BUNDLED WITH 297 | 1.17.2 298 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Pere Daniel Prieto 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. -------------------------------------------------------------------------------- /MathExpression.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |spec| 2 | spec.name = 'MathExpression' 3 | spec.version = '1.3.0' 4 | spec.summary = 'Framework to parse and evaluate arithmetic mathematical expressions given by a String' 5 | spec.description = 'This framework provides an algorithm and an API to easily evaluate arithmetic mathematical expressions given by a String. In addition to the basic arithmetic operators (addition, subtraction, product and division), we can pass in a transformation (in the form of a block) to add flexibility in the expressions provided.' 6 | spec.license = { :type => 'MIT', :file => 'LICENSE' } 7 | spec.homepage = 'https://github.com/peredaniel/MathExpression' 8 | spec.authors = { 'Pere Daniel Prieto' => 'math.pedro.daniel.prieto@gmail.com' } 9 | spec.source = { :git => 'https://github.com/peredaniel/MathExpression.git', :tag => spec.version } 10 | 11 | spec.platform = :ios, :tvos, :osx 12 | spec.ios.deployment_target = '12.0' 13 | spec.tvos.deployment_target = '12.0' 14 | spec.osx.deployment_target = '10.14' 15 | 16 | spec.swift_version = "5.0" 17 | 18 | spec.ios.source_files = ['MathExpression/**/*.{h,m,swift}'] 19 | spec.tvos.source_files = ['MathExpression/**/*.{h,m,swift}'] 20 | spec.osx.source_files = ['MathExpression/Source/**/*.{h,m,swift}'] 21 | end 22 | -------------------------------------------------------------------------------- /MathExpression.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /MathExpression.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /MathExpression.xcodeproj/xcshareddata/xcbaselines/9F153DA0231E87A4005A6966.xcbaseline/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | runDestinationsByUUID 6 | 7 | 93FBB218-913E-4092-9842-36EF602C8886 8 | 9 | localComputer 10 | 11 | busSpeedInMHz 12 | 400 13 | cpuCount 14 | 1 15 | cpuKind 16 | 8-Core Intel Core i9 17 | cpuSpeedInMHz 18 | 2300 19 | logicalCPUCoresPerPackage 20 | 16 21 | modelCode 22 | MacBookPro16,1 23 | physicalCPUCoresPerPackage 24 | 8 25 | platformIdentifier 26 | com.apple.platform.macosx 27 | 28 | targetArchitecture 29 | x86_64 30 | targetDevice 31 | 32 | modelCode 33 | iPhone12,1 34 | platformIdentifier 35 | com.apple.platform.iphonesimulator 36 | 37 | 38 | A2BA1546-25D3-486D-8D64-9A8BE5867597 39 | 40 | localComputer 41 | 42 | busSpeedInMHz 43 | 100 44 | cpuCount 45 | 1 46 | cpuKind 47 | Intel Core i5 48 | cpuSpeedInMHz 49 | 2500 50 | logicalCPUCoresPerPackage 51 | 4 52 | modelCode 53 | MacBookPro9,2 54 | physicalCPUCoresPerPackage 55 | 2 56 | platformIdentifier 57 | com.apple.platform.macosx 58 | 59 | targetArchitecture 60 | x86_64 61 | targetDevice 62 | 63 | modelCode 64 | iPhone10,4 65 | platformIdentifier 66 | com.apple.platform.iphonesimulator 67 | 68 | 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /MathExpression.xcodeproj/xcshareddata/xcschemes/MathExpression-iOS.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 38 | 39 | 40 | 41 | 43 | 49 | 50 | 51 | 52 | 53 | 63 | 64 | 70 | 71 | 72 | 73 | 79 | 80 | 86 | 87 | 88 | 89 | 91 | 92 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /MathExpression.xcodeproj/xcshareddata/xcschemes/MathExpression-macOS.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 41 | 42 | 52 | 53 | 59 | 60 | 61 | 62 | 68 | 69 | 75 | 76 | 77 | 78 | 80 | 81 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /MathExpression.xcodeproj/xcshareddata/xcschemes/MathExpression-tvOS.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 55 | 61 | 62 | 63 | 64 | 65 | 66 | 72 | 73 | 79 | 80 | 81 | 82 | 84 | 85 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /MathExpression.xcodeproj/xcshareddata/xcschemes/MathExpressionExample.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 41 | 42 | 52 | 54 | 60 | 61 | 62 | 63 | 69 | 71 | 77 | 78 | 79 | 80 | 82 | 83 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /MathExpression.xcodeproj/xcshareddata/xcschemes/PerformanceTests.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 34 | 40 | 41 | 42 | 43 | 44 | 54 | 55 | 61 | 62 | 63 | 64 | 70 | 71 | 77 | 78 | 79 | 80 | 82 | 83 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /MathExpression.xcodeproj/xcuserdata/peredaniel.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /MathExpression.xcodeproj/xcuserdata/peredaniel.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | MathExpression-iOS.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | MathExpression-macOS.xcscheme_^#shared#^_ 13 | 14 | orderHint 15 | 1 16 | 17 | MathExpression-tvOS.xcscheme_^#shared#^_ 18 | 19 | orderHint 20 | 2 21 | 22 | MathExpressionExample.xcscheme_^#shared#^_ 23 | 24 | orderHint 25 | 3 26 | 27 | PerformanceTests.xcscheme_^#shared#^_ 28 | 29 | orderHint 30 | 4 31 | 32 | 33 | SuppressBuildableAutocreation 34 | 35 | 9F153DA0231E87A4005A6966 36 | 37 | primary 38 | 39 | 40 | 9F1A2CE023166BAF009CEB51 41 | 42 | primary 43 | 44 | 45 | 9F1A2CE923166BAF009CEB51 46 | 47 | primary 48 | 49 | 50 | 9FA74AA02327D2630049E401 51 | 52 | primary 53 | 54 | 55 | 9FF96D9E2321399200FC90CA 56 | 57 | primary 58 | 59 | 60 | 9FF96DB023214AAF00FC90CA 61 | 62 | primary 63 | 64 | 65 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /MathExpression/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | $(MARKETING_VERSION) 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | 22 | 23 | -------------------------------------------------------------------------------- /MathExpression/MathExpression.h: -------------------------------------------------------------------------------- 1 | // Copyright © 2019 Pedro Daniel Prieto Martínez. Distributed under MIT License. 2 | 3 | #import 4 | 5 | //! Project version number for MathExpression. 6 | FOUNDATION_EXPORT double MathExpressionVersionNumber; 7 | 8 | //! Project version string for MathExpression. 9 | FOUNDATION_EXPORT const unsigned char MathExpressionVersionString[]; 10 | 11 | // In this header, you should import all the public headers of your framework using statements like #import 12 | 13 | 14 | -------------------------------------------------------------------------------- /MathExpression/Source/Extensions/FloatingPoint+Extensions.swift: -------------------------------------------------------------------------------- 1 | // Copyright © 2019 Pedro Daniel Prieto Martínez. Distributed under MIT License. 2 | 3 | import Foundation 4 | 5 | extension FloatingPoint { 6 | var negative: Self { 7 | var negativeValue = self 8 | negativeValue.negate() 9 | return negativeValue 10 | } 11 | 12 | func avoidScientificNotation() -> String { 13 | Formatter.avoidScientificNotation.string(for: self) ?? "" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /MathExpression/Source/Extensions/Formatter+Extensions.swift: -------------------------------------------------------------------------------- 1 | // Copyright © 2019 Pedro Daniel Prieto Martínez. Distributed under MIT License. 2 | 3 | import Foundation 4 | 5 | extension Formatter { 6 | static let avoidScientificNotation: NumberFormatter = { 7 | let numberFormatter = NumberFormatter() 8 | numberFormatter.maximumFractionDigits = 16 9 | numberFormatter.numberStyle = .decimal 10 | numberFormatter.decimalSeparator = "." 11 | numberFormatter.usesGroupingSeparator = false 12 | return numberFormatter 13 | }() 14 | } 15 | -------------------------------------------------------------------------------- /MathExpression/Source/Models/MathBrackets.swift: -------------------------------------------------------------------------------- 1 | // Copyright © 2019 Pedro Daniel Prieto Martínez. Distributed under MIT License. 2 | 3 | enum MathBrackets: CaseIterable { 4 | case parenthesis 5 | 6 | var opening: String { 7 | switch self { 8 | case .parenthesis: return "(" 9 | } 10 | } 11 | 12 | var closing: String { 13 | switch self { 14 | case .parenthesis: return ")" 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /MathExpression/Source/Models/MathOperator.swift: -------------------------------------------------------------------------------- 1 | // Copyright © 2019 Pedro Daniel Prieto Martínez. Distributed under MIT License. 2 | 3 | import Foundation 4 | 5 | enum AdditiveOperator: String, CaseIterable { 6 | case sum = "+" 7 | case subtraction = "-" 8 | 9 | var character: Character { 10 | Character(rawValue) 11 | } 12 | } 13 | 14 | enum MathOperator: String { 15 | case negative = "_" 16 | case product = "*" 17 | case division = "/" 18 | case sum = "+" 19 | case subtraction = "-" 20 | 21 | static var evaluationCases: [MathOperator] { 22 | [.sum, .subtraction, .product, .division, .negative] 23 | } 24 | 25 | static var multiplicativeOperators: [MathOperator] { 26 | [.product, .division] 27 | } 28 | 29 | static var validationCases: [MathOperator] { 30 | [.product, .division, .sum, .subtraction] 31 | } 32 | } 33 | 34 | // MARK: - Static variables 35 | 36 | extension MathOperator { 37 | static var validConsecutiveOperators: [String: String] { 38 | [ 39 | MathOperator.sum.rawValue + MathOperator.subtraction.rawValue: MathOperator.subtraction.rawValue, 40 | MathOperator.subtraction.rawValue + MathOperator.sum.rawValue: MathOperator.subtraction.rawValue, 41 | MathOperator.sum.rawValue + MathOperator.sum.rawValue: MathOperator.sum.rawValue, 42 | MathOperator.subtraction.rawValue + MathOperator.subtraction.rawValue: MathOperator.sum.rawValue 43 | ] 44 | } 45 | 46 | static var validConsecutiveOperatorsDuringEvaluation: [String: String] { 47 | [ 48 | MathOperator.negative.rawValue + MathOperator.sum.rawValue: MathOperator.negative.rawValue, 49 | MathOperator.negative.rawValue + MathOperator.subtraction.rawValue: MathOperator.sum.rawValue, 50 | MathOperator.sum.rawValue + MathOperator.subtraction.rawValue: MathOperator.sum.rawValue + MathOperator.negative.rawValue, 51 | MathOperator.product.rawValue + MathOperator.subtraction.rawValue: MathOperator.product.rawValue + MathOperator.negative.rawValue, 52 | MathOperator.division.rawValue + MathOperator.subtraction.rawValue: MathOperator.division.rawValue + MathOperator.negative.rawValue 53 | ] 54 | } 55 | 56 | static var invalidConsecutiveOperators: [String] { 57 | [ 58 | MathOperator.sum.rawValue + MathOperator.product.rawValue, 59 | MathOperator.sum.rawValue + MathOperator.division.rawValue, 60 | MathOperator.subtraction.rawValue + MathOperator.product.rawValue, 61 | MathOperator.subtraction.rawValue + MathOperator.division.rawValue, 62 | MathOperator.product.rawValue + MathOperator.product.rawValue, 63 | MathOperator.product.rawValue + MathOperator.division.rawValue, 64 | MathOperator.division.rawValue + MathOperator.product.rawValue, 65 | MathOperator.division.rawValue + MathOperator.division.rawValue 66 | ] 67 | } 68 | } 69 | 70 | // MARK: - Computed variables 71 | 72 | extension MathOperator { 73 | var character: Character { 74 | Character(rawValue) 75 | } 76 | 77 | func apply(to args: [Double]) -> Double { 78 | switch self { 79 | case .sum: return args.sum() 80 | case .subtraction: return args.subtract() 81 | case .product: return args.multiply() 82 | case .division: return args.divide() 83 | case .negative: return args.first?.negative ?? .zero 84 | } 85 | } 86 | } 87 | 88 | // MARK: - Helper extensions 89 | 90 | private extension Array where Element == Double { 91 | func sum() -> Element { 92 | guard let last = last else { return .zero } 93 | return last + dropLast().sum() 94 | } 95 | 96 | func subtract() -> Double { 97 | guard let first = first else { return .zero } 98 | return first + reversed().dropLast().map { $0.negative }.sum() 99 | } 100 | 101 | func multiply() -> Double { 102 | guard !contains(.zero) else { return .zero } 103 | guard let last = last else { return 1.0 } 104 | return last * dropLast().multiply() 105 | } 106 | 107 | func divide() -> Double { 108 | guard let numerator = first else { return 1.0 } 109 | return numerator / reversed().dropLast().multiply() 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /MathExpression/Source/Parser/MathExpression.swift: -------------------------------------------------------------------------------- 1 | // Copyright © 2019 Pedro Daniel Prieto Martínez. Distributed under MIT License. 2 | 3 | import Foundation 4 | 5 | public struct MathExpression { 6 | public enum ValidationError: Error { 7 | case emptyExpression 8 | case misplacedBrackets 9 | case unevenOpeningClosingBracketNumber 10 | case invalidConsecutiveOperators(String) 11 | case startsWithNonSumOrSubtractionOperator(String) 12 | case endsWithOperator(String) 13 | } 14 | 15 | private let formula: MathFormula 16 | 17 | public init( 18 | _ string: String, 19 | transformation: @escaping (String) -> Double = { Double($0) ?? .zero } 20 | ) throws { 21 | formula = try MathFormula(string, transformation: transformation) 22 | } 23 | 24 | internal init(_ formula: MathFormula) { 25 | self.formula = formula 26 | } 27 | 28 | public func evaluate() -> Double { 29 | switch formula.evaluationState() { 30 | case .isNumeric(let value): 31 | return value 32 | case .startsWithSymbol(let symbol): 33 | switch symbol { 34 | case .sum: 35 | return MathExpression(formula.dropingInitialValue()).evaluate() 36 | case .subtraction: 37 | return MathExpression(formula.replaceSubtractionByNegative()).evaluate() 38 | } 39 | case .containsBracket(let brackets): 40 | return MathExpression(formula.evaluatingExpression(between: brackets)).evaluate() 41 | case .canApplyOperator(let mathOperator): 42 | return mathOperator.apply( 43 | to: formula.decompose(with: mathOperator).map { MathExpression($0) }.evaluate() 44 | ) 45 | case .canApplyTransformation: 46 | return formula.applyTransformation() 47 | } 48 | } 49 | } 50 | 51 | private extension Array where Element == MathExpression { 52 | func evaluate() -> [Double] { 53 | map { $0.evaluate() } 54 | } 55 | } 56 | 57 | extension MathExpression.ValidationError: Equatable { 58 | public static func == (lhs: MathExpression.ValidationError, rhs: MathExpression.ValidationError) -> Bool { 59 | switch (lhs, rhs) { 60 | case (.emptyExpression, .emptyExpression), 61 | (.misplacedBrackets, .misplacedBrackets), 62 | (.unevenOpeningClosingBracketNumber, .unevenOpeningClosingBracketNumber): 63 | return true 64 | case (.invalidConsecutiveOperators(let lhsValue), .invalidConsecutiveOperators(let rhsValue)), 65 | (.startsWithNonSumOrSubtractionOperator(let lhsValue), .startsWithNonSumOrSubtractionOperator(let rhsValue)), 66 | (.endsWithOperator(let lhsValue), .endsWithOperator(let rhsValue)): 67 | return lhsValue == rhsValue 68 | default: 69 | return false 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /MathExpression/Source/Parser/MathFormula.swift: -------------------------------------------------------------------------------- 1 | // Copyright © 2019 Pedro Daniel Prieto Martínez. Distributed under MIT License. 2 | 3 | struct MathFormula { 4 | enum EvaluationState { 5 | case isNumeric(Double) 6 | case startsWithSymbol(AdditiveOperator) 7 | case containsBracket(MathBrackets) 8 | case canApplyOperator(MathOperator) 9 | case canApplyTransformation 10 | } 11 | 12 | private let string: String 13 | private let transformation: (String) -> Double 14 | 15 | init( 16 | _ string: String, 17 | transformation: @escaping (String) -> Double = { Double($0) ?? .zero } 18 | ) throws { 19 | self.string = string.trimmingWhiteSpacesAndDoubleMathOperators() 20 | self.transformation = transformation 21 | try validate() 22 | } 23 | 24 | init( 25 | validString string: String, 26 | transformation: @escaping (String) -> Double 27 | ) { 28 | self.string = string.trimmingWhiteSpacesAndDoubleMathOperators() 29 | self.transformation = transformation 30 | } 31 | } 32 | 33 | // MARK: - Computed properties 34 | 35 | extension MathFormula { 36 | var asDouble: Double? { 37 | Double(string) 38 | } 39 | 40 | func evaluationState(validating: Bool = false) -> EvaluationState { 41 | if let value = asDouble { 42 | return .isNumeric(value) 43 | } 44 | 45 | for additiveOperator in AdditiveOperator.allCases { 46 | if starts(with: additiveOperator) { 47 | return .startsWithSymbol(additiveOperator) 48 | } 49 | } 50 | 51 | for brackets in MathBrackets.allCases { 52 | if containsBracket(brackets) { 53 | return .containsBracket(brackets) 54 | } 55 | } 56 | 57 | if let firstOperator = getFirstOperator(validating: validating) { 58 | return .canApplyOperator(firstOperator) 59 | } 60 | 61 | return .canApplyTransformation 62 | } 63 | 64 | func getFirstOperator(validating: Bool = false) -> MathOperator? { 65 | let cases = validating ? MathOperator.validationCases : MathOperator.evaluationCases 66 | return cases.first { string.contains($0.rawValue) } 67 | } 68 | } 69 | 70 | // MARK: - Functions 71 | 72 | extension MathFormula { 73 | func applyTransformation() -> Double { 74 | transformation(string) 75 | } 76 | 77 | func decompose(with mathOperator: MathOperator) -> [MathFormula] { 78 | var finalString = string 79 | if let _ = MathOperator.validConsecutiveOperatorsDuringEvaluation.first(where: { string.contains($0.key) }) { 80 | for (doubleOperator, combinedOperator) in MathOperator.validConsecutiveOperatorsDuringEvaluation { 81 | finalString = finalString.replacingOccurrences(of: doubleOperator, with: combinedOperator) 82 | } 83 | } 84 | return finalString.split(separator: mathOperator.character).map { 85 | MathFormula(validString: String($0), transformation: transformation) 86 | } 87 | } 88 | 89 | func dropingInitialValue() -> MathFormula { 90 | MathFormula( 91 | validString: String(string.dropFirst()), 92 | transformation: transformation 93 | ) 94 | } 95 | 96 | func evaluatingExpression(between bracket: MathBrackets) -> MathFormula { 97 | let stringWithBrackets = (try? getString(between: bracket)) ?? "" 98 | 99 | let value = MathExpression( 100 | MathFormula( 101 | validString: String(stringWithBrackets.dropFirst().dropLast()), 102 | transformation: transformation 103 | ) 104 | ).evaluate() 105 | return MathFormula( 106 | validString: string.replacingOccurrences(of: stringWithBrackets, with: value.avoidScientificNotation()), 107 | transformation: transformation 108 | ) 109 | } 110 | 111 | func replaceSubtractionByNegative() -> MathFormula { 112 | return MathFormula( 113 | validString: MathOperator.negative.rawValue + String(string.dropFirst()), 114 | transformation: transformation 115 | ) 116 | } 117 | } 118 | 119 | // MARK: - Private API 120 | 121 | private extension MathFormula { 122 | func containsBracket(_ bracket: MathBrackets) -> Bool { 123 | string.contains(bracket.opening) || string.contains(bracket.closing) 124 | } 125 | 126 | func getString(between bracket: MathBrackets) throws -> String { 127 | try String(string.map { $0 }.characters(between: bracket)) 128 | } 129 | 130 | func starts(with additiveOperator: AdditiveOperator) -> Bool { 131 | string.first == additiveOperator.character 132 | } 133 | 134 | func validate() throws { 135 | guard !string.isEmpty else { 136 | throw MathExpression.ValidationError.emptyExpression 137 | } 138 | 139 | try string.validateStartingAndEndingCharacters() 140 | try string.validateNoInvalidConsecutiveOperators() 141 | 142 | switch evaluationState(validating: true) { 143 | case .isNumeric: 144 | break 145 | case .startsWithSymbol(let symbol): 146 | switch symbol { 147 | case .sum: 148 | _ = try MathFormula(String(string.dropFirst()), transformation: transformation) 149 | case .subtraction: 150 | _ = try MathFormula(MathOperator.negative.rawValue + String(string.dropFirst()), transformation: transformation) 151 | } 152 | case .containsBracket(let brackets): 153 | let stringWithBrackets = try getString(between: brackets) 154 | _ = try MathFormula( 155 | String(stringWithBrackets.dropFirst().dropLast()), 156 | transformation: transformation 157 | ) 158 | _ = try MathFormula( 159 | string.replacingOccurrences(of: stringWithBrackets, with: "0"), 160 | transformation: transformation 161 | ) 162 | 163 | case .canApplyOperator(let mathOperator): 164 | _ = try string.split(separator: mathOperator.character).map { 165 | try MathFormula(String($0), transformation: transformation) 166 | } 167 | case .canApplyTransformation: 168 | break 169 | } 170 | } 171 | } 172 | 173 | // MARK: - Helper extensions 174 | 175 | private extension String { 176 | func trimmingWhiteSpacesAndDoubleMathOperators() -> String { 177 | var finalString = replacingOccurrences(of: " ", with: "") 178 | for (doubleOperator, combinedOperator) in MathOperator.validConsecutiveOperators { 179 | finalString = finalString.replacingOccurrences(of: doubleOperator, with: combinedOperator) 180 | } 181 | return finalString 182 | } 183 | 184 | func validateNoInvalidConsecutiveOperators() throws { 185 | for consecutiveOperators in MathOperator.invalidConsecutiveOperators { 186 | if let _ = range(of: consecutiveOperators) { 187 | throw MathExpression.ValidationError.invalidConsecutiveOperators(consecutiveOperators) 188 | } 189 | } 190 | } 191 | 192 | func validateStartingAndEndingCharacters() throws { 193 | guard let first = first, let last = last else { return } 194 | if MathOperator.multiplicativeOperators.map({ $0.rawValue }).contains(String(first)) { 195 | throw MathExpression.ValidationError.startsWithNonSumOrSubtractionOperator(String(first)) 196 | } 197 | 198 | if MathOperator.validationCases.map({ $0.rawValue }).contains(String(last)) { 199 | throw MathExpression.ValidationError.endsWithOperator(String(last)) 200 | } 201 | } 202 | } 203 | 204 | private extension Array where Element == String.Element { 205 | func characters(between brackets: MathBrackets) throws -> [Character] { 206 | guard let initialIndex = lastIndex(of: Character(brackets.opening)) else { 207 | throw MathExpression.ValidationError.unevenOpeningClosingBracketNumber 208 | } 209 | 210 | guard let finalIndex = self[initialIndex.. Int { 5 | guard self > 1 else { return 1 } 6 | return self * (self - 1).factorial() 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /MathExpressionExample/Models/MathTransformation.swift: -------------------------------------------------------------------------------- 1 | // Copyright © 2022 Pedro Daniel Prieto Martínez. Distributed under MIT License. 2 | 3 | enum MathTransformation: CaseIterable { 4 | case numericValueOrZero 5 | case count 6 | case factorial 7 | 8 | var function: (String) -> Double { 9 | switch self { 10 | case .count: 11 | return { Double($0.count) } 12 | case .factorial: 13 | return { 14 | guard $0.last == "!", let number = Int($0.dropLast()) else { return .zero } 15 | return Double(number.factorial()) 16 | } 17 | case .numericValueOrZero: 18 | return { Double($0) ?? .zero } 19 | } 20 | } 21 | 22 | var name: String { 23 | switch self { 24 | case .count: return "String count" 25 | case .factorial: return "Factorial (integers only)" 26 | case .numericValueOrZero: return "Numeric value or zero" 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /MathExpressionExample/Models/ValidationError.swift: -------------------------------------------------------------------------------- 1 | // Copyright © 2022 Pedro Daniel Prieto Martínez. Distributed under MIT License. 2 | 3 | struct ValidationError: Error { 4 | let description: String 5 | 6 | init(_ description: String) { 7 | self.description = description 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /MathExpressionExample/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /MathExpressionExample/Screens/Calculator/CalculatorView.swift: -------------------------------------------------------------------------------- 1 | // Copyright © 2022 Pedro Daniel Prieto Martínez. Distributed under MIT License. 2 | 3 | import SwiftUI 4 | 5 | struct CalculatorView: View { 6 | @StateObject var viewModel: CalculatorViewModel = .init() 7 | 8 | var body: some View { 9 | VStack(alignment: .trailing, spacing: 16) { 10 | Text(viewModel.expression) 11 | .font(.headline) 12 | .padding(.top, 24) 13 | Text(viewModel.operationResult) 14 | .font(.largeTitle) 15 | Spacer() 16 | CalculatorKeyboardView( 17 | clearButtonTitle: $viewModel.clearButtonTitle, 18 | lastKeyTapped: $viewModel.lastKeyTapped 19 | ) 20 | } 21 | .padding(.horizontal) 22 | .padding(.vertical, 24) 23 | .navigationTitle("Calculator") 24 | .navigationBarTitleDisplayMode(.inline) 25 | .alert("Validation error", isPresented: $viewModel.shouldShowError) { 26 | Button("Ok") { 27 | viewModel.reset() 28 | } 29 | } message: { 30 | Text(viewModel.operationError?.description ?? "") 31 | } 32 | } 33 | } 34 | 35 | struct CalculatorView_Previews: PreviewProvider { 36 | static var previews: some View { 37 | CalculatorView() 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /MathExpressionExample/Screens/Calculator/CalculatorViewModel.swift: -------------------------------------------------------------------------------- 1 | // Copyright © 2022 Pedro Daniel Prieto Martínez. Distributed under MIT License. 2 | 3 | import Foundation 4 | import MathExpression 5 | 6 | class CalculatorViewModel: ObservableObject { 7 | @Published var operationResult: String = "0" 8 | @Published var shouldShowError: Bool = false 9 | @Published var expression: String = "" 10 | @Published var clearButtonTitle: String = "AC" 11 | @Published var lastKeyTapped: String? { 12 | didSet { parseLastKeyTapped(lastKeyTapped) } 13 | } 14 | 15 | private(set) var operationError: ValidationError? 16 | private var shouldClearNewCharactersOnNextTap: Bool = true 17 | 18 | func reset() { 19 | shouldShowError = false 20 | expression = "" 21 | operationResult = "0" 22 | operationError = nil 23 | clearButtonTitle = "AC" 24 | shouldClearNewCharactersOnNextTap = true 25 | lastKeyTapped = nil 26 | } 27 | } 28 | 29 | private extension CalculatorViewModel { 30 | func parseLastKeyTapped(_ value: String?) { 31 | switch value { 32 | case "=": 33 | if !expression.isEmpty, expression.last != ")" { 34 | updateExpression(operationResult) 35 | } 36 | evaluateExpression() 37 | case "+/-": 38 | guard !operationResult.isEmpty, operationResult != "0" else { return } 39 | if operationResult.starts(with: "-") { 40 | operationResult = String(operationResult.dropFirst()) 41 | } else { 42 | operationResult = "-\(operationResult)" 43 | } 44 | case "0", "1", "2", "3", "4", "5", "6", "7", "8", "9": 45 | if operationResult == "0" || shouldClearNewCharactersOnNextTap { 46 | operationResult = value ?? "" 47 | clearButtonTitle = "C" 48 | shouldClearNewCharactersOnNextTap = false 49 | } else if expression.last == "=" { 50 | expression = "" 51 | } else { 52 | operationResult += (value ?? "") 53 | } 54 | case "+", "-", "*", "/": 55 | if shouldClearNewCharactersOnNextTap { 56 | expression = "" 57 | } 58 | if expression.last != ")", operationResult != "0" { 59 | updateExpression(operationResult) 60 | } 61 | updateExpression(value) 62 | shouldClearNewCharactersOnNextTap = true 63 | case "(", ")": 64 | if value == ")" { 65 | updateExpression(operationResult) 66 | } else { 67 | shouldClearNewCharactersOnNextTap = true 68 | } 69 | updateExpression(value) 70 | case "AC": 71 | operationResult = "0" 72 | expression = "" 73 | case "C": 74 | operationResult = "0" 75 | clearButtonTitle = "AC" 76 | case ".": 77 | operationResult += "." 78 | default: 79 | break 80 | } 81 | } 82 | 83 | func updateExpression(_ newCharacters: String?) { 84 | guard let newCharacters = newCharacters else { return } 85 | if !expression.isEmpty, expression.last != "(", newCharacters != ")" { 86 | expression += " \(newCharacters)" 87 | } else { 88 | expression += newCharacters 89 | } 90 | } 91 | 92 | func evaluateExpression() { 93 | do { 94 | let mathExpression = try MathExpression(expression, transformation: MathTransformation.numericValueOrZero.function) 95 | let result = Formatter.mathExpression.string(from: NSNumber(value: mathExpression.evaluate())) ?? "" 96 | operationResult = result 97 | updateExpression("=") 98 | shouldClearNewCharactersOnNextTap = true 99 | clearButtonTitle = "AC" 100 | } catch { 101 | guard let error = error as? MathExpression.ValidationError else { 102 | fatalError("[CalculatorViewModel] Expression \(expression) returned unknown error during validation!") 103 | } 104 | switch error { 105 | case .emptyExpression: 106 | operationError = ValidationError("The expression is empty!") 107 | case .misplacedBrackets: 108 | operationError = ValidationError("There is a bracket out of place.") 109 | case .unevenOpeningClosingBracketNumber: 110 | operationError = ValidationError("The number of opening and closing brackets is uneven!") 111 | case .invalidConsecutiveOperators(let value): 112 | operationError = ValidationError("The combination of operators \(value) is not valid.") 113 | case .startsWithNonSumOrSubtractionOperator(let value): 114 | operationError = ValidationError("The expression can not start with the \(value) operator.") 115 | case .endsWithOperator(let value): 116 | operationError = ValidationError("The expression can not end with the \(value) operator.") 117 | } 118 | shouldShowError = true 119 | } 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /MathExpressionExample/Screens/Calculator/Supporting views/CalculatorKeyboardView.swift: -------------------------------------------------------------------------------- 1 | // Copyright © 2022 Pedro Daniel Prieto Martínez. Distributed under MIT License. 2 | 3 | import SwiftUI 4 | 5 | struct CalculatorKeyboardView: View { 6 | @Binding var clearButtonTitle: String 7 | @Binding var lastKeyTapped: String? 8 | 9 | private var data: [String] { 10 | [ 11 | clearButtonTitle, "(", ")", "/", 12 | "7", "8", "9", "*", 13 | "4", "5", "6", "-", 14 | "1", "2", "3", "+", 15 | "0", ".", "+/-", "=" 16 | ] 17 | } 18 | 19 | private let columns: [GridItem] = [ 20 | .init(.flexible()), 21 | .init(.flexible()), 22 | .init(.flexible()), 23 | .init(.flexible()) 24 | ] 25 | 26 | var body: some View { 27 | LazyVGrid(columns: columns, spacing: 48) { 28 | ForEach(data, id: \.self) { item in 29 | Button { 30 | lastKeyTapped = item 31 | } label: { 32 | Text(item) 33 | .font(.title) 34 | } 35 | } 36 | } 37 | } 38 | } 39 | 40 | struct CalculatorKeyboardView_Previews: PreviewProvider { 41 | static var previews: some View { 42 | CalculatorKeyboardView(clearButtonTitle: .constant("C"), lastKeyTapped: .constant("")) 43 | .padding(.horizontal, 16) 44 | } 45 | } 46 | 47 | -------------------------------------------------------------------------------- /MathExpressionExample/Screens/Evaluator/EvaluatorView.swift: -------------------------------------------------------------------------------- 1 | // Copyright © 2022 Pedro Daniel Prieto Martínez. Distributed under MIT License. 2 | 3 | import SwiftUI 4 | 5 | struct EvaluatorView: View { 6 | @StateObject var viewModel: EvaluatorViewModel = .init() 7 | 8 | var body: some View { 9 | VStack(alignment: .leading, spacing: 20) { 10 | VStack(alignment: .leading, spacing: 12) { 11 | Text("Expression to evaluate") 12 | .font(.subheadline) 13 | ZStack(alignment: .trailing) { 14 | TextField("Type in a mathematical expression", text: $viewModel.expression) 15 | .textFieldStyle(.roundedBorder) 16 | .multilineTextAlignment(.leading) 17 | Button { 18 | viewModel.expression = "" 19 | } label: { 20 | Image(systemName: "xmark.circle") 21 | } 22 | .padding(.trailing, 4) 23 | .foregroundColor(.gray) 24 | 25 | } 26 | } 27 | Divider() 28 | VStack(alignment: .leading, spacing: 12) { 29 | Text("Transformation to apply") 30 | Picker("", selection: $viewModel.transformation) { 31 | ForEach(MathTransformation.allCases, id: \.name) { transformation in 32 | Text(transformation.name) 33 | .tag(transformation) 34 | } 35 | } 36 | .pickerStyle(.menu) 37 | } 38 | Divider() 39 | if !viewModel.operationResult.isEmpty { 40 | Text(viewModel.operationResult) 41 | } 42 | HStack { 43 | Spacer() 44 | Button("Evaluate expression!") { 45 | viewModel.evaluateExpression() 46 | } 47 | Spacer() 48 | } 49 | Spacer() 50 | } 51 | .navigationTitle("Evaluator") 52 | .navigationBarTitleDisplayMode(.inline) 53 | .padding() 54 | .alert("Validation error", isPresented: $viewModel.shouldShowError) { 55 | Button("Ok") { 56 | viewModel.shouldShowError = false 57 | } 58 | } message: { 59 | Text(viewModel.operationError?.description ?? "") 60 | } 61 | } 62 | } 63 | 64 | struct EvaluatorView_Previews: PreviewProvider { 65 | static var previews: some View { 66 | EvaluatorView() 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /MathExpressionExample/Screens/Evaluator/EvaluatorViewModel.swift: -------------------------------------------------------------------------------- 1 | // Copyright © 2022 Pedro Daniel Prieto Martínez. Distributed under MIT License. 2 | 3 | import Foundation 4 | import MathExpression 5 | 6 | class EvaluatorViewModel: ObservableObject { 7 | @Published var operationResult: String = "" 8 | @Published var shouldShowError: Bool = false 9 | 10 | @Published var expression: String = "" { 11 | didSet { operationResult = "" } 12 | } 13 | @Published var transformation: MathTransformation = .numericValueOrZero 14 | 15 | private(set) var operationError: ValidationError? 16 | 17 | func evaluateExpression() { 18 | do { 19 | let mathExpression = try MathExpression(expression, transformation: transformation.function) 20 | let result = Formatter.mathExpression.string(from: NSNumber(value: mathExpression.evaluate())) ?? "" 21 | operationResult = "The evaluation result is: \(result)" 22 | } catch { 23 | guard let error = error as? MathExpression.ValidationError else { 24 | fatalError("[EvaluatorViewModel] Expression \(expression) returned unknown error during validation!") 25 | } 26 | switch error { 27 | case .emptyExpression: 28 | operationError = ValidationError("The expression is empty!") 29 | case .misplacedBrackets: 30 | operationError = ValidationError("There is a bracket out of place.") 31 | case .unevenOpeningClosingBracketNumber: 32 | operationError = ValidationError("The number of opening and closing brackets is uneven!") 33 | case .invalidConsecutiveOperators(let value): 34 | operationError = ValidationError("The combination of operators \(value) is not valid.") 35 | case .startsWithNonSumOrSubtractionOperator(let value): 36 | operationError = ValidationError("The expression can not start with the \(value) operator.") 37 | case .endsWithOperator(let value): 38 | operationError = ValidationError("The expression can not end with the \(value) operator.") 39 | } 40 | shouldShowError = true 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /MathExpressionExample/Screens/MenuView.swift: -------------------------------------------------------------------------------- 1 | // Copyright © 2022 Pedro Daniel Prieto Martínez. Distributed under MIT License. 2 | 3 | import SwiftUI 4 | 5 | struct MenuView: View { 6 | var body: some View { 7 | NavigationView { 8 | VStack(alignment: .leading, spacing: 20) { 9 | Text("The calculator is a simple calculator, similar to the built-in calculator app in iOS, enabling simple mathematical computations to be performed.") 10 | .font(.callout) 11 | .padding(.horizontal) 12 | .multilineTextAlignment(.leading) 13 | NavigationLink(destination: CalculatorView()) { 14 | HStack { 15 | Spacer() 16 | Text("Go to Calculator!") 17 | .foregroundColor(.accentColor) 18 | Spacer() 19 | } 20 | } 21 | Divider() 22 | .padding(.horizontal) 23 | Text("The evaluator enables to compute more complicated expressions, also giving the opportunity to use some pre-built transformations to convert plain String instances to numeric values.") 24 | .font(.callout) 25 | .padding(.horizontal) 26 | .multilineTextAlignment(.leading) 27 | NavigationLink(destination: EvaluatorView()) { 28 | HStack { 29 | Spacer() 30 | Text("Go to Evaluator!") 31 | .foregroundColor(.accentColor) 32 | Spacer() 33 | } 34 | } 35 | Spacer() 36 | } 37 | .padding(.top, 8) 38 | .navigationTitle("Example App") 39 | } 40 | } 41 | } 42 | 43 | struct MenuView_Previews: PreviewProvider { 44 | static var previews: some View { 45 | MenuView() 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /MathExpressionPerformanceTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 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 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /MathExpressionPerformanceTests/Source/(Q - {0}, *) group tests/AbelianMultiplicativeGroupAxiomTests.swift: -------------------------------------------------------------------------------- 1 | // Copyright © 2019 Pedro Daniel Prieto Martínez. Distributed under MIT License. 2 | 3 | @testable import MathExpression 4 | import XCTest 5 | 6 | class AbelianMultiplicativeGroupAxiomPerformanceTests: XCTestCase { 7 | func testPerformanceNeutralElement() { 8 | let a = Int16.random() 9 | let neutralElement: Int16 = 1 10 | 11 | measure { 12 | let leftNeutralExpression = try! MathExpression(String(format: Formula.AbelianMultiplicativeGroupAxiomTests.neutralElement, neutralElement, a)) 13 | _ = leftNeutralExpression.evaluate() 14 | 15 | let rightNeutralExpression = try! MathExpression(String(format: Formula.AbelianMultiplicativeGroupAxiomTests.neutralElement, a, neutralElement)) 16 | _ = rightNeutralExpression.evaluate() 17 | } 18 | } 19 | 20 | func testPerformanceInverse() { 21 | let a = Int16.random() 22 | 23 | measure { 24 | let inverseExpression = try! MathExpression(String(format: Formula.AbelianMultiplicativeGroupAxiomTests.inverseElement, a)) 25 | _ = inverseExpression.evaluate() 26 | 27 | let leftProductWithInverse = try! MathExpression(String(format: Formula.AbelianMultiplicativeGroupAxiomTests.leftInverse, a, a)) 28 | _ = leftProductWithInverse.evaluate() 29 | 30 | let rightProductWithInverse = try! MathExpression(String(format: Formula.AbelianMultiplicativeGroupAxiomTests.rightInverse, a, a)) 31 | _ = rightProductWithInverse.evaluate() 32 | } 33 | } 34 | 35 | func testPerformanceAssociativity() { 36 | let a = Int16.random() 37 | let b = Int16.random() 38 | let c = Int16.random() 39 | 40 | measure { 41 | let leftAssociativityExpression = try! MathExpression(String(format: Formula.AbelianMultiplicativeGroupAxiomTests.leftAssociativity, a, b, c)) 42 | _ = leftAssociativityExpression.evaluate() 43 | 44 | let rightAssociativityExpression = try! MathExpression(String(format: Formula.AbelianMultiplicativeGroupAxiomTests.rightAssociativity, a, b, c)) 45 | _ = rightAssociativityExpression.evaluate() 46 | } 47 | } 48 | 49 | func testPerformanceCommutativity() { 50 | let a = Int16.random() 51 | let b = Int16.random() 52 | 53 | measure { 54 | let firstExpression = try! MathExpression(String(format: Formula.AbelianMultiplicativeGroupAxiomTests.commutativity, a, b)) 55 | _ = firstExpression.evaluate() 56 | 57 | let secondExpression = try! MathExpression(String(format: Formula.AbelianMultiplicativeGroupAxiomTests.commutativity, b, a)) 58 | _ = secondExpression.evaluate() 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /MathExpressionPerformanceTests/Source/(Q - {0}, *) group tests/DivisionPerformanceTests.swift: -------------------------------------------------------------------------------- 1 | // Copyright © 2019 Pedro Daniel Prieto Martínez. Distributed under MIT License. 2 | 3 | @testable import MathExpression 4 | import XCTest 5 | 6 | class DivisionPerformanceTests: XCTestCase { 7 | func testPerformanceSimpleDivision() { 8 | let a = Int16.random() 9 | let b = Int16.random() 10 | 11 | measure { 12 | let expression = try! MathExpression(String(format: Formula.DivisionTests.simpleDivision, a, b)) 13 | _ = expression.evaluate() 14 | } 15 | } 16 | 17 | func testPerformanceSimpleDivisionWithParentheses() { 18 | let a = Int16.random() 19 | let b = Int16.random() 20 | 21 | measure { 22 | let expression = try! MathExpression(String(format: Formula.DivisionTests.simpleDivisionWithParentheses, a, b)) 23 | _ = expression.evaluate() 24 | } 25 | } 26 | 27 | func testPerformanceSimpleDivisionWithParenthesesWithInitialDivision() { 28 | let a = Int16.random() 29 | let b = Int16.random() 30 | 31 | measure { 32 | let expression = try! MathExpression(String(format: Formula.DivisionTests.simpleDivisionWithParenthesesWithInitialDivision, a, b)) 33 | _ = expression.evaluate() 34 | } 35 | } 36 | 37 | func testPerformanceComplicatedDivision() { 38 | let a = Int16.random() 39 | let b = Int16.random() 40 | let c = Int16.random() 41 | 42 | measure { 43 | let expression = try! MathExpression(String(format: Formula.DivisionTests.complicatedDivision, a, b, c)) 44 | _ = expression.evaluate() 45 | } 46 | } 47 | 48 | func testPerformanceDivideSingleNumber() { 49 | let a = Int16.random() 50 | 51 | measure { 52 | let expression = try! MathExpression(String(format: Formula.DivisionTests.divideSingleNumber, a)) 53 | _ = expression.evaluate() 54 | } 55 | } 56 | 57 | func testPerformanceConsecutiveDivisionExpressionWithInitialDivision() { 58 | let a = Int16.random() 59 | let b = Int16.random() 60 | let c = Int16.random() 61 | 62 | measure { 63 | let expression = try! MathExpression(String(format: Formula.DivisionTests.consecutiveDivisionExpressionWithInitialDivision, a, b, c)) 64 | _ = expression.evaluate() 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /MathExpressionPerformanceTests/Source/(Q - {0}, *) group tests/ProductAndDivisionPerformanceTests.swift: -------------------------------------------------------------------------------- 1 | // Copyright © 2019 Pedro Daniel Prieto Martínez. Distributed under MIT License. 2 | 3 | @testable import MathExpression 4 | import XCTest 5 | 6 | class ProductAndDivisionPerformanceTests: XCTestCase { 7 | func testPerformanceSimpleOperation() { 8 | let a = Int16.random() 9 | let b = Int16.random() 10 | let c = Int16.random() 11 | 12 | measure { 13 | let expression = try! MathExpression(String(format: Formula.ProductAndDivisionTests.simpleOperation, a, b, c)) 14 | _ = expression.evaluate() 15 | } 16 | } 17 | 18 | func testPerformanceSimpleOperationWithParentheses() { 19 | let a = Int16.random() 20 | let b = Int16.random() 21 | let c = Int16.random() 22 | 23 | measure { 24 | let expression = try! MathExpression(String(format: Formula.ProductAndDivisionTests.simpleOperationWithParentheses, a, b, c)) 25 | _ = expression.evaluate() 26 | } 27 | } 28 | 29 | func testPerformanceSimpleOperationWithParenthesesAndInitialDivision() { 30 | let a = Int16.random() 31 | let b = Int16.random() 32 | let c = Int16.random() 33 | let d = Int16.random() 34 | 35 | measure { 36 | let expression = try! MathExpression(String(format: Formula.ProductAndDivisionTests.simpleOperationWithParenthesesAndInitialDivision, a, b, c, d)) 37 | _ = expression.evaluate() 38 | } 39 | } 40 | 41 | func testPerformanceComplicatedOperationWithParentheses() { 42 | let a = Int16.random() 43 | let b = Int16.random() 44 | let c = Int16.random() 45 | let d = Int16.random() 46 | let e = Int16.random() 47 | let f = Int16.random() 48 | 49 | measure { 50 | let expression = try! MathExpression(String(format: Formula.ProductAndDivisionTests.complicatedOperationWithParentheses, a, b, c, d, e, f)) 51 | _ = expression.evaluate() 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /MathExpressionPerformanceTests/Source/(Q - {0}, *) group tests/ProductPerformanceTests.swift: -------------------------------------------------------------------------------- 1 | // Copyright © 2019 Pedro Daniel Prieto Martínez. Distributed under MIT License. 2 | 3 | @testable import MathExpression 4 | import XCTest 5 | 6 | class ProductPerformanceTests: XCTestCase { 7 | func testPerformanceSimpleProduct() { 8 | let a = Int16.random() 9 | let b = Int16.random() 10 | 11 | measure { 12 | let expression = try! MathExpression(String(format: Formula.ProductTests.simpleProduct, a, b)) 13 | _ = expression.evaluate() 14 | } 15 | } 16 | 17 | func testPerformanceComplicatedProduct() { 18 | let a = Int16.random() 19 | let b = Int16.random() 20 | let c = Int16.random() 21 | let d = Int16.random() 22 | 23 | measure { 24 | let expression = try! MathExpression(String(format: Formula.ProductTests.complicatedProduct, a, b, c, d)) 25 | _ = expression.evaluate() 26 | } 27 | } 28 | 29 | func testPerformanceSimpleProductWithParentheses() { 30 | let a = Int16.random() 31 | let b = Int16.random() 32 | 33 | measure { 34 | let expression = try! MathExpression(String(format: Formula.ProductTests.simpleProductWithParentheses, a, b)) 35 | _ = expression.evaluate() 36 | } 37 | } 38 | 39 | func testPerformanceComplicatedAdditionWithSingleParenthesis() { 40 | let a = Int16.random() 41 | let b = Int16.random() 42 | let c = Int16.random() 43 | let d = Int16.random() 44 | 45 | measure { 46 | let expression = try! MathExpression(String(format: Formula.ProductTests.complicatedProductWithSingleParenthesis, a, b, c, d)) 47 | _ = expression.evaluate() 48 | } 49 | } 50 | 51 | func testPerformanceComplicatedProductWithMultipleParenthesis() { 52 | let a = Int16.random() 53 | let b = Int16.random() 54 | let c = Int16.random() 55 | let d = Int16.random() 56 | 57 | measure { 58 | let expression = try! MathExpression(String(format: Formula.ProductTests.complicatedProductWithMultipleParenthesis, a, b, c, d)) 59 | _ = expression.evaluate() 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /MathExpressionPerformanceTests/Source/(Q, +) group tests/AbelianAdditiveGroupAxiomsPerformanceTests.swift: -------------------------------------------------------------------------------- 1 | // Copyright © 2019 Pedro Daniel Prieto Martínez. Distributed under MIT License. 2 | 3 | @testable import MathExpression 4 | import XCTest 5 | 6 | class AbelianAdditiveGroupAxiomsPerformanceTests: XCTestCase { 7 | func testPerformanceNeutralElement() { 8 | let a = Int16.random() 9 | let neutralElement: Int16 = 0 10 | 11 | measure { 12 | let leftNeutralExpression = try! MathExpression(String(format: Formula.AbelianAdditiveGroupAxiomsTests.neutralElement, neutralElement, a)) 13 | _ = leftNeutralExpression.evaluate() 14 | 15 | let rightNeutralExpression = try! MathExpression(String(format: Formula.AbelianAdditiveGroupAxiomsTests.neutralElement, a, neutralElement)) 16 | _ = rightNeutralExpression.evaluate() 17 | } 18 | } 19 | 20 | func testPerformanceInverse() { 21 | let a = Int16.random() 22 | 23 | measure { 24 | let inverseExpression = try! MathExpression(String(format: Formula.AbelianAdditiveGroupAxiomsTests.inverseElement, a)) 25 | _ = inverseExpression.evaluate() 26 | 27 | let leftProductWithInverse = try! MathExpression(String(format: Formula.AbelianAdditiveGroupAxiomsTests.leftInverse, a, a)) 28 | _ = leftProductWithInverse.evaluate() 29 | 30 | let rightProductWithInverse = try! MathExpression(String(format: Formula.AbelianAdditiveGroupAxiomsTests.rightInverse, a, a)) 31 | _ = rightProductWithInverse.evaluate() 32 | } 33 | } 34 | 35 | func testPerformanceAssociativity() { 36 | let a = Int16.random() 37 | let b = Int16.random() 38 | let c = Int16.random() 39 | 40 | measure { 41 | let leftAssociativityExpression = try! MathExpression(String(format: Formula.AbelianAdditiveGroupAxiomsTests.leftAssociativity, a, b, c)) 42 | _ = leftAssociativityExpression.evaluate() 43 | 44 | let rightAssociativityExpression = try! MathExpression(String(format: Formula.AbelianAdditiveGroupAxiomsTests.rightAssociativity, a, b, c)) 45 | _ = rightAssociativityExpression.evaluate() 46 | } 47 | } 48 | 49 | func testPerformanceCommutativity() { 50 | let a = Int16.random() 51 | let b = Int16.random() 52 | 53 | measure { 54 | let firstExpression = try! MathExpression(String(format: Formula.AbelianAdditiveGroupAxiomsTests.commutativity, a, b)) 55 | _ = firstExpression.evaluate() 56 | 57 | let secondExpression = try! MathExpression(String(format: Formula.AbelianAdditiveGroupAxiomsTests.commutativity, b, a)) 58 | _ = secondExpression.evaluate() 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /MathExpressionPerformanceTests/Source/(Q, +) group tests/AdditionAndSubtractionPerformanceTests.swift: -------------------------------------------------------------------------------- 1 | // Copyright © 2019 Pedro Daniel Prieto Martínez. Distributed under MIT License. 2 | 3 | @testable import MathExpression 4 | import XCTest 5 | 6 | class AdditionAndSubtractionPerformanceTests: XCTestCase { 7 | func testPerformanceSimpleOperation() { 8 | let a = Int16.random() 9 | let b = Int16.random() 10 | let c = Int16.random() 11 | 12 | measure { 13 | let expression = try! MathExpression(String(format: Formula.AdditionAndSubtractionTests.simpleOperation, a, b, c)) 14 | _ = expression.evaluate() 15 | } 16 | } 17 | 18 | func testPerformanceSimpleOperationWithParentheses() { 19 | let a = Int16.random() 20 | let b = Int16.random() 21 | let c = Int16.random() 22 | 23 | measure { 24 | let expression = try! MathExpression(String(format: Formula.AdditionAndSubtractionTests.simpleOperationWithParentheses, a, b, c)) 25 | _ = expression.evaluate() 26 | } 27 | } 28 | 29 | func testPerformanceSimpleOperationWithParenthesesAndInitialMinus() { 30 | let a = Int16.random() 31 | let b = Int16.random() 32 | let c = Int16.random() 33 | let d = Int16.random() 34 | 35 | measure { 36 | let expression = try! MathExpression(String(format: Formula.AdditionAndSubtractionTests.simpleOperationWithParenthesesAndInitialMinus, a, b, c, d)) 37 | _ = expression.evaluate() 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /MathExpressionPerformanceTests/Source/(Q, +) group tests/AdditionPerformanceTests.swift: -------------------------------------------------------------------------------- 1 | // Copyright © 2019 Pedro Daniel Prieto Martínez. Distributed under MIT License. 2 | 3 | @testable import MathExpression 4 | import XCTest 5 | 6 | class AdditionPerformanceTests: XCTestCase { 7 | func testPerformanceSimpleAddition() { 8 | let a = Int16.random() 9 | let b = Int16.random() 10 | 11 | measure { 12 | let expression = try! MathExpression(String(format: Formula.AdditionTests.simpleAddition, a, b)) 13 | _ = expression.evaluate() 14 | } 15 | } 16 | 17 | func testPerformanceComplicatedAddition() { 18 | let a = Int16.random() 19 | let b = Int16.random() 20 | let c = Int16.random() 21 | let d = Int16.random() 22 | 23 | measure { 24 | let expression = try! MathExpression(String(format: Formula.AdditionTests.complicatedAddition, a, b, c, d)) 25 | _ = expression.evaluate() 26 | } 27 | } 28 | 29 | func testPerformanceSimpleAdditionWithParentheses() { 30 | let a = Int16.random() 31 | let b = Int16.random() 32 | 33 | measure { 34 | let expression = try! MathExpression(String(format: Formula.AdditionTests.simpleAdditionWithParentheses, a, b)) 35 | _ = expression.evaluate() 36 | } 37 | } 38 | 39 | func testPerformanceComplicatedAdditionWithSingleParenthesis() { 40 | let a = Int16.random() 41 | let b = Int16.random() 42 | let c = Int16.random() 43 | let d = Int16.random() 44 | 45 | measure { 46 | let expression = try! MathExpression(String(format: Formula.AdditionTests.complicatedAdditionWithSingleParenthesis, a, b, c, d)) 47 | _ = expression.evaluate() 48 | } 49 | } 50 | 51 | func testPerformanceComplicatedAdditionWithMultipleParenthesis() { 52 | let a = Int16.random() 53 | let b = Int16.random() 54 | let c = Int16.random() 55 | let d = Int16.random() 56 | let e = Int16.random() 57 | let f = Int16.random() 58 | 59 | measure { 60 | let expression = try! MathExpression(String(format: Formula.AdditionTests.complicatedAdditionWithMultipleParenthesis, a, b, c, d, e, f)) 61 | _ = expression.evaluate() 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /MathExpressionPerformanceTests/Source/(Q, +) group tests/SubtractionPerformanceTests.swift: -------------------------------------------------------------------------------- 1 | // Copyright © 2019 Pedro Daniel Prieto Martínez. Distributed under MIT License. 2 | 3 | @testable import MathExpression 4 | import XCTest 5 | 6 | class SubtractionPerformanceTests: XCTestCase { 7 | func testPerformanceSimpleSubtraction() { 8 | let a = Int16.random() 9 | let b = Int16.random() 10 | 11 | measure { 12 | let expression = try! MathExpression(String(format: Formula.SubtractionTests.simpleSubtraction, a, b)) 13 | _ = expression.evaluate() 14 | } 15 | } 16 | 17 | func testPerformanceSimpleSubtractionWithParentheses() { 18 | let a = Int16.random() 19 | let b = Int16.random() 20 | 21 | measure { 22 | let expression = try! MathExpression(String(format: Formula.SubtractionTests.simpleSubtractionWithParentheses, a, b)) 23 | _ = expression.evaluate() 24 | } 25 | } 26 | 27 | func testPerformanceSimpleSubtractionWithParenthesesWithInitialMinus() { 28 | let a = Int16.random() 29 | let b = Int16.random() 30 | 31 | measure { 32 | let expression = try! MathExpression(String(format: Formula.SubtractionTests.simpleSubtractionWithParenthesesWithInitialMinus, a, b)) 33 | _ = expression.evaluate() 34 | } 35 | } 36 | 37 | func testPerformanceComplicatedSubtraction() { 38 | let a = Int16.random() 39 | let b = Int16.random() 40 | let c = Int16.random() 41 | 42 | measure { 43 | let expression = try! MathExpression(String(format: Formula.SubtractionTests.complicatedSubtraction, a, b, c)) 44 | _ = expression.evaluate() 45 | } 46 | } 47 | 48 | func testPerformanceNegativeSingleNumber() { 49 | let a = Int16.random() 50 | 51 | measure { 52 | let expression = try! MathExpression(String(format: Formula.SubtractionTests.negativeSingleNumber, a)) 53 | _ = expression.evaluate() 54 | } 55 | } 56 | 57 | func testPerformanceNegativeExpression() { 58 | let a = Int16.random() 59 | let b = Int16.random() 60 | let c = Int16.random() 61 | 62 | measure { 63 | let expression = try! MathExpression(String(format: Formula.SubtractionTests.negativeExpressionWithInitialMinus, a, b, c)) 64 | _ = expression.evaluate() 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /MathExpressionPerformanceTests/Source/(Q, +, *) field tests/CombinedOperationsPerformanceTest.swift: -------------------------------------------------------------------------------- 1 | // Copyright © 2019 Pedro Daniel Prieto Martínez. Distributed under MIT License. 2 | 3 | @testable import MathExpression 4 | import XCTest 5 | 6 | class CombinedOperationsPerformanceTests: XCTestCase { 7 | func testPerformanceProductOfAddition() { 8 | let a = Int16.random() 9 | let b = Int16.random() 10 | let c = Int16.random() 11 | let d = Int16.random() 12 | 13 | measure { 14 | let expression = try! MathExpression(String(format: Formula.CombinedOperationsTests.productOfAddition, a, b, c, d)) 15 | _ = expression.evaluate() 16 | } 17 | } 18 | 19 | func testPerformanceSquareOfAddition() { 20 | let a = Int16.random() 21 | let b = Int16.random() 22 | 23 | measure { 24 | let expression = try! MathExpression(String(format: Formula.CombinedOperationsTests.productOfAddition, a, b, a, b)) 25 | _ = expression.evaluate() 26 | } 27 | } 28 | 29 | func testPerformanceProductOfSubtraction() { 30 | let a = Int16.random() 31 | let b = Int16.random() 32 | let c = Int16.random() 33 | let d = Int16.random() 34 | 35 | measure { 36 | let expression = try! MathExpression(String(format: Formula.CombinedOperationsTests.productOfSubtraction, a, b, c, d)) 37 | _ = expression.evaluate() 38 | } 39 | } 40 | 41 | func testPerformanceSquareOfSubtraction() { 42 | let a = Int16.random() 43 | let b = Int16.random() 44 | 45 | measure { 46 | let expression = try! MathExpression(String(format: Formula.CombinedOperationsTests.productOfSubtraction, a, b, a, b)) 47 | _ = expression.evaluate() 48 | } 49 | } 50 | 51 | func testPerformanceProductOfAdditionWithSubtraction() { 52 | let a = Int16.random() 53 | let b = Int16.random() 54 | let c = Int16.random() 55 | let d = Int16.randomExcluding(c) 56 | 57 | measure { 58 | let expression = try! MathExpression(String(format: Formula.CombinedOperationsTests.productOfAdditionWithSubtraction, a, b, c, d)) 59 | _ = expression.evaluate() 60 | } 61 | } 62 | 63 | func testPerformanceProductOfAdditionWithSubtractionIdentity() { 64 | let a = Int16.random() 65 | let b = Int16.random() 66 | 67 | measure { 68 | let expression = try! MathExpression(String(format: Formula.CombinedOperationsTests.productOfAdditionWithSubtraction, a, b, a, b)) 69 | _ = expression.evaluate() 70 | } 71 | } 72 | 73 | func testPerformanceDivisionOfAdditionWithSubtraction() { 74 | let a = Int16.random() 75 | let b = Int16.random() 76 | let c = Int16.random() 77 | let d = Int16.randomExcluding(c) 78 | 79 | measure { 80 | let expression = try! MathExpression(String(format: Formula.CombinedOperationsTests.divisionOfAdditionWithSubtraction, a, b, c, d)) 81 | _ = expression.evaluate() 82 | } 83 | } 84 | 85 | func testPerformanceDivisionOfProductOfAdditionsWithProductOfSubtractions() { 86 | let a = Int16.random() 87 | let b = Int16.random() 88 | let c = Int16.random() 89 | let d = Int16.random() 90 | let e = Int16.random() 91 | let f = Int16.randomExcluding(e) 92 | let g = Int16.random() 93 | let h = Int16.randomExcluding(g) 94 | 95 | measure { 96 | let expression = try! MathExpression(String(format: Formula.CombinedOperationsTests.divisionOfProductOfAdditionsWithProductOfSubtractions, a, b, c, d, e, f, g, h)) 97 | _ = expression.evaluate() 98 | } 99 | } 100 | 101 | func testPerformanceDivisionOfCrossedProducts() { 102 | let a = Int16.random() 103 | let b = Int16.random() 104 | let c = Int16.random() 105 | let d = Int16.random() 106 | let e = Int16.random() 107 | let f = Int16.random() 108 | let g = Int16.random() 109 | let h = Int16.randomExcluding(g) 110 | 111 | measure { 112 | let expression = try! MathExpression(String(format: Formula.CombinedOperationsTests.divisionOfCrossedProducts, a, b, c, d, e, f, g, h)) 113 | _ = expression.evaluate() 114 | } 115 | } 116 | 117 | func testPerformanceAdditionWithProductNoParentheses() { 118 | let a = Int16.random() 119 | let b = Int16.random() 120 | let c = Int16.random() 121 | 122 | measure { 123 | let expression = try! MathExpression(String(format: Formula.CombinedOperationsTests.additionWithProductNoParentheses, a, b, c)) 124 | _ = expression.evaluate() 125 | } 126 | } 127 | 128 | func testPerformanceAdditionWithDivisionNoParentheses() { 129 | let a = Int16.random() 130 | let b = Int16.random() 131 | let c = Int16.random() 132 | 133 | measure { 134 | let expression = try! MathExpression(String(format: Formula.CombinedOperationsTests.additionWithDivisionNoParentheses, a, b, c)) 135 | _ = expression.evaluate() 136 | } 137 | } 138 | 139 | func testPerformanceSubtractionWithProductNoParentheses() { 140 | let a = Int16.random() 141 | let b = Int16.random() 142 | let c = Int16.random() 143 | 144 | measure { 145 | let expression = try! MathExpression(String(format: Formula.CombinedOperationsTests.subtractionWithProductNoParentheses, a, b, c)) 146 | _ = expression.evaluate() 147 | } 148 | } 149 | 150 | func testPerformanceSubtractionWithDivisionNoParentheses() { 151 | let a = Int16.random() 152 | let b = Int16.random() 153 | let c = Int16.random() 154 | 155 | measure { 156 | let expression = try! MathExpression(String(format: Formula.CombinedOperationsTests.subtractionWithDivisionNoParentheses, a, b, c)) 157 | _ = expression.evaluate() 158 | } 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /MathExpressionPerformanceTests/Source/(Q, +, *) field tests/FieldAxiomPerformanceTests.swift: -------------------------------------------------------------------------------- 1 | // Copyright © 2019 Pedro Daniel Prieto Martínez. Distributed under MIT License. 2 | 3 | @testable import MathExpression 4 | import XCTest 5 | 6 | class FieldAxiomPerformanceTests: XCTestCase { 7 | func testPerformanceDistributivityOfProductOverAddition() { 8 | let a = Int16.random() 9 | let b = Int16.random() 10 | let c = Int16.random() 11 | 12 | measure { 13 | let leftDistributivityFactoredExpression = try! MathExpression(String(format: Formula.FieldAxiomTests.leftDistributivityFactored, a, b, c)) 14 | _ = leftDistributivityFactoredExpression.evaluate() 15 | 16 | let rightDistributivityFactoredExpression = try! MathExpression(String(format: Formula.FieldAxiomTests.rightDistributivityFactored, a, b, c)) 17 | _ = rightDistributivityFactoredExpression.evaluate() 18 | 19 | let distributivityExpandedExpression = try! MathExpression(String(format: Formula.FieldAxiomTests.distributivityExpanded, a, c, b, c)) 20 | _ = distributivityExpandedExpression.evaluate() 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /MathExpressionPerformanceTests/Source/Non-trivial transformation tests/CountTransformationPerformanceTests.swift: -------------------------------------------------------------------------------- 1 | // Copyright © 2019 Pedro Daniel Prieto Martínez. Distributed under MIT License. 2 | 3 | @testable import MathExpression 4 | import XCTest 5 | 6 | class CountTransformationPerformanceTests: XCTestCase { 7 | let transformation: (String) -> Double = { Double($0.count) } 8 | 9 | func testPerformanceProductOfAddition() { 10 | let a = Int16.random() 11 | let b = String.random(length: Int.random(in: 5...20)) 12 | let c = Int16.random() 13 | let d = String.random(length: Int.random(in: 5...20)) 14 | let formula = String(format: Formula.TransformationTests.productOfAddition, a, b, c, d) 15 | 16 | measure { 17 | let expression = try! MathExpression(formula, transformation: transformation) 18 | _ = expression.evaluate() 19 | } 20 | } 21 | 22 | func testPerformanceSquareOfAddition() { 23 | let a = Int16.random() 24 | let b = String.random(length: Int.random(in: 5...20)) 25 | let formula = String(format: Formula.TransformationTests.productOfAddition, a, b, a, b) 26 | 27 | measure { 28 | let expression = try! MathExpression(formula, transformation: transformation) 29 | _ = expression.evaluate() 30 | } 31 | } 32 | 33 | func testPerformanceProductOfSubtraction() { 34 | let a = Int16.random() 35 | let b = String.random(length: Int.random(in: 5...20)) 36 | let c = Int16.random() 37 | let d = String.random(length: Int.random(in: 5...20)) 38 | let formula = String(format: Formula.TransformationTests.productOfSubtraction, a, b, c, d) 39 | 40 | measure { 41 | let expression = try! MathExpression(formula, transformation: transformation) 42 | _ = expression.evaluate() 43 | } 44 | } 45 | 46 | func testPerformanceSquareOfSubtraction() { 47 | let a = Int16.random() 48 | let b = String.random(length: Int.random(in: 5...20)) 49 | let formula = String(format: Formula.TransformationTests.productOfSubtraction, a, b, a, b) 50 | 51 | measure { 52 | let expression = try! MathExpression(formula, transformation: transformation) 53 | _ = expression.evaluate() 54 | } 55 | } 56 | 57 | func testPerformanceProductOfAdditionWithSubtraction() { 58 | let a = Int16.random() 59 | let b = String.random(length: Int.random(in: 5...20)) 60 | let c = Int16.random() 61 | let d = String.random(length: Int.random(in: 5...20)) 62 | let formula = String(format: Formula.TransformationTests.productOfAdditionWithSubtraction, a, b, c, d) 63 | 64 | measure { 65 | let expression = try! MathExpression(formula, transformation: transformation) 66 | _ = expression.evaluate() 67 | } 68 | } 69 | 70 | func testPerformanceProductOfAdditionWithSubtractionIdentity() { 71 | let a = Int16.random() 72 | let b = String.random(length: Int.random(in: 5...20)) 73 | let formula = String(format: Formula.TransformationTests.productOfAdditionWithSubtraction, a, b, a, b) 74 | 75 | measure { 76 | let expression = try! MathExpression(formula, transformation: transformation) 77 | _ = expression.evaluate() 78 | } 79 | } 80 | 81 | func testPerformanceDivisionOfAdditionWithSubtraction() { 82 | let a = Int16.random() 83 | let b = String.random(length: Int.random(in: 5...20)) 84 | let c = Int16.random() 85 | let d = String.random(length: Int.random(in: 5...20)) 86 | let formula = String(format: Formula.TransformationTests.divisionOfAdditionWithSubtraction, a, b, c, d) 87 | 88 | measure { 89 | let expression = try! MathExpression(formula, transformation: transformation) 90 | _ = expression.evaluate() 91 | } 92 | } 93 | 94 | func testPerformanceDivisionOfProductOfAdditionsWithProductOfSubtractions() { 95 | let a = Int16.random() 96 | let b = String.random(length: Int.random(in: 5...20)) 97 | let c = Int16.random() 98 | let d = String.random(length: Int.random(in: 5...20)) 99 | let e = Int16.random() 100 | let f = String.random(length: Int.random(in: 5...20)) 101 | let g = Int16.random() 102 | let h = String.random(length: Int.random(in: 5...20)) 103 | let formula = String(format: Formula.TransformationTests.divisionOfProductOfAdditionsWithProductOfSubtractions, a, b, c, d, e, f, g, h) 104 | 105 | measure { 106 | let expression = try! MathExpression(formula, transformation: transformation) 107 | _ = expression.evaluate() 108 | } 109 | } 110 | 111 | func testPerformanceDivisionOfCrossedProducts() { 112 | let a = Int16.random() 113 | let b = String.random(length: Int.random(in: 5...20)) 114 | let c = Int16.random() 115 | let d = String.random(length: Int.random(in: 5...20)) 116 | let e = Int16.random() 117 | let f = String.random(length: Int.random(in: 5...20)) 118 | let g = Int16.random() 119 | let h = String.random(length: Int.random(in: 5...20)) 120 | let formula = String(format: Formula.TransformationTests.divisionOfCrossedProducts, a, b, c, d, e, f, g, h) 121 | 122 | measure { 123 | let expression = try! MathExpression(formula, transformation: transformation) 124 | _ = expression.evaluate() 125 | } 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /MathExpressionPerformanceTests/Source/Non-trivial transformation tests/FactorialTransformationPerformanceTests.swift: -------------------------------------------------------------------------------- 1 | // Copyright © 2019 Pedro Daniel Prieto Martínez. Distributed under MIT License. 2 | 3 | @testable import MathExpression 4 | import XCTest 5 | 6 | class FactorialTransformationPerformanceTests: XCTestCase { 7 | let transformation: (String) -> Double = { string in 8 | guard string.last == "!", var number = Int(string.dropLast()) else { return .zero } 9 | return Double(number.factorial()) 10 | } 11 | 12 | func testPerformanceProductOfAddition() { 13 | let a = Int16.random() 14 | let b = Int.random(in: 0...10) 15 | let c = Int16.random() 16 | let d = Int.random(in: 0...10) 17 | let formula = String(format: Formula.TransformationTests.productOfAddition, a, "\(b)!", c, "\(d)!") 18 | 19 | measure { 20 | let expression = try! MathExpression(formula, transformation: transformation) 21 | _ = expression.evaluate() 22 | } 23 | } 24 | 25 | func testPerformanceSquareOfAddition() { 26 | let a = Int16.random() 27 | let b = Int.random(in: 0...10) 28 | let formula = String(format: Formula.TransformationTests.productOfAddition, a, "\(b)!", a, "\(b)!") 29 | 30 | measure { 31 | let expression = try! MathExpression(formula, transformation: transformation) 32 | _ = expression.evaluate() 33 | } 34 | } 35 | 36 | func testPerformanceProductOfSubtraction() { 37 | let a = Int16.random() 38 | let b = Int.random(in: 0...10) 39 | let c = Int16.random() 40 | let d = Int.random(in: 0...10) 41 | let formula = String(format: Formula.TransformationTests.productOfSubtraction, a, "\(b)!", c, "\(d)!") 42 | 43 | measure { 44 | let expression = try! MathExpression(formula, transformation: transformation) 45 | _ = expression.evaluate() 46 | } 47 | } 48 | 49 | func testPerformanceSquareOfSubtraction() { 50 | let a = Int16.random() 51 | let b = Int.random(in: 0...10) 52 | let formula = String(format: Formula.TransformationTests.productOfSubtraction, a, "\(b)!", a, "\(b)!") 53 | 54 | measure { 55 | let expression = try! MathExpression(formula, transformation: transformation) 56 | _ = expression.evaluate() 57 | } 58 | } 59 | 60 | func testPerformanceProductOfAdditionWithSubtraction() { 61 | let a = Int16.random() 62 | let b = Int.random(in: 0...10) 63 | let c = Int16.random() 64 | let d = Int.random(in: 0...10) 65 | let formula = String(format: Formula.TransformationTests.productOfAdditionWithSubtraction, a, "\(b)!", c, "\(d)!") 66 | 67 | measure { 68 | let expression = try! MathExpression(formula, transformation: transformation) 69 | _ = expression.evaluate() 70 | } 71 | } 72 | 73 | func testPerformanceProductOfAdditionWithSubtractionIdentity() { 74 | let a = Int16.random() 75 | let b = Int.random(in: 0...10) 76 | let formula = String(format: Formula.TransformationTests.productOfAdditionWithSubtraction, a, "\(b)!", a, "\(b)!") 77 | 78 | measure { 79 | let expression = try! MathExpression(formula, transformation: transformation) 80 | _ = expression.evaluate() 81 | } 82 | } 83 | 84 | func testPerformanceDivisionOfAdditionWithSubtraction() { 85 | let a = Int16.random() 86 | let b = Int.random(in: 0...10) 87 | let c = Int16.random() 88 | let d = Int.random(in: 0...10) 89 | let formula = String(format: Formula.TransformationTests.divisionOfAdditionWithSubtraction, a, "\(b)!", c, "\(d)!") 90 | 91 | measure { 92 | let expression = try! MathExpression(formula, transformation: transformation) 93 | _ = expression.evaluate() 94 | } 95 | } 96 | 97 | func testPerformanceDivisionOfProductOfAdditionsWithProductOfSubtractions() { 98 | let a = Int16.random() 99 | let b = Int.random(in: 0...10) 100 | let c = Int16.random() 101 | let d = Int.random(in: 0...10) 102 | let e = Int16.random() 103 | let f = Int.random(in: 0...10) 104 | let g = Int16.random() 105 | let h = Int.random(in: 0...10) 106 | let formula = String(format: Formula.TransformationTests.divisionOfProductOfAdditionsWithProductOfSubtractions, a, "\(b)!", c, "\(d)!", e, "\(f)!", g, "\(h)!") 107 | 108 | measure { 109 | let expression = try! MathExpression(formula, transformation: transformation) 110 | _ = expression.evaluate() 111 | } 112 | } 113 | 114 | func testPerformanceDivisionOfCrossedProducts() { 115 | let a = Int16.random() 116 | let b = Int.random(in: 0...10) 117 | let c = Int16.random() 118 | let d = Int.random(in: 0...10) 119 | let e = Int16.random() 120 | let f = Int.random(in: 0...10) 121 | let g = Int16.random() 122 | let h = Int.random(in: 0...10) 123 | let formula = String(format: Formula.TransformationTests.divisionOfCrossedProducts, a, "\(b)!", c, "\(d)!", e, "\(f)!", g, "\(h)!") 124 | 125 | measure { 126 | let expression = try! MathExpression(formula, transformation: transformation) 127 | _ = expression.evaluate() 128 | } 129 | } 130 | } 131 | 132 | private extension Int { 133 | func factorial() -> Int { 134 | guard self > 1 else { return 1 } 135 | return self * (self - 1).factorial() 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /MathExpressionPerformanceTests/Source/Stress tests/StressPerformanceTests.swift: -------------------------------------------------------------------------------- 1 | // Copyright © 2019 Pedro Daniel Prieto Martínez. Distributed under MIT License. 2 | 3 | @testable import MathExpression 4 | import XCTest 5 | 6 | class StressPerformanceTests: XCTestCase { 7 | func testPerformanceFullParenthesesSingleValue() { 8 | let numberOfParentheses = 500 9 | var expression = String(repeating: Character(MathBrackets.parenthesis.opening), count: numberOfParentheses) 10 | expression += "10" 11 | expression += String(repeating: Character(MathBrackets.parenthesis.closing), count: numberOfParentheses) 12 | 13 | measure { 14 | let mathExpression = try! MathExpression(expression) 15 | _ = mathExpression.evaluate() 16 | } 17 | } 18 | 19 | func testPerformanceFullParenthesesSumAndProduct() { 20 | let numberOfParentheses = 500 21 | var expression = "" 22 | for _ in 0.. Double { 10 | let divisor = pow(10.0, Double(places)) 11 | return (self * divisor).rounded(roundingRule) / divisor 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /MathExpressionTestHelpers/Extensions/Numeric+Random.swift: -------------------------------------------------------------------------------- 1 | // Copyright © 2019 Pedro Daniel Prieto Martínez. Distributed under MIT License. 2 | 3 | extension Int16 { 4 | static func random() -> Int16 { 5 | Int16.random(in: Int16.min...Int16.max) 6 | } 7 | 8 | static func randomEven(in range: ClosedRange) -> Int16 { 9 | let randomValue = random(in: range) 10 | if randomValue.quotientAndRemainder(dividingBy: 2).remainder == 0 { 11 | return randomValue 12 | } else if randomValue > 0 { 13 | return randomValue - 1 14 | } else { 15 | return randomValue + 1 16 | } 17 | } 18 | 19 | static func randomOdd(in range: ClosedRange) -> Int16 { 20 | let evenNumber = randomEven(in: range) 21 | return evenNumber > .zero ? evenNumber - 1 : evenNumber + 1 22 | } 23 | 24 | static func randomExcludingZero() -> Int16 { 25 | randomExcluding(.zero) 26 | } 27 | 28 | static func randomExcluding(_ excluded: Int16) -> Int16 { 29 | let randomValue = random() 30 | return randomValue == excluded ? randomExcluding(excluded) : randomValue 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /MathExpressionTestHelpers/Extensions/String+Random.swift: -------------------------------------------------------------------------------- 1 | // Copyright © 2019 Pedro Daniel Prieto Martínez. Distributed under MIT License. 2 | 3 | extension String { 4 | static func random(length: Int) -> String { 5 | let letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" 6 | return String((0..( 7 | _ expression: @autoclosure () throws -> T, 8 | throws error: E, 9 | in file: StaticString = #file, 10 | line: UInt = #line 11 | ) { 12 | var thrownError: Error? 13 | 14 | XCTAssertThrowsError(try expression(), file: file, line: line) { error in 15 | thrownError = error 16 | } 17 | 18 | XCTAssertTrue(thrownError is E, "Unexpected error type: \(type(of: thrownError))", file: file, line: line) 19 | XCTAssertEqual(thrownError as? E, error, file: file, line: line) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /MathExpressionTestHelpers/Helpers/Formulae.swift: -------------------------------------------------------------------------------- 1 | // Copyright © 2019 Pedro Daniel Prieto Martínez. Distributed under MIT License. 2 | 3 | enum Formula { 4 | enum AbelianAdditiveGroupAxiomsTests { 5 | static let neutralElement = "%d + %d" 6 | static let inverseElement = "- %d" 7 | static let leftInverse = "- %d + %d" 8 | static let rightInverse = "%d + (- %d)" 9 | static let leftAssociativity = "(%d + %d) + %d" 10 | static let rightAssociativity = "%d + (%d + %d)" 11 | static let commutativity = "%d + %d" 12 | } 13 | 14 | enum AbelianMultiplicativeGroupAxiomTests { 15 | static let neutralElement = "%d * %d" 16 | static let inverseElement = "1 / %d" 17 | static let leftInverse = "(1 / %d) * %d" 18 | static let rightInverse = "%d * (1 / %d)" 19 | static let leftAssociativity = "(%d * %d) * %d" 20 | static let rightAssociativity = "%d * (%d * %d)" 21 | static let commutativity = "%d * %d" 22 | } 23 | 24 | enum AdditionAndSubtractionTests { 25 | static let simpleOperation = "%d - %d + %d" 26 | static let simpleOperationWithParentheses = "%d - (-%d + %d)" 27 | static let simpleOperationWithParenthesesAndInitialMinus = "- (%d - %d) - %d + %d" 28 | } 29 | 30 | enum AdditionTests { 31 | static let simpleAddition = "%d + %d" 32 | static let complicatedAddition = "%d + %d + %d + %d" 33 | static let simpleAdditionWithParentheses = "(%d + %d)" 34 | static let complicatedAdditionWithSingleParenthesis = "%d + (%d + %d) + %d" 35 | static let complicatedAdditionWithMultipleParenthesis = "(%d + %d) + %d + ((%d + %d) + %d)" 36 | } 37 | 38 | enum CombinedOperationsTests { 39 | static let productOfAddition = "(%d + %d) * (%d + %d)" 40 | static let productOfSubtraction = "(%d - %d) * (%d - %d)" 41 | static let productOfAdditionWithSubtraction = "(%d + %d) * (%d - %d)" 42 | static let divisionOfAdditionWithSubtraction = "(%d + %d) / (%d - %d)" 43 | static let divisionOfProductOfAdditionsWithProductOfSubtractions = "(%d + %d) * (%d + %d) / ((%d - %d) * (%d - %d))" 44 | static let divisionOfCrossedProducts = "(%d + %d) * (%d - %d) / ((%d + %d) * (%d - %d))" 45 | static let additionWithProductNoParentheses = "%d + %d * %d" 46 | static let additionWithDivisionNoParentheses = "%d + %d / %d" 47 | static let subtractionWithProductNoParentheses = "%d * %d - %d" 48 | static let subtractionWithDivisionNoParentheses = "%d - %d / %d" 49 | } 50 | 51 | enum DivisionTests { 52 | static let simpleDivision = "%d / %d" 53 | static let simpleDivisionWithParentheses = "(%d / %d)" 54 | static let simpleDivisionWithParenthesesWithInitialDivision = "1 / (%d / %d)" 55 | static let complicatedDivision = "%d / %d / %d" 56 | static let divideSingleNumber = "1 / %d" 57 | static let consecutiveDivisionExpressionWithInitialDivision = "1 / %d / %d / %d" 58 | } 59 | 60 | enum FieldAxiomTests { 61 | static let leftDistributivityFactored = "%d * (%d + %d)" 62 | static let rightDistributivityFactored = "(%d + %d) * %d" 63 | static let distributivityExpanded = "(%d * %d) + (%d * %d)" 64 | } 65 | 66 | enum ProductAndDivisionTests { 67 | static let simpleOperation = "%d / %d * %d" 68 | static let simpleOperationWithParentheses = "%d / (1 / %d * %d)" 69 | static let simpleOperationWithParenthesesAndInitialDivision = "1 / (%d / %d) / %d * %d" 70 | static let complicatedOperationWithParentheses = "(1 / %d * (%d / %d) * %d) / (%d * %d)" 71 | } 72 | 73 | enum ProductTests { 74 | static let simpleProduct = "%d * %d" 75 | static let complicatedProduct = "%d * %d * %d * %d" 76 | static let simpleProductWithParentheses = "(%d * %d)" 77 | static let complicatedProductWithSingleParenthesis = "%d * (%d * %d) * %d" 78 | static let complicatedProductWithMultipleParenthesis = "(%d * %d) * (%d * %d)" 79 | } 80 | 81 | enum SubtractionTests { 82 | static let simpleSubtraction = "%d - %d" 83 | static let simpleSubtractionWithParentheses = "(%d - %d)" 84 | static let simpleSubtractionWithParenthesesWithInitialMinus = "- (%d - %d)" 85 | static let complicatedSubtraction = "%d - %d - %d" 86 | static let negativeSingleNumber = "- %d" 87 | static let negativeExpressionWithInitialMinus = "- %d - %d - %d" 88 | } 89 | 90 | enum TransformationTests { 91 | static let productOfAddition = "(%d + %@) * (%d + %@)" 92 | static let productOfSubtraction = "(%d - %@) * (%d - %@)" 93 | static let productOfAdditionWithSubtraction = "(%d + %@) * (%d - %@)" 94 | static let divisionOfAdditionWithSubtraction = "(%d + %@) / (%d - %@)" 95 | static let divisionOfProductOfAdditionsWithProductOfSubtractions = "(%d + %@) * (%d + %@) / ((%d - %@) * (%d - %@))" 96 | static let divisionOfCrossedProducts = "(%d + %@) * (%d - %@) / ((%d + %@) * (%d - %@))" 97 | } 98 | 99 | enum ValidationTests { 100 | enum Invalid { 101 | static let unevenBracketNumber = "(%d + %d) * %d / %d - %d)" 102 | static let misplacedBrackets = ")%d) + %d) * (%d / (%d - %d)" 103 | static let misplacedSumAndClosingBracket = "(%d + %d +) * (%d / (%d - %d))" 104 | static let misplacedSubtractionAndClosingBracket = "(%d + %d -) * (%d / (%d - %d))" 105 | static let misplacedProductAndClosingBracket = "(%d + %d *) * (%d / (%d - %d))" 106 | static let misplacedDivisionAndClosingBracket = "(%d + %d /) * (%d / (%d - %d))" 107 | static let misplacedOpeningBracketAndProduct = "(%d + %d) * (%d / (* %d - %d))" 108 | static let misplacedOpeningBracketAndDivision = "(%d + %d) * (%d / (/ %d - %d))" 109 | static let productFollowedByDivision = "(%d + %d) * / (%d / (%d - %d))" 110 | static let divisionFollowedByProduct = "(%d + %d) / * (%d / (%d - %d))" 111 | static let consecutiveProducts = "(%d + %d) * * (%d / (%d - %d))" 112 | static let consecutiveDivisions = "(%d + %d) / / (%d / (%d - %d))" 113 | static let sumFollowedByProduct = "(%d + %d) + * (%d / (%d - %d))" 114 | static let sumFollowedByDivision = "(%d + %d) + / (%d / (%d - %d))" 115 | static let subtractionFollowedByProduct = "(%d + %d) - * (%d / (%d - %d))" 116 | static let subtractionFollowedByDivision = "(%d + %d) - / (%d / (%d - %d))" 117 | static let startsWithProduct = "* (%d + %d) / (%d / (%d - %d))" 118 | static let startsWithDivision = "/ (%d + %d) * (%d / (%d - %d))" 119 | static let endsWithSum = "(%d + %d) * (%d / (%d - %d)) +" 120 | static let endsWithSubtraction = "(%d + %d) * (%d / (%d - %d)) -" 121 | static let endsWithProduct = "(%d + %d) * (%d / (%d - %d)) *" 122 | static let endsWithDivision = "(%d + %d) * (%d / (%d - %d)) /" 123 | } 124 | 125 | enum Valid { 126 | static let generic = "(%d + %d) * (%d / (%d - %d))" 127 | static let consecutiveSubtractions = "(%d - - %d) * (%d / (%d - %d))" 128 | static let consecutiveSums = "(%d + + %d) * (%d / (%d - %d))" 129 | static let consecutiveSumAndSubtraction = "(%d + %d) * (%d / (%d + - %d))" 130 | static let consecutiveSubtractionAndSum = "(%d + %d) * (%d / (%d - + %d))" 131 | } 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /MathExpressionTestHelpers/Helpers/Operation.swift: -------------------------------------------------------------------------------- 1 | // Copyright © 2019 Pedro Daniel Prieto Martínez. Distributed under MIT License. 2 | 3 | import MathExpression 4 | 5 | struct Operation { 6 | private let expression: MathExpression 7 | let expectedResult: Double 8 | 9 | init(_ expression: String, expectedResult: Double) { 10 | self.expression = try! MathExpression(expression) 11 | self.expectedResult = expectedResult 12 | } 13 | 14 | func compute() -> Double { 15 | return expression.evaluate() 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /MathExpressionTestHelpers/Helpers/RandomExpressionGenerator.swift: -------------------------------------------------------------------------------- 1 | // Copyright © 2019 Pedro Daniel Prieto Martínez. Distributed under MIT License. 2 | 3 | import Foundation 4 | @testable import MathExpression 5 | 6 | class RandomExpressionGenerator { 7 | let inputs: [String: Int] = [ 8 | Formula.AdditionTests.simpleAddition: 2, 9 | Formula.AdditionTests.complicatedAddition: 4, 10 | Formula.AdditionTests.simpleAdditionWithParentheses: 2, 11 | Formula.AdditionTests.complicatedAdditionWithSingleParenthesis: 4, 12 | Formula.AdditionTests.complicatedAdditionWithMultipleParenthesis: 6, 13 | Formula.AdditionAndSubtractionTests.simpleOperation: 3, 14 | Formula.AdditionAndSubtractionTests.simpleOperationWithParentheses: 3, 15 | Formula.AdditionAndSubtractionTests.simpleOperationWithParenthesesAndInitialMinus: 4, 16 | Formula.CombinedOperationsTests.productOfAddition: 4, 17 | Formula.CombinedOperationsTests.productOfSubtraction: 4, 18 | Formula.CombinedOperationsTests.productOfAdditionWithSubtraction: 4, 19 | Formula.CombinedOperationsTests.divisionOfAdditionWithSubtraction: 4, 20 | Formula.CombinedOperationsTests.divisionOfProductOfAdditionsWithProductOfSubtractions: 8, 21 | Formula.CombinedOperationsTests.divisionOfCrossedProducts: 8, 22 | Formula.DivisionTests.simpleDivision: 2, 23 | Formula.DivisionTests.simpleDivisionWithParentheses: 2, 24 | Formula.DivisionTests.simpleDivisionWithParenthesesWithInitialDivision: 2, 25 | Formula.DivisionTests.complicatedDivision: 3, 26 | Formula.DivisionTests.divideSingleNumber: 1, 27 | Formula.DivisionTests.consecutiveDivisionExpressionWithInitialDivision: 3, 28 | Formula.ProductAndDivisionTests.simpleOperation: 3, 29 | Formula.ProductAndDivisionTests.simpleOperationWithParentheses: 3, 30 | Formula.ProductAndDivisionTests.simpleOperationWithParenthesesAndInitialDivision: 4, 31 | Formula.ProductAndDivisionTests.complicatedOperationWithParentheses: 6, 32 | Formula.ProductTests.simpleProduct: 2, 33 | Formula.ProductTests.complicatedProduct: 4, 34 | Formula.ProductTests.simpleProductWithParentheses: 2, 35 | Formula.ProductTests.complicatedProductWithSingleParenthesis: 4, 36 | Formula.ProductTests.complicatedProductWithMultipleParenthesis: 4, 37 | Formula.SubtractionTests.simpleSubtraction: 2, 38 | Formula.SubtractionTests.simpleSubtractionWithParentheses: 2, 39 | Formula.SubtractionTests.simpleSubtractionWithParenthesesWithInitialMinus: 2, 40 | Formula.SubtractionTests.complicatedSubtraction: 3, 41 | Formula.SubtractionTests.negativeSingleNumber: 1, 42 | Formula.SubtractionTests.negativeExpressionWithInitialMinus: 3 43 | ] 44 | 45 | func generateRandomExpression(combining numberOfOperationsToCombine: Int) -> String { 46 | var expression = "" 47 | var openingBrackets = 0 48 | 49 | for index in 0.. 0 { 55 | expression += MathOperator.validationCases.randomElement()!.rawValue 56 | } 57 | 58 | // We generate the formula string with random number values using the auxiliary function. 59 | let formula = generateString(for: formulaPair) 60 | 61 | // We randomly add an opening bracket (30% chance), or we add it if there's a "-" at the beginning of the 62 | // formula to prevent a validation failure. 63 | if UInt8.random(in: 0...100) <= 30 || formula.first == Character(MathOperator.subtraction.rawValue) { 64 | expression += MathBrackets.parenthesis.opening 65 | openingBrackets += 1 66 | } 67 | 68 | expression += formula 69 | 70 | // If there's any open bracket, we randomly add a closing bracket (30% chance). 71 | if openingBrackets > 0, UInt8.random(in: 0...10) <= 30 { 72 | expression += MathBrackets.parenthesis.closing 73 | openingBrackets -= 1 74 | } 75 | } 76 | 77 | // In case there is still any open bracket without counterpart, we add closing brackets to match. 78 | expression += String(repeating: Character(MathBrackets.parenthesis.closing), count: openingBrackets) 79 | 80 | return expression 81 | } 82 | 83 | private func generateString(for formula: (key: String, value: Int)) -> String { 84 | var args: [Int16] = [] 85 | 86 | for _ in 0.. 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 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 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /MathExpressionTests/Source/(Q - {0}, *) group tests/AbelianMultiplicativeGroupAxiomTests.swift: -------------------------------------------------------------------------------- 1 | // Copyright © 2019 Pedro Daniel Prieto Martínez. Distributed under MIT License. 2 | 3 | @testable import MathExpression 4 | import XCTest 5 | 6 | class AbelianMultiplicativeGroupAxiomTests: XCTestCase { 7 | func testNeutralElement() throws { 8 | let a = Int16.random() 9 | let neutralElement: Int16 = 1 10 | 11 | let leftNeutralExpression = try MathExpression(String(format: Formula.AbelianMultiplicativeGroupAxiomTests.neutralElement, neutralElement, a)) 12 | let rightNeutralExpression = try MathExpression(String(format: Formula.AbelianMultiplicativeGroupAxiomTests.neutralElement, a, neutralElement)) 13 | let expectedResult = Double(a) 14 | 15 | XCTAssertEqual(leftNeutralExpression.evaluate(), rightNeutralExpression.evaluate()) 16 | XCTAssertEqual(leftNeutralExpression.evaluate(), expectedResult) 17 | } 18 | 19 | func testInverse() throws { 20 | let a = Int16.random() 21 | 22 | let inverseExpression = try MathExpression(String(format: Formula.AbelianMultiplicativeGroupAxiomTests.inverseElement, a)) 23 | let leftProductWithInverse = try MathExpression(String(format: Formula.AbelianMultiplicativeGroupAxiomTests.leftInverse, a, a)) 24 | let rightProductWithInverse = try MathExpression(String(format: Formula.AbelianMultiplicativeGroupAxiomTests.rightInverse, a, a)) 25 | let expectedResult = 1.0 / Double(a) 26 | 27 | XCTAssertEqual(inverseExpression.evaluate(), expectedResult) 28 | XCTAssertEqual(leftProductWithInverse.evaluate(), rightProductWithInverse.evaluate()) 29 | // We need to round the evaluation result to catch cases in which the result is off 1 by 10^(-6) or less, 30 | // such as 0.99999999999953 or 1.0000000000005138 31 | // These errors are acceptable minor errors inherent to floating value computations and not the 32 | // algorithm itself. 33 | XCTAssertEqual(leftProductWithInverse.evaluate().rounded(.toNearestOrAwayFromZero), 1.0) 34 | } 35 | 36 | func testAssociativity() throws { 37 | let a = Int16.random() 38 | let b = Int16.random() 39 | let c = Int16.random() 40 | 41 | let leftAssociativityExpression = try MathExpression(String(format: Formula.AbelianMultiplicativeGroupAxiomTests.leftAssociativity, a, b, c)) 42 | let rightAssociativityExpression = try MathExpression(String(format: Formula.AbelianMultiplicativeGroupAxiomTests.rightAssociativity, a, b, c)) 43 | let expectedResult = Double(a) * Double(b) * Double(c) 44 | 45 | XCTAssertEqual(leftAssociativityExpression.evaluate(), rightAssociativityExpression.evaluate()) 46 | XCTAssertEqual(leftAssociativityExpression.evaluate(), expectedResult) 47 | } 48 | 49 | func testCommutativity() throws { 50 | let a = Int16.random() 51 | let b = Int16.random() 52 | 53 | let firstExpression = try MathExpression(String(format: Formula.AbelianMultiplicativeGroupAxiomTests.commutativity, a, b)) 54 | let secondExpression = try MathExpression(String(format: Formula.AbelianMultiplicativeGroupAxiomTests.commutativity, b, a)) 55 | let expectedResult = Double(a) * Double(b) 56 | 57 | XCTAssertEqual(firstExpression.evaluate(), secondExpression.evaluate()) 58 | XCTAssertEqual(firstExpression.evaluate(), expectedResult) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /MathExpressionTests/Source/(Q - {0}, *) group tests/DivisionTests.swift: -------------------------------------------------------------------------------- 1 | // Copyright © 2019 Pedro Daniel Prieto Martínez. Distributed under MIT License. 2 | 3 | @testable import MathExpression 4 | import XCTest 5 | 6 | // Most operation results in this class are rounded to avoid intrinsic computation errors. 7 | // We consider that results which are off by 10^(-6) or less are acceptable errors inherent 8 | // to floating value computations and not the algorithm itself. 9 | 10 | class DivisionTests: XCTestCase { 11 | func testSimpleDivision() { 12 | let a = Int16.random() 13 | let b = Int16.random() 14 | let operation = Operation( 15 | String(format: Formula.DivisionTests.simpleDivision, a, b), 16 | expectedResult: Double(a) / Double(b) 17 | ) 18 | XCTAssertEqual(operation.compute().rounded(toPlaces: 6), operation.expectedResult.rounded(toPlaces: 6)) 19 | } 20 | 21 | func testSimpleDivisionWithParentheses() { 22 | let a = Int16.random() 23 | let b = Int16.random() 24 | let operation = Operation( 25 | String(format: Formula.DivisionTests.simpleDivisionWithParentheses, a, b), 26 | expectedResult: Double(a) / Double(b) 27 | ) 28 | XCTAssertEqual(operation.compute().rounded(toPlaces: 6), operation.expectedResult.rounded(toPlaces: 6)) 29 | } 30 | 31 | func testSimpleDivisionWithParenthesesWithInitialDivision() { 32 | let a = Int16.random() 33 | let b = Int16.random() 34 | let operation = Operation( 35 | String(format: Formula.DivisionTests.simpleDivisionWithParenthesesWithInitialDivision, a, b), 36 | expectedResult: 1.0 / (Double(a) / Double(b)) 37 | ) 38 | XCTAssertEqual(operation.compute().rounded(toPlaces: 6), operation.expectedResult.rounded(toPlaces: 6)) 39 | } 40 | 41 | func testComplicatedDivision() { 42 | let a = Int16.random() 43 | let b = Int16.random() 44 | let c = Int16.random() 45 | let operation = Operation( 46 | String(format: Formula.DivisionTests.complicatedDivision, a, b, c), 47 | expectedResult: Double(a) / Double(b) / Double(c) 48 | ) 49 | XCTAssertEqual(operation.compute().rounded(toPlaces: 6), operation.expectedResult.rounded(toPlaces: 6)) 50 | } 51 | 52 | func testDivideSingleNumber() { 53 | let a = Int16.random() 54 | let operation = Operation( 55 | String(format: Formula.DivisionTests.divideSingleNumber, a), 56 | expectedResult: 1.0 / Double(a) 57 | ) 58 | XCTAssertEqual(operation.compute().rounded(toPlaces: 6), operation.expectedResult.rounded(toPlaces: 6)) 59 | } 60 | 61 | func testConsecutiveDivisionExpressionWithInitialDivision() { 62 | let a = Int16.random() 63 | let b = Int16.random() 64 | let c = Int16.random() 65 | let operation = Operation( 66 | String(format: Formula.DivisionTests.consecutiveDivisionExpressionWithInitialDivision, a, b, c), 67 | expectedResult: 1.0 / Double(a) / Double(b) / Double(c) 68 | ) 69 | XCTAssertEqual(operation.compute().rounded(toPlaces: 6), operation.expectedResult.rounded(toPlaces: 6)) 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /MathExpressionTests/Source/(Q - {0}, *) group tests/ProductAndDivisionTests.swift: -------------------------------------------------------------------------------- 1 | // Copyright © 2019 Pedro Daniel Prieto Martínez. Distributed under MIT License. 2 | 3 | @testable import MathExpression 4 | import XCTest 5 | 6 | class ProductAndDivisionTests: XCTestCase { 7 | func testSimpleOperation() { 8 | let a = Int16.random() 9 | let b = Int16.random() 10 | let c = Int16.random() 11 | let operation = Operation( 12 | String(format: Formula.ProductAndDivisionTests.simpleOperation, a, b, c), 13 | expectedResult: Double(a) / Double(b) * Double(c) 14 | ) 15 | XCTAssertEqual(operation.compute(), operation.expectedResult) 16 | } 17 | 18 | func testSimpleOperationWithParentheses() { 19 | let a = Int16.random() 20 | let b = Int16.random() 21 | let c = Int16.random() 22 | let operation = Operation( 23 | String(format: Formula.ProductAndDivisionTests.simpleOperationWithParentheses, a, b, c), 24 | expectedResult: Double(a) / (Double(c) / Double(b)) 25 | ) 26 | XCTAssertEqual(operation.compute().rounded(toPlaces: 6), operation.expectedResult.rounded(toPlaces: 6)) 27 | } 28 | 29 | func testSimpleOperationWithParenthesesAndInitialDivision() { 30 | let a = Int16.random() 31 | let b = Int16.random() 32 | let c = Int16.random() 33 | let d = Int16.random() 34 | let operation = Operation( 35 | String(format: Formula.ProductAndDivisionTests.simpleOperationWithParenthesesAndInitialDivision, a, b, c, d), 36 | expectedResult: 1.0 / (Double(a) / Double(b)) / Double(c) * Double(d) 37 | ) 38 | XCTAssertEqual(operation.compute().rounded(toPlaces: 6), operation.expectedResult.rounded(toPlaces: 6)) 39 | } 40 | 41 | func testComplicatedOperationWithParentheses() { 42 | let a = Int16.random() 43 | let b = Int16.random() 44 | let c = Int16.random() 45 | let d = Int16.random() 46 | let e = Int16.random() 47 | let f = Int16.random() 48 | 49 | let operation = Operation( 50 | String(format: Formula.ProductAndDivisionTests.complicatedOperationWithParentheses, a, b, c, d, e, f), 51 | expectedResult: (1.0 / Double(a) * (Double(b) / Double(c)) * Double(d)) / (Double(e) * Double(f)) 52 | ) 53 | XCTAssertEqual(operation.compute().rounded(toPlaces: 6), operation.expectedResult.rounded(toPlaces: 6)) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /MathExpressionTests/Source/(Q - {0}, *) group tests/ProductTests.swift: -------------------------------------------------------------------------------- 1 | // Copyright © 2019 Pedro Daniel Prieto Martínez. Distributed under MIT License. 2 | 3 | import XCTest 4 | 5 | class ProductTests: XCTestCase { 6 | func testSimpleProduct() { 7 | let a = Int16.random() 8 | let b = Int16.random() 9 | let operation = Operation( 10 | String(format: Formula.ProductTests.simpleProduct, a, b), 11 | expectedResult: Double(a) * Double(b) 12 | ) 13 | XCTAssertEqual(operation.compute(), operation.expectedResult) 14 | } 15 | 16 | func testComplicatedProduct() { 17 | let a = Int16.random() 18 | let b = Int16.random() 19 | let c = Int16.random() 20 | let d = Int16.random() 21 | let operation = Operation( 22 | String(format: Formula.ProductTests.complicatedProduct, a, b, c, d), 23 | expectedResult: Double(a) * Double(b) * Double(c) * Double(d) 24 | ) 25 | XCTAssertEqual(operation.compute(), operation.expectedResult) 26 | } 27 | 28 | func testSimpleProductWithParentheses() { 29 | let a = Int16.random() 30 | let b = Int16.random() 31 | let operation = Operation( 32 | String(format: Formula.ProductTests.simpleProductWithParentheses, a, b), 33 | expectedResult: Double(a) * Double(b) 34 | ) 35 | XCTAssertEqual(operation.compute(), operation.expectedResult) 36 | } 37 | 38 | func testComplicatedAdditionWithSingleParenthesis() { 39 | let a = Int16.random() 40 | let b = Int16.random() 41 | let c = Int16.random() 42 | let d = Int16.random() 43 | let operation = Operation( 44 | String(format: Formula.ProductTests.complicatedProductWithSingleParenthesis, a, b, c, d), 45 | expectedResult: Double(a) * Double(b) * Double(c) * Double(d) 46 | ) 47 | XCTAssertEqual(operation.compute(), operation.expectedResult) 48 | } 49 | 50 | func testComplicatedProductWithMultipleParenthesis() { 51 | let a = Int16.random() 52 | let b = Int16.random() 53 | let c = Int16.random() 54 | let d = Int16.random() 55 | let operation = Operation( 56 | String(format: Formula.ProductTests.complicatedProductWithMultipleParenthesis, a, b, c, d), 57 | expectedResult: Double(a) * Double(b) * Double(c) * Double(d) 58 | ) 59 | XCTAssertEqual(operation.compute(), operation.expectedResult) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /MathExpressionTests/Source/(Q, +) group tests/AbelianAdditiveGroupAxiomsTests.swift: -------------------------------------------------------------------------------- 1 | // Copyright © 2019 Pedro Daniel Prieto Martínez. Distributed under MIT License. 2 | 3 | @testable import MathExpression 4 | import XCTest 5 | 6 | class AbelianAdditiveGroupAxiomsTests: XCTestCase { 7 | func testNeutralElement() throws { 8 | let a = Int16.random() 9 | let neutralElement: Int16 = .zero 10 | 11 | let leftNeutralExpression = try MathExpression(String(format: Formula.AbelianAdditiveGroupAxiomsTests.neutralElement, neutralElement, a)) 12 | let rightNeutralExpression = try MathExpression(String(format: Formula.AbelianAdditiveGroupAxiomsTests.neutralElement, a, neutralElement)) 13 | let expectedResult = Double(a) 14 | 15 | XCTAssertEqual(leftNeutralExpression.evaluate(), rightNeutralExpression.evaluate()) 16 | XCTAssertEqual(leftNeutralExpression.evaluate(), expectedResult) 17 | } 18 | 19 | func testInverse() throws { 20 | let a = Int16.random() 21 | 22 | let inverseExpression = try MathExpression(String(format: Formula.AbelianAdditiveGroupAxiomsTests.inverseElement, a)) 23 | let leftSumWithInverse = try MathExpression(String(format: Formula.AbelianAdditiveGroupAxiomsTests.leftInverse, a, a)) 24 | let rightSumWithInverse = try MathExpression(String(format: Formula.AbelianAdditiveGroupAxiomsTests.rightInverse, a, a)) 25 | let expectedResult = Double(a).negative 26 | 27 | XCTAssertEqual(inverseExpression.evaluate(), expectedResult) 28 | XCTAssertEqual(leftSumWithInverse.evaluate(), rightSumWithInverse.evaluate()) 29 | XCTAssertEqual(leftSumWithInverse.evaluate(), .zero) 30 | } 31 | 32 | func testAssociativity() throws { 33 | let a = Int16.random() 34 | let b = Int16.random() 35 | let c = Int16.random() 36 | 37 | let leftAssociativityExpression = try MathExpression(String(format: Formula.AbelianAdditiveGroupAxiomsTests.leftAssociativity, a, b, c)) 38 | let rightAssociativityExpression = try MathExpression(String(format: Formula.AbelianAdditiveGroupAxiomsTests.rightAssociativity, a, b, c)) 39 | let expectedResult = Double(a) + Double(b) + Double(c) 40 | 41 | XCTAssertEqual(leftAssociativityExpression.evaluate(), rightAssociativityExpression.evaluate()) 42 | XCTAssertEqual(leftAssociativityExpression.evaluate(), expectedResult) 43 | } 44 | 45 | func testCommutativity() throws { 46 | let a = Int16.random() 47 | let b = Int16.random() 48 | 49 | let firstExpression = try MathExpression(String(format: Formula.AbelianAdditiveGroupAxiomsTests.commutativity, a, b)) 50 | let secondExpression = try MathExpression(String(format: Formula.AbelianAdditiveGroupAxiomsTests.commutativity, b, a)) 51 | let expectedResult = Double(a) + Double(b) 52 | 53 | XCTAssertEqual(firstExpression.evaluate(), secondExpression.evaluate()) 54 | XCTAssertEqual(firstExpression.evaluate(), expectedResult) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /MathExpressionTests/Source/(Q, +) group tests/AdditionAndSubtractionTests.swift: -------------------------------------------------------------------------------- 1 | // Copyright © 2019 Pedro Daniel Prieto Martínez. Distributed under MIT License. 2 | 3 | @testable import MathExpression 4 | import XCTest 5 | 6 | class AdditionAndSubtractionTests: XCTestCase { 7 | func testSimpleOperation() { 8 | let a = Int16.random() 9 | let b = Int16.random() 10 | let c = Int16.random() 11 | let operation = Operation( 12 | String(format: Formula.AdditionAndSubtractionTests.simpleOperation, a, b, c), 13 | expectedResult: Double(a) - Double(b) + Double(c) 14 | ) 15 | XCTAssertEqual(operation.compute(), operation.expectedResult) 16 | } 17 | 18 | func testSimpleOperationWithParentheses() { 19 | let a = Int16.random() 20 | let b = Int16.random() 21 | let c = Int16.random() 22 | let operation = Operation( 23 | String(format: Formula.AdditionAndSubtractionTests.simpleOperationWithParentheses, a, b, c), 24 | expectedResult: Double(a) + Double(b) - Double(c) 25 | ) 26 | XCTAssertEqual(operation.compute(), operation.expectedResult) 27 | } 28 | 29 | func testSimpleOperationWithParenthesesAndInitialMinus() { 30 | let a = Int16.random() 31 | let b = Int16.random() 32 | let c = Int16.random() 33 | let d = Int16.random() 34 | let operation = Operation( 35 | String(format: Formula.AdditionAndSubtractionTests.simpleOperationWithParenthesesAndInitialMinus, a, b, c, d), 36 | expectedResult: (Double(a) - Double(b)).negative - Double(c) + Double(d) 37 | ) 38 | XCTAssertEqual(operation.compute(), operation.expectedResult) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /MathExpressionTests/Source/(Q, +) group tests/AdditionTests.swift: -------------------------------------------------------------------------------- 1 | // Copyright © 2019 Pedro Daniel Prieto Martínez. Distributed under MIT License. 2 | 3 | @testable import MathExpression 4 | import XCTest 5 | 6 | class AdditionTests: XCTestCase { 7 | func testSimpleAddition() { 8 | let a = Int16.random() 9 | let b = Int16.random() 10 | let operation = Operation( 11 | String(format: Formula.AdditionTests.simpleAddition, a, b), 12 | expectedResult: Double(a) + Double(b) 13 | ) 14 | XCTAssertEqual(operation.compute(), operation.expectedResult) 15 | } 16 | 17 | func testComplicatedAddition() { 18 | let a = Int16.random() 19 | let b = Int16.random() 20 | let c = Int16.random() 21 | let d = Int16.random() 22 | let operation = Operation( 23 | String(format: Formula.AdditionTests.complicatedAddition, a, b, c, d), 24 | expectedResult: Double(a) + Double(b) + Double(c) + Double(d) 25 | ) 26 | XCTAssertEqual(operation.compute(), operation.expectedResult) 27 | } 28 | 29 | func testSimpleAdditionWithParentheses() { 30 | let a = Int16.random() 31 | let b = Int16.random() 32 | let operation = Operation( 33 | String(format: Formula.AdditionTests.simpleAdditionWithParentheses, a, b), 34 | expectedResult: Double(a) + Double(b) 35 | ) 36 | XCTAssertEqual(operation.compute(), operation.expectedResult) 37 | } 38 | 39 | func testComplicatedAdditionWithSingleParenthesis() { 40 | let a = Int16.random() 41 | let b = Int16.random() 42 | let c = Int16.random() 43 | let d = Int16.random() 44 | let operation = Operation( 45 | String(format: Formula.AdditionTests.complicatedAdditionWithSingleParenthesis, a, b, c, d), 46 | expectedResult: Double(a) + Double(b) + Double(c) + Double(d) 47 | ) 48 | XCTAssertEqual(operation.compute(), operation.expectedResult) 49 | } 50 | 51 | func testComplicatedAdditionWithMultipleParenthesis() { 52 | let a = Int16.random() 53 | let b = Int16.random() 54 | let c = Int16.random() 55 | let d = Int16.random() 56 | let e = Int16.random() 57 | let f = Int16.random() 58 | let operation = Operation( 59 | String(format: Formula.AdditionTests.complicatedAdditionWithMultipleParenthesis, a, b, c, d, e, f), 60 | expectedResult: Double(a) + Double(b) + Double(c) + Double(d) + Double(e) + Double(f) 61 | ) 62 | XCTAssertEqual(operation.compute(), operation.expectedResult) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /MathExpressionTests/Source/(Q, +) group tests/SubtractionTests.swift: -------------------------------------------------------------------------------- 1 | // Copyright © 2019 Pedro Daniel Prieto Martínez. Distributed under MIT License. 2 | 3 | @testable import MathExpression 4 | import XCTest 5 | 6 | class SubtractionTests: XCTestCase { 7 | func testSimpleSubtraction() { 8 | let a = Int16.random() 9 | let b = Int16.random() 10 | let operation = Operation( 11 | String(format: Formula.SubtractionTests.simpleSubtraction, a, b), 12 | expectedResult: Double(a) - Double(b) 13 | ) 14 | XCTAssertEqual(operation.compute(), operation.expectedResult) 15 | } 16 | 17 | func testSimpleSubtractionWithParentheses() { 18 | let a = Int16.random() 19 | let b = Int16.random() 20 | let operation = Operation( 21 | String(format: Formula.SubtractionTests.simpleSubtractionWithParentheses, a, b), 22 | expectedResult: Double(a) - Double(b) 23 | ) 24 | XCTAssertEqual(operation.compute(), operation.expectedResult) 25 | } 26 | 27 | func testSimpleSubtractionWithParenthesesWithInitialMinus() { 28 | let a = Int16.random() 29 | let b = Int16.random() 30 | let operation = Operation( 31 | String(format: Formula.SubtractionTests.simpleSubtractionWithParenthesesWithInitialMinus, a, b), 32 | expectedResult: Double(a).negative + Double(b) 33 | ) 34 | XCTAssertEqual(operation.compute(), operation.expectedResult) 35 | } 36 | 37 | func testComplicatedSubtraction() { 38 | let a = Int16.random() 39 | let b = Int16.random() 40 | let c = Int16.random() 41 | let operation = Operation( 42 | String(format: Formula.SubtractionTests.complicatedSubtraction, a, b, c), 43 | expectedResult: Double(a) - Double(b) - Double(c) 44 | ) 45 | XCTAssertEqual(operation.compute(), operation.expectedResult) 46 | } 47 | 48 | func testNegativeSingleNumber() { 49 | let a = Int16.random() 50 | let operation = Operation( 51 | String(format: Formula.SubtractionTests.negativeSingleNumber, a), 52 | expectedResult: Double(a).negative 53 | ) 54 | XCTAssertEqual(operation.compute(), operation.expectedResult) 55 | } 56 | 57 | func testNegativeExpression() { 58 | let a = Int16.random() 59 | let b = Int16.random() 60 | let c = Int16.random() 61 | let operation = Operation( 62 | String(format: Formula.SubtractionTests.negativeExpressionWithInitialMinus, a, b, c), 63 | expectedResult: Double(a).negative - Double(b) - Double(c) 64 | ) 65 | XCTAssertEqual(operation.compute(), operation.expectedResult) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /MathExpressionTests/Source/(Q, +, *) field tests/CombinedOperationsTest.swift: -------------------------------------------------------------------------------- 1 | // Copyright © 2019 Pedro Daniel Prieto Martínez. Distributed under MIT License. 2 | 3 | import XCTest 4 | 5 | class CombinedOperationsTests: XCTestCase { 6 | func testProductOfAddition() { 7 | let a = Int16.random() 8 | let b = Int16.random() 9 | let c = Int16.random() 10 | let d = Int16.random() 11 | let operation = Operation( 12 | String(format: Formula.CombinedOperationsTests.productOfAddition, a, b, c, d), 13 | expectedResult: (Double(a) + Double(b)) * (Double(c) + Double(d)) 14 | ) 15 | XCTAssertEqual(operation.compute(), operation.expectedResult) 16 | } 17 | 18 | func testSquareOfAddition() { 19 | let a = Int16.random() 20 | let b = Int16.random() 21 | let operation = Operation( 22 | String(format: Formula.CombinedOperationsTests.productOfAddition, a, b, a, b), 23 | expectedResult: pow(Double(a) + Double(b), 2.0) 24 | ) 25 | XCTAssertEqual(operation.compute(), operation.expectedResult) 26 | } 27 | 28 | func testProductOfSubtraction() { 29 | let a = Int16.random() 30 | let b = Int16.random() 31 | let c = Int16.random() 32 | let d = Int16.random() 33 | let operation = Operation( 34 | String(format: Formula.CombinedOperationsTests.productOfSubtraction, a, b, c, d), 35 | expectedResult: (Double(a) - Double(b)) * (Double(c) - Double(d)) 36 | ) 37 | XCTAssertEqual(operation.compute(), operation.expectedResult) 38 | } 39 | 40 | func testSquareOfSubtraction() { 41 | let a = Int16.random() 42 | let b = Int16.random() 43 | let operation = Operation( 44 | String(format: Formula.CombinedOperationsTests.productOfSubtraction, a, b, a, b), 45 | expectedResult: pow(Double(a) - Double(b), 2.0) 46 | ) 47 | XCTAssertEqual(operation.compute(), operation.expectedResult) 48 | } 49 | 50 | func testProductOfAdditionWithSubtraction() { 51 | let a = Int16.random() 52 | let b = Int16.random() 53 | let c = Int16.random() 54 | let d = Int16.randomExcluding(c) 55 | let operation = Operation( 56 | String(format: Formula.CombinedOperationsTests.productOfAdditionWithSubtraction, a, b, c, d), 57 | expectedResult: (Double(a) + Double(b)) * (Double(c) - Double(d)) 58 | ) 59 | XCTAssertEqual(operation.compute(), operation.expectedResult) 60 | } 61 | 62 | func testProductOfAdditionWithSubtractionIdentity() { 63 | let a = Int16.random() 64 | let b = Int16.random() 65 | let operation = Operation( 66 | String(format: Formula.CombinedOperationsTests.productOfAdditionWithSubtraction, a, b, a, b), 67 | expectedResult: pow(Double(a), 2.0) - pow(Double(b), 2.0) 68 | ) 69 | XCTAssertEqual(operation.compute(), operation.expectedResult) 70 | } 71 | 72 | func testDivisionOfAdditionWithSubtraction() { 73 | let a = Int16.random() 74 | let b = Int16.random() 75 | let c = Int16.random() 76 | let d = Int16.randomExcluding(c) 77 | let operation = Operation( 78 | String(format: Formula.CombinedOperationsTests.divisionOfAdditionWithSubtraction, a, b, c, d), 79 | expectedResult: (Double(a) + Double(b)) / (Double(c) - Double(d)) 80 | ) 81 | XCTAssertEqual(operation.compute(), operation.expectedResult) 82 | } 83 | 84 | func testDivisionOfProductOfAdditionsWithProductOfSubtractions() { 85 | let a = Int16.random() 86 | let b = Int16.random() 87 | let c = Int16.random() 88 | let d = Int16.random() 89 | let e = Int16.random() 90 | let f = Int16.randomExcluding(e) 91 | let g = Int16.random() 92 | let h = Int16.randomExcluding(g) 93 | let operation = Operation( 94 | String(format: Formula.CombinedOperationsTests.divisionOfProductOfAdditionsWithProductOfSubtractions, a, b, c, d, e, f, g, h), 95 | expectedResult: (Double(a) + Double(b)) * (Double(c) + Double(d)) / ((Double(e) - Double(f)) * (Double(g) - Double(h))) 96 | ) 97 | XCTAssertEqual(operation.compute().rounded(toPlaces: 6), operation.expectedResult.rounded(toPlaces: 6)) 98 | } 99 | 100 | func testDivisionOfCrossedProducts() { 101 | let a = Int16.random() 102 | let b = Int16.random() 103 | let c = Int16.random() 104 | let d = Int16.random() 105 | let e = Int16.random() 106 | let f = Int16.random() 107 | let g = Int16.random() 108 | let h = Int16.randomExcluding(g) 109 | let operation = Operation( 110 | String(format: Formula.CombinedOperationsTests.divisionOfCrossedProducts, a, b, c, d, e, f, g, h), 111 | expectedResult: (Double(a) + Double(b)) * (Double(c) - Double(d)) / ((Double(e) + Double(f)) * (Double(g) - Double(h))) 112 | ) 113 | XCTAssertEqual(operation.compute().rounded(toPlaces: 6), operation.expectedResult.rounded(toPlaces: 6)) 114 | } 115 | 116 | func testAdditionWithProductNoParentheses() { 117 | let a = Int16.random() 118 | let b = Int16.random() 119 | let c = Int16.random() 120 | let operation = Operation( 121 | String(format: Formula.CombinedOperationsTests.additionWithProductNoParentheses, a, b, c), 122 | expectedResult: Double(a) + Double(b) * Double(c) 123 | ) 124 | XCTAssertEqual(operation.compute(), operation.expectedResult) 125 | } 126 | 127 | func testAdditionWithDivisionNoParentheses() { 128 | let a = Int16.random() 129 | let b = Int16.random() 130 | let c = Int16.random() 131 | let operation = Operation( 132 | String(format: Formula.CombinedOperationsTests.additionWithDivisionNoParentheses, a, b, c), 133 | expectedResult: Double(a) + Double(b) / Double(c) 134 | ) 135 | XCTAssertEqual(operation.compute().rounded(toPlaces: 6), operation.expectedResult.rounded(toPlaces: 6)) 136 | } 137 | 138 | func testSubtractionWithProductNoParentheses() { 139 | let a = Int16.random() 140 | let b = Int16.random() 141 | let c = Int16.random() 142 | let operation = Operation( 143 | String(format: Formula.CombinedOperationsTests.subtractionWithProductNoParentheses, a, b, c), 144 | expectedResult: Double(a) * Double(b) - Double(c) 145 | ) 146 | XCTAssertEqual(operation.compute(), operation.expectedResult) 147 | } 148 | 149 | func testSubtractionWithDivisionNoParentheses() { 150 | let a = Int16.random() 151 | let b = Int16.random() 152 | let c = Int16.random() 153 | let operation = Operation( 154 | String(format: Formula.CombinedOperationsTests.subtractionWithDivisionNoParentheses, a, b, c), 155 | expectedResult: Double(a) - Double(b) / Double(c) 156 | ) 157 | XCTAssertEqual(operation.compute().rounded(toPlaces: 6), operation.expectedResult.rounded(toPlaces: 6)) 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /MathExpressionTests/Source/(Q, +, *) field tests/FieldAxiomTests.swift: -------------------------------------------------------------------------------- 1 | // Copyright © 2019 Pedro Daniel Prieto Martínez. Distributed under MIT License. 2 | 3 | @testable import MathExpression 4 | import XCTest 5 | 6 | class FieldAxiomTests: XCTestCase { 7 | // We test the distributivity of product over the addition, since the remaining field axioms 8 | // are tested in 'AbelianAdditiveGroupAxiomsTests' and 'AbelianMultiplicativeGroupAxiomTests'. 9 | 10 | func testLeftDistributivityOfProductOverAddition() throws { 11 | let a = Int16.random() 12 | let b = Int16.random() 13 | let c = Int16.random() 14 | 15 | let distributivityFactoredExpression = try MathExpression(String(format: Formula.FieldAxiomTests.leftDistributivityFactored, a, b, c)) 16 | let distributivityExpandedExpression = try MathExpression(String(format: Formula.FieldAxiomTests.distributivityExpanded, a, b, a, c)) 17 | let expectedResult = Double(a) * Double(b) + Double(a) * Double(c) 18 | 19 | XCTAssertEqual(distributivityFactoredExpression.evaluate(), distributivityExpandedExpression.evaluate()) 20 | XCTAssertEqual(distributivityFactoredExpression.evaluate(), expectedResult) 21 | } 22 | 23 | func testRightDistributivityOfProductOverAddition() throws { 24 | let a = Int16.random() 25 | let b = Int16.random() 26 | let c = Int16.random() 27 | 28 | let distributivityFactoredExpression = try MathExpression(String(format: Formula.FieldAxiomTests.rightDistributivityFactored, a, b, c)) 29 | let distributivityExpandedExpression = try MathExpression(String(format: Formula.FieldAxiomTests.distributivityExpanded, a, c, b, c)) 30 | let expectedResult = Double(a) * Double(c) + Double(b) * Double(c) 31 | 32 | XCTAssertEqual(distributivityFactoredExpression.evaluate(), distributivityExpandedExpression.evaluate()) 33 | XCTAssertEqual(distributivityFactoredExpression.evaluate(), expectedResult) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /MathExpressionTests/Source/Non-trivial transformation tests/CountTransformationTests.swift: -------------------------------------------------------------------------------- 1 | // Copyright © 2019 Pedro Daniel Prieto Martínez. Distributed under MIT License. 2 | 3 | @testable import MathExpression 4 | import XCTest 5 | 6 | class CountTransformationTests: XCTestCase { 7 | let transformation: (String) -> Double = { Double($0.count) } 8 | 9 | func testProductOfAddition() throws { 10 | let a = Int16.random() 11 | let b = String.random(length: Int.random(in: 5...20)) 12 | let c = Int16.random() 13 | let d = String.random(length: Int.random(in: 5...20)) 14 | 15 | let formula = String(format: Formula.TransformationTests.productOfAddition, a, b, c, d) 16 | let expression = try MathExpression(formula, transformation: transformation) 17 | let expectedResult = (Double(a) + Double(b.count)) * (Double(c) + Double(d.count)) 18 | 19 | XCTAssertEqual(expression.evaluate(), expectedResult) 20 | } 21 | 22 | func testSquareOfAddition() throws { 23 | let a = Int16.random() 24 | let b = String.random(length: Int.random(in: 5...20)) 25 | 26 | let formula = String(format: Formula.TransformationTests.productOfAddition, a, b, a, b) 27 | let expression = try MathExpression(formula, transformation: transformation) 28 | let expectedResult = pow(Double(a) + Double(b.count), 2.0) 29 | 30 | XCTAssertEqual(expression.evaluate(), expectedResult) 31 | } 32 | 33 | func testProductOfSubtraction() throws { 34 | let a = Int16.random() 35 | let b = String.random(length: Int.random(in: 5...20)) 36 | let c = Int16.random() 37 | let d = String.random(length: Int.random(in: 5...20)) 38 | 39 | let formula = String(format: Formula.TransformationTests.productOfSubtraction, a, b, c, d) 40 | let expression = try MathExpression(formula, transformation: transformation) 41 | let expectedResult = (Double(a) - Double(b.count)) * (Double(c) - Double(d.count)) 42 | 43 | XCTAssertEqual(expression.evaluate(), expectedResult) 44 | } 45 | 46 | func testSquareOfSubtraction() throws { 47 | let a = Int16.random() 48 | let b = String.random(length: Int.random(in: 5...20)) 49 | 50 | let formula = String(format: Formula.TransformationTests.productOfSubtraction, a, b, a, b) 51 | let expression = try MathExpression(formula, transformation: transformation) 52 | let expectedResult = pow(Double(a) - Double(b.count), 2.0) 53 | 54 | XCTAssertEqual(expression.evaluate(), expectedResult) 55 | } 56 | 57 | func testProductOfAdditionWithSubtraction() throws { 58 | let a = Int16.random() 59 | let b = String.random(length: Int.random(in: 5...20)) 60 | let c = Int16.random() 61 | let d = String.random(length: Int.random(in: 5...20)) 62 | 63 | let formula = String(format: Formula.TransformationTests.productOfAdditionWithSubtraction, a, b, c, d) 64 | let expression = try MathExpression(formula, transformation: transformation) 65 | let expectedResult = (Double(a) + Double(b.count)) * (Double(c) - Double(d.count)) 66 | 67 | XCTAssertEqual(expression.evaluate(), expectedResult) 68 | } 69 | 70 | func testProductOfAdditionWithSubtractionIdentity() throws { 71 | let a = Int16.random() 72 | let b = String.random(length: Int.random(in: 5...20)) 73 | 74 | let formula = String(format: Formula.TransformationTests.productOfAdditionWithSubtraction, a, b, a, b) 75 | let expression = try MathExpression(formula, transformation: transformation) 76 | let expectedResult = pow(Double(a), 2.0) - pow(Double(b.count), 2.0) 77 | 78 | XCTAssertEqual(expression.evaluate(), expectedResult) 79 | } 80 | 81 | func testDivisionOfAdditionWithSubtraction() throws { 82 | let a = Int16.random() 83 | let b = String.random(length: Int.random(in: 5...20)) 84 | let c = Int16.random() 85 | let d = String.random(length: Int.random(in: 5...20)) 86 | 87 | let formula = String(format: Formula.TransformationTests.divisionOfAdditionWithSubtraction, a, b, c, d) 88 | let expression = try MathExpression(formula, transformation: transformation) 89 | let expectedResult = (Double(a) + Double(b.count)) / (Double(c) - Double(d.count)) 90 | 91 | XCTAssertEqual(expression.evaluate(), expectedResult) 92 | } 93 | 94 | func testDivisionOfProductOfAdditionsWithProductOfSubtractions() throws { 95 | let a = Int16.random() 96 | let b = String.random(length: Int.random(in: 5...20)) 97 | let c = Int16.random() 98 | let d = String.random(length: Int.random(in: 5...20)) 99 | let e = Int16.random() 100 | let f = String.random(length: Int.random(in: 5...20)) 101 | let g = Int16.random() 102 | let h = String.random(length: Int.random(in: 5...20)) 103 | 104 | let formula = String(format: Formula.TransformationTests.divisionOfProductOfAdditionsWithProductOfSubtractions, a, b, c, d, e, f, g, h) 105 | let expression = try MathExpression(formula, transformation: transformation) 106 | let expectedResult = (Double(a) + Double(b.count)) * (Double(c) + Double(d.count)) / ((Double(e) - Double(f.count)) * (Double(g) - Double(h.count))) 107 | 108 | XCTAssertEqual(expression.evaluate().rounded(toPlaces: 6), expectedResult.rounded(toPlaces: 6)) 109 | } 110 | 111 | func testDivisionOfCrossedProducts() throws { 112 | let a = Int16.random() 113 | let b = String.random(length: Int.random(in: 5...20)) 114 | let c = Int16.random() 115 | let d = String.random(length: Int.random(in: 5...20)) 116 | let e = Int16.random() 117 | let f = String.random(length: Int.random(in: 5...20)) 118 | let g = Int16.random() 119 | let h = String.random(length: Int.random(in: 5...20)) 120 | 121 | let formula = String(format: Formula.TransformationTests.divisionOfCrossedProducts, a, b, c, d, e, f, g, h) 122 | let expression = try MathExpression(formula, transformation: transformation) 123 | let expectedResult = (Double(a) + Double(b.count)) * (Double(c) - Double(d.count)) / ((Double(e) + Double(f.count)) * (Double(g) - Double(h.count))) 124 | 125 | XCTAssertEqual(expression.evaluate().rounded(toPlaces: 6), expectedResult.rounded(toPlaces: 6)) 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /MathExpressionTests/Source/Non-trivial transformation tests/ExponentialTransformationTests.swift: -------------------------------------------------------------------------------- 1 | // Copyright © 2019 Pedro Daniel Prieto Martínez. Distributed under MIT License. 2 | 3 | @testable import MathExpression 4 | import XCTest 5 | 6 | // These tests intend to show that we can define mathematical functions as the transformation passed to the 7 | // expression... with some drawbacks. The most important fact to remember is that transformations are applied 8 | // LAST, that is, when the string is not a number and there are no operators or parentheses, then the parser 9 | // calls the transformation function. 10 | // 11 | // This means that, depending on the transformation, we can have limitations or even get wrong results. 12 | // In these tests, we use an exponentiation transformation to prove these facts. Due to the order in which 13 | // operations are performed, it only works properly with non-negative bases and non-negative exponents. 14 | // 15 | // Negative bases will always return a negative result, independently of the exponent, since subtractions are processed first. 16 | // Negative exponents must be explicitly expressed as '1 / (a ^ b)', with b >= 0. 17 | 18 | class ExponentialTransformationTests: XCTestCase { 19 | let transformation: (String) -> Double = { string in 20 | let splitString = string.split(separator: "^").map { String($0) } 21 | guard splitString.count == 2, 22 | let base = try? MathExpression(splitString.first ?? ""), 23 | let exponent = try? MathExpression(splitString.last ?? "") else { return .zero } 24 | return pow(base.evaluate(), exponent.evaluate()) 25 | } 26 | 27 | // MARK: - Satisfied identities 28 | 29 | func testSimpleAdditionOfExponentials() throws { 30 | let a = Int16.random(in: 0...100) 31 | let b = Int16.random(in: 1...4) 32 | let c = Int16.random(in: 0...8) 33 | let d = Int16.random(in: 1...15) 34 | 35 | let expression = try MathExpression("(\(a) ^ \(b)) + (\(c) ^ \(d))", transformation: transformation) 36 | let expectedResult = pow(Double(a), Double(b)) + pow(Double(c), Double(d)) 37 | XCTAssertEqual(expression.evaluate(), expectedResult) 38 | } 39 | 40 | func testAdditionOfExponents() throws { 41 | let a = Int16.random(in: 0...10) 42 | let b = Int16.random(in: 1...7) 43 | let c = Int16.random(in: 1...7) 44 | 45 | let expression = try MathExpression("(\(a)^\(b)) * (\(a)^\(c))", transformation: transformation) 46 | let expandedExpression = try MathExpression("\(a) ^ (\(b) + \(c))", transformation: transformation) 47 | let expectedResult = pow(Double(a), Double(b) + Double(c)) 48 | XCTAssertEqual(expression.evaluate(), expandedExpression.evaluate()) 49 | XCTAssertEqual(expression.evaluate(), expectedResult) 50 | } 51 | 52 | func testAdditionOfBases() throws { 53 | let a = Int16.random(in: 0...50) 54 | let b = Int16.random(in: 0...50) 55 | let c = Int16.random(in: 1...5) 56 | 57 | let expression = try MathExpression("(\(a) + \(b)) ^ \(c)", transformation: transformation) 58 | let expectedResult = pow(Double(a) + Double(b), Double(c)) 59 | XCTAssertEqual(expression.evaluate(), expectedResult) 60 | } 61 | 62 | func testSimpleProductOfBasesExponentials() throws { 63 | let a = Int16.random(in: 0...50) 64 | let b = Int16.random(in: 1...5) 65 | let c = Int16.random(in: 0...10) 66 | let d = Int16.random(in: 1...8) 67 | 68 | let expression = try MathExpression("(\(a)^\(b)) * (\(c)^\(d))", transformation: transformation) 69 | let expectedResult = pow(Double(a), Double(b)) * pow(Double(c), Double(d)) 70 | XCTAssertEqual(expression.evaluate(), expectedResult) 71 | } 72 | 73 | func testExponentialOfExponential() throws { 74 | let a = Int16.random(in: 0...10) 75 | let b = Int16.random(in: 0...7) 76 | let c = Int16.random(in: 1...7) 77 | 78 | let expression = try MathExpression("(\(a) ^ \(b)) ^ \(c)", transformation: transformation) 79 | let expandedExpression = try MathExpression("\(a) ^ (\(b) * \(c))", transformation: transformation) 80 | let expectedResult = pow(pow(Double(a), Double(b)), Double(c)) 81 | XCTAssertEqual(expression.evaluate(), expandedExpression.evaluate()) 82 | XCTAssertEqual(expression.evaluate(), expectedResult) 83 | } 84 | 85 | // MARK: - Non-satisfied idendities 86 | 87 | func testNegativeBaseWithEvenExponentGivesWrongResult() throws { 88 | let a = Int16.random(in: -20...0) - 1 89 | let b = Int16.randomEven(in: 1...6) 90 | 91 | let expression = try MathExpression("\(a)^\(b)", transformation: transformation) 92 | let expectedResult = pow(Double(a), Double(b)) 93 | XCTAssertNotEqual(expression.evaluate(), expectedResult) 94 | } 95 | 96 | func testNegativeExponentGivesWrongResult() throws { 97 | let a = Int16.random(in: 0...20) 98 | let b = Int16.random(in: 1...6) 99 | 100 | let expression = try MathExpression("\(a)^(-\(b))", transformation: transformation) 101 | let expandedExpression = try MathExpression("1 / (\(a)^\(b))", transformation: transformation) 102 | let expectedResult = Double(1) / pow(Double(a), Double(b)) 103 | XCTAssertNotEqual(expression.evaluate(), expandedExpression.evaluate()) 104 | XCTAssertNotEqual(expression.evaluate(), expectedResult) 105 | XCTAssertEqual(expandedExpression.evaluate(), expectedResult) 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /MathExpressionTests/Source/Non-trivial transformation tests/FactorialTransformationTests.swift: -------------------------------------------------------------------------------- 1 | // Copyright © 2019 Pedro Daniel Prieto Martínez. Distributed under MIT License. 2 | 3 | @testable import MathExpression 4 | import XCTest 5 | 6 | class FactorialTransformationTests: XCTestCase { 7 | let transformation: (String) -> Double = { string in 8 | guard string.last == "!", let number = Int(string.dropLast()) else { return .zero } 9 | return Double(number.factorial()) 10 | } 11 | 12 | func testProductOfAddition() throws { 13 | let a = Int16.random() 14 | let b = Int.random(in: 0...10) 15 | let c = Int16.random() 16 | let d = Int.random(in: 0...10) 17 | 18 | let formula = String(format: Formula.TransformationTests.productOfAddition, a, "\(b)!", c, "\(d)!") 19 | let expression = try MathExpression(formula, transformation: transformation) 20 | let expectedResult = (Double(a) + Double(b.factorial())) * (Double(c) + Double(d.factorial())) 21 | 22 | XCTAssertEqual(expression.evaluate(), expectedResult) 23 | } 24 | 25 | func testSquareOfAddition() throws { 26 | let a = Int16.random() 27 | let b = Int.random(in: 0...10) 28 | 29 | let formula = String(format: Formula.TransformationTests.productOfAddition, a, "\(b)!", a, "\(b)!") 30 | let expression = try MathExpression(formula, transformation: transformation) 31 | let expectedResult = pow(Double(a) + Double(b.factorial()), 2.0) 32 | 33 | XCTAssertEqual(expression.evaluate(), expectedResult) 34 | } 35 | 36 | func testProductOfSubtraction() throws { 37 | let a = Int16.random() 38 | let b = Int.random(in: 0...10) 39 | let c = Int16.random() 40 | let d = Int.random(in: 0...10) 41 | 42 | let formula = String(format: Formula.TransformationTests.productOfSubtraction, a, "\(b)!", c, "\(d)!") 43 | let expression = try MathExpression(formula, transformation: transformation) 44 | let expectedResult = (Double(a) - Double(b.factorial())) * (Double(c) - Double(d.factorial())) 45 | 46 | XCTAssertEqual(expression.evaluate(), expectedResult) 47 | } 48 | 49 | func testSquareOfSubtraction() throws { 50 | let a = Int16.random() 51 | let b = Int.random(in: 0...10) 52 | 53 | let formula = String(format: Formula.TransformationTests.productOfSubtraction, a, "\(b)!", a, "\(b)!") 54 | let expression = try MathExpression(formula, transformation: transformation) 55 | let expectedResult = pow(Double(a) - Double(b.factorial()), 2.0) 56 | 57 | XCTAssertEqual(expression.evaluate(), expectedResult) 58 | } 59 | 60 | func testProductOfAdditionWithSubtraction() throws { 61 | let a = Int16.random() 62 | let b = Int.random(in: 0...10) 63 | let c = Int16.random() 64 | let d = Int.random(in: 0...10) 65 | 66 | let formula = String(format: Formula.TransformationTests.productOfAdditionWithSubtraction, a, "\(b)!", c, "\(d)!") 67 | let expression = try MathExpression(formula, transformation: transformation) 68 | let expectedResult = (Double(a) + Double(b.factorial())) * (Double(c) - Double(d.factorial())) 69 | 70 | XCTAssertEqual(expression.evaluate(), expectedResult) 71 | } 72 | 73 | func testProductOfAdditionWithSubtractionIdentity() throws { 74 | let a = Int16.random() 75 | let b = Int.random(in: 0...10) 76 | 77 | let formula = String(format: Formula.TransformationTests.productOfAdditionWithSubtraction, a, "\(b)!", a, "\(b)!") 78 | let expression = try MathExpression(formula, transformation: transformation) 79 | let expectedResult = pow(Double(a), 2.0) - pow(Double(b.factorial()), 2.0) 80 | 81 | XCTAssertEqual(expression.evaluate(), expectedResult) 82 | } 83 | 84 | func testDivisionOfAdditionWithSubtraction() throws { 85 | let a = Int16.random() 86 | let b = Int.random(in: 0...10) 87 | let c = Int16.random() 88 | let d = Int.random(in: 0...10) 89 | 90 | let formula = String(format: Formula.TransformationTests.divisionOfAdditionWithSubtraction, a, "\(b)!", c, "\(d)!") 91 | let expression = try MathExpression(formula, transformation: transformation) 92 | let expectedResult = (Double(a) + Double(b.factorial())) / (Double(c) - Double(d.factorial())) 93 | 94 | XCTAssertEqual(expression.evaluate(), expectedResult) 95 | } 96 | 97 | func testDivisionOfProductOfAdditionsWithProductOfSubtractions() throws { 98 | let a = Int16.random() 99 | let b = Int.random(in: 0...10) 100 | let c = Int16.random() 101 | let d = Int.random(in: 0...10) 102 | let e = Int16.random() 103 | let f = Int.random(in: 0...10) 104 | let g = Int16.random() 105 | let h = Int.random(in: 0...10) 106 | 107 | let formula = String(format: Formula.TransformationTests.divisionOfProductOfAdditionsWithProductOfSubtractions, a, "\(b)!", c, "\(d)!", e, "\(f)!", g, "\(h)!") 108 | let expression = try MathExpression(formula, transformation: transformation) 109 | let expectedResult = (Double(a) + Double(b.factorial())) * (Double(c) + Double(d.factorial())) / ((Double(e) - Double(f.factorial())) * (Double(g) - Double(h.factorial()))) 110 | 111 | XCTAssertEqual(expression.evaluate().rounded(toPlaces: 6), expectedResult.rounded(toPlaces: 6)) 112 | } 113 | 114 | func testDivisionOfCrossedProducts() throws { 115 | let a = Int16.random() 116 | let b = Int.random(in: 0...10) 117 | let c = Int16.random() 118 | let d = Int.random(in: 0...10) 119 | let e = Int16.random() 120 | let f = Int.random(in: 0...10) 121 | let g = Int16.random() 122 | let h = Int.random(in: 0...10) 123 | 124 | let formula = String(format: Formula.TransformationTests.divisionOfCrossedProducts, a, "\(b)!", c, "\(d)!", e, "\(f)!", g, "\(h)!") 125 | let expression = try MathExpression(formula, transformation: transformation) 126 | let expectedResult = (Double(a) + Double(b.factorial())) * (Double(c) - Double(d.factorial())) / ((Double(e) + Double(f.factorial())) * (Double(g) - Double(h.factorial()))) 127 | 128 | XCTAssertEqual(expression.evaluate().rounded(toPlaces: 6), expectedResult.rounded(toPlaces: 6)) 129 | } 130 | } 131 | 132 | private extension Int { 133 | func factorial() -> Int { 134 | guard self > 1 else { return 1 } 135 | return self * (self - 1).factorial() 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /MathExpressionTests/Source/Validation tests/ValidationTests.swift: -------------------------------------------------------------------------------- 1 | // Copyright © 2019 Pedro Daniel Prieto Martínez. Distributed under MIT License. 2 | 3 | @testable import MathExpression 4 | import XCTest 5 | 6 | class ValidationTests: XCTestCase { 7 | var a: Int16! 8 | var b: Int16! 9 | var c: Int16! 10 | var d: Int16! 11 | var e: Int16! 12 | 13 | override func setUp() { 14 | super.setUp() 15 | a = Int16.random() 16 | b = Int16.random() 17 | c = Int16.random() 18 | d = Int16.random() 19 | e = Int16.randomExcluding(d) 20 | } 21 | 22 | override func tearDown() { 23 | a = nil 24 | b = nil 25 | c = nil 26 | d = nil 27 | e = nil 28 | } 29 | 30 | func testInvalidFormulaThrowsError_emptyExpression() { 31 | assert(try MathExpression(""), throws: MathExpression.ValidationError.emptyExpression) 32 | } 33 | 34 | func testInvalidFormulaThrowsError_unevenBracketNumber() { 35 | let formula = String(format: Formula.ValidationTests.Invalid.unevenBracketNumber, a, b, c, d, e) 36 | 37 | assert(try MathExpression(formula), throws: MathExpression.ValidationError.unevenOpeningClosingBracketNumber) 38 | } 39 | 40 | func testInvalidFormulaThrowsError_misplacedBrackets() { 41 | let formula = String(format: Formula.ValidationTests.Invalid.misplacedBrackets, a, b, c, d, e) 42 | 43 | assert(try MathExpression(formula), throws: MathExpression.ValidationError.misplacedBrackets) 44 | } 45 | 46 | func testInvalidFormulaThrowsError_misplacedSumAndClosingBracket() { 47 | let formula = String(format: Formula.ValidationTests.Invalid.misplacedSumAndClosingBracket, a, b, c, d, e) 48 | 49 | assert(try MathExpression(formula), throws: MathExpression.ValidationError.endsWithOperator("+")) 50 | } 51 | 52 | func testInvalidFormulaThrowsError_misplacedSubtractionAndClosingBracket() { 53 | let formula = String(format: Formula.ValidationTests.Invalid.misplacedSubtractionAndClosingBracket, a, b, c, d, e) 54 | 55 | assert(try MathExpression(formula), throws: MathExpression.ValidationError.endsWithOperator("-")) 56 | } 57 | 58 | func testInvalidFormulaThrowsError_misplacedProductAndClosingBracket() { 59 | let formula = String(format: Formula.ValidationTests.Invalid.misplacedProductAndClosingBracket, a, b, c, d, e) 60 | 61 | assert(try MathExpression(formula), throws: MathExpression.ValidationError.endsWithOperator("*")) 62 | } 63 | 64 | func testInvalidFormulaThrowsError_misplacedDivisionAndClosingBracket() { 65 | let formula = String(format: Formula.ValidationTests.Invalid.misplacedDivisionAndClosingBracket, a, b, c, d, e) 66 | 67 | assert(try MathExpression(formula), throws: MathExpression.ValidationError.endsWithOperator("/")) 68 | } 69 | 70 | func testInvalidFormulaThrowsError_misplacedOpeningBracketAndProduct() { 71 | let formula = String(format: Formula.ValidationTests.Invalid.misplacedOpeningBracketAndProduct, a, b, c, d, e) 72 | 73 | assert(try MathExpression(formula), throws: MathExpression.ValidationError.startsWithNonSumOrSubtractionOperator("*")) 74 | } 75 | 76 | func testInvalidFormulaThrowsError_misplacedOpeningBracketAndDivision() { 77 | let formula = String(format: Formula.ValidationTests.Invalid.misplacedOpeningBracketAndDivision, a, b, c, d, e) 78 | 79 | assert(try MathExpression(formula), throws: MathExpression.ValidationError.startsWithNonSumOrSubtractionOperator("/")) 80 | } 81 | 82 | func testInvalidFormulaThrowsError_productFollowedByDivision() { 83 | let formula = String(format: Formula.ValidationTests.Invalid.productFollowedByDivision, a, b, c, d, e) 84 | 85 | assert(try MathExpression(formula), throws: MathExpression.ValidationError.invalidConsecutiveOperators("*/")) 86 | } 87 | 88 | func testInvalidFormulaThrowsError_divisionFollowedByProduct() { 89 | let formula = String(format: Formula.ValidationTests.Invalid.divisionFollowedByProduct, a, b, c, d, e) 90 | 91 | assert(try MathExpression(formula), throws: MathExpression.ValidationError.invalidConsecutiveOperators("/*")) 92 | } 93 | 94 | func testInvalidFormulaThrowsError_consecutiveProducts() { 95 | let formula = String(format: Formula.ValidationTests.Invalid.consecutiveProducts, a, b, c, d, e) 96 | 97 | assert(try MathExpression(formula), throws: MathExpression.ValidationError.invalidConsecutiveOperators("**")) 98 | } 99 | 100 | func testInvalidFormulaThrowsError_consecutiveDivisions() { 101 | let formula = String(format: Formula.ValidationTests.Invalid.consecutiveDivisions, a, b, c, d, e) 102 | 103 | assert(try MathExpression(formula), throws: MathExpression.ValidationError.invalidConsecutiveOperators("//")) 104 | } 105 | 106 | func testInvalidFormulaThrowsError_sumFollowedByProduct() { 107 | let formula = String(format: Formula.ValidationTests.Invalid.sumFollowedByProduct, a, b, c, d, e) 108 | 109 | assert(try MathExpression(formula), throws: MathExpression.ValidationError.invalidConsecutiveOperators("+*")) 110 | } 111 | 112 | func testInvalidFormulaThrowsError_sumFollowedByDivision() { 113 | let formula = String(format: Formula.ValidationTests.Invalid.sumFollowedByDivision, a, b, c, d, e) 114 | 115 | assert(try MathExpression(formula), throws: MathExpression.ValidationError.invalidConsecutiveOperators("+/")) 116 | } 117 | 118 | func testInvalidFormulaThrowsError_subtractionFollowedByProduct() { 119 | let formula = String(format: Formula.ValidationTests.Invalid.subtractionFollowedByProduct, a, b, c, d, e) 120 | 121 | assert(try MathExpression(formula), throws: MathExpression.ValidationError.invalidConsecutiveOperators("-*")) 122 | } 123 | 124 | func testInvalidFormulaThrowsError_subtractionFollowedByDivision() { 125 | let formula = String(format: Formula.ValidationTests.Invalid.subtractionFollowedByDivision, a, b, c, d, e) 126 | 127 | assert(try MathExpression(formula), throws: MathExpression.ValidationError.invalidConsecutiveOperators("-/")) 128 | } 129 | 130 | func testInvalidFormulaThrowsError_startsWithProduct() { 131 | let formula = String(format: Formula.ValidationTests.Invalid.startsWithProduct, a, b, c, d, e) 132 | 133 | assert(try MathExpression(formula), throws: MathExpression.ValidationError.startsWithNonSumOrSubtractionOperator("*")) 134 | } 135 | 136 | func testInvalidFormulaThrowsError_startsWithDivision() { 137 | let formula = String(format: Formula.ValidationTests.Invalid.startsWithDivision, a, b, c, d, e) 138 | 139 | assert(try MathExpression(formula), throws: MathExpression.ValidationError.startsWithNonSumOrSubtractionOperator("/")) 140 | } 141 | 142 | func testInvalidFormulaThrowsError_endsWithSum() { 143 | let formula = String(format: Formula.ValidationTests.Invalid.endsWithSum, a, b, c, d, e) 144 | 145 | assert(try MathExpression(formula), throws: MathExpression.ValidationError.endsWithOperator("+")) 146 | } 147 | 148 | func testInvalidFormulaThrowsError_endsWithSubtraction() { 149 | let formula = String(format: Formula.ValidationTests.Invalid.endsWithSubtraction, a, b, c, d, e) 150 | 151 | assert(try MathExpression(formula), throws: MathExpression.ValidationError.endsWithOperator("-")) 152 | } 153 | 154 | func testInvalidFormulaThrowsError_endsWithProduct() { 155 | let formula = String(format: Formula.ValidationTests.Invalid.endsWithProduct, a, b, c, d, e) 156 | 157 | assert(try MathExpression(formula), throws: MathExpression.ValidationError.endsWithOperator("*")) 158 | } 159 | 160 | func testInvalidFormulaThrowsError_endsWithDivision() { 161 | let formula = String(format: Formula.ValidationTests.Invalid.endsWithDivision, a, b, c, d, e) 162 | 163 | assert(try MathExpression(formula), throws: MathExpression.ValidationError.endsWithOperator("/")) 164 | } 165 | 166 | func testValidFormulaNotThrowingError_generic() { 167 | let formula = String(format: Formula.ValidationTests.Valid.generic, a, b, c, d, e) 168 | 169 | XCTAssertNoThrow(try MathExpression(formula)) 170 | } 171 | 172 | func testValidFormulaNotThrowingError_consecutiveSubtractions() { 173 | let formula = String(format: Formula.ValidationTests.Valid.consecutiveSubtractions, a, b, c, d, e) 174 | 175 | XCTAssertNoThrow(try MathExpression(formula)) 176 | } 177 | 178 | func testValidFormulaNotThrowingError_consecutiveSums() { 179 | let formula = String(format: Formula.ValidationTests.Valid.consecutiveSums, a, b, c, d, e) 180 | 181 | XCTAssertNoThrow(try MathExpression(formula)) 182 | } 183 | 184 | func testValidFormulaNotThrowingError_consecutiveSumAndSubtraction() { 185 | let formula = String(format: Formula.ValidationTests.Valid.consecutiveSumAndSubtraction, a, b, c, d, e) 186 | 187 | XCTAssertNoThrow(try MathExpression(formula)) 188 | } 189 | 190 | func testValidFormulaNotThrowingError_consecutiveSubtractionAndSum() { 191 | let formula = String(format: Formula.ValidationTests.Valid.consecutiveSubtractionAndSum, a, b, c, d, e) 192 | 193 | XCTAssertNoThrow(try MathExpression(formula)) 194 | } 195 | } 196 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.0 2 | import PackageDescription 3 | 4 | let package = Package( 5 | name: "MathExpression", 6 | platforms: [ 7 | .macOS(.v10_14), .iOS(.v12), .tvOS(.v12) 8 | ], 9 | products: [ 10 | .library(name: "MathExpression", targets: ["MathExpression"]), 11 | ], 12 | targets: [ 13 | .target(name: "MathExpression", path: "MathExpression/Source"), 14 | ] 15 | ) 16 | -------------------------------------------------------------------------------- /fastlane/Fastfile: -------------------------------------------------------------------------------- 1 | default_platform(:ios) 2 | 3 | #################################################################################################### 4 | #################################################################################################### 5 | 6 | ################### 7 | # Deployment lane # 8 | ################### 9 | 10 | desc "Deploys the podspec file to Trunk" 11 | desc "Usage example: fastlane deploy_pod" 12 | lane :deploy_pod do 13 | pod_push( 14 | path: "MathExpression.podspec", 15 | verbose: false, 16 | swift_version: "5.0" 17 | ) 18 | end 19 | 20 | ############## 21 | # Test lanes # 22 | ############## 23 | 24 | platform :ios do 25 | desc "Runs framework's unit tests in the specified device." 26 | desc "Usage example: fastlane ios test device:'iPhone 8'" 27 | lane :test do |options| 28 | raise "Missing 'device' parameter. Usage: fastlane ios test device:DEVICE" unless options[:device] 29 | scan( 30 | scheme: "MathExpression-iOS", 31 | device: options[:device], 32 | clean: true, 33 | disable_concurrent_testing: true 34 | ) 35 | end 36 | 37 | lane :test_performance do |options| 38 | raise "Missing 'device' parameter. Usage: fastlane ios test_performance device:DEVICE" unless options[:device] 39 | scan( 40 | scheme: "PerformanceTests", 41 | device: options[:device], 42 | clean: true, 43 | disable_concurrent_testing: true 44 | ) 45 | end 46 | end 47 | 48 | 49 | ############### 50 | # Build lanes # 51 | ############### 52 | 53 | desc "Builds the framework for the specified platform (either 'iOS', 'tvOS' or 'macOS')." 54 | desc "This lane is to make sure that all platforms build correctly and there are no breaking changes. No tests are executed." 55 | desc "Usage example: fastlane build_framework platform:'iOS'" 56 | lane :build_framework do |options| 57 | raise "Missing 'platform' parameter. Usage: fastlane build_framework platform:PLATFORM" unless options[:platform] 58 | platform = options[:platform] 59 | xcbuild( 60 | scheme: "MathExpression-" + "#{platform}", 61 | clean: true 62 | ) 63 | end 64 | 65 | desc "Builds the example app for the specified iOS version." 66 | desc "This lane is to make sure that the example app builds correctly and that breaking API changes are detected before deployment." 67 | desc "Usage example: fastlane build_example_app ios_version:'12.4'" 68 | lane :build_example_app do |options| 69 | raise "Missing 'ios_version' parameter. Usage: fastlane build_example_app ios_version:IOS_VERSION" unless options[:ios_version] 70 | ios_version = options[:ios_version] 71 | xcbuild( 72 | scheme: "MathExpressionExample", 73 | sdk: "iphonesimulator" + "#{ios_version}", 74 | clean: true 75 | ) 76 | end 77 | -------------------------------------------------------------------------------- /fastlane/README.md: -------------------------------------------------------------------------------- 1 | fastlane documentation 2 | ================ 3 | # Installation 4 | 5 | Make sure you have the latest version of the Xcode command line tools installed: 6 | 7 | ``` 8 | xcode-select --install 9 | ``` 10 | 11 | Install _fastlane_ using 12 | ``` 13 | [sudo] gem install fastlane -NV 14 | ``` 15 | or alternatively using `brew cask install fastlane` 16 | 17 | # Available Actions 18 | ### deploy_pod 19 | ``` 20 | fastlane deploy_pod 21 | ``` 22 | Deploys the podspec file to Trunk 23 | ### build_framework 24 | ``` 25 | fastlane build_framework 26 | ``` 27 | Builds the framework for the specified platform (either 'iOS', 'tvOS' or 'macOS'). 28 | 29 | This lane is to make sure that all platforms build correctly and there are no breaking changes. No tests are executed. 30 | 31 | Usage example: fastlane build_framework platform:'iOS' 32 | ### build_example_app 33 | ``` 34 | fastlane build_example_app 35 | ``` 36 | Builds the example app for the specified iOS version. 37 | 38 | This lane is to make sure that the example app builds correctly and that breaking API changes are detected before deployment. 39 | 40 | Usage example: fastlane build_example_app ios_version:'12.4' 41 | 42 | ---- 43 | 44 | ## iOS 45 | ### ios test 46 | ``` 47 | fastlane ios test 48 | ``` 49 | Runs framework's unit tests in the specified device. 50 | 51 | Usage example: fastlane ios test device:'iPhone 8' 52 | ### ios test_performance 53 | ``` 54 | fastlane ios test_performance 55 | ``` 56 | 57 | 58 | ---- 59 | 60 | This README.md is auto-generated and will be re-generated every time [fastlane](https://fastlane.tools) is run. 61 | More information about fastlane can be found on [fastlane.tools](https://fastlane.tools). 62 | The documentation of fastlane can be found on [docs.fastlane.tools](https://docs.fastlane.tools). 63 | --------------------------------------------------------------------------------