├── .swift-version
├── .swiftlint.yml
├── codecov.yml
├── RomanNumeralKit.xcodeproj
├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
├── xcuserdata
│ └── kyle.xcuserdatad
│ │ └── xcschemes
│ │ └── xcschememanagement.plist
└── xcshareddata
│ ├── IDETemplateMacros.plist
│ └── xcschemes
│ └── RomanNumeralKit.xcscheme
├── Sources
└── RomanNumeralKit
│ ├── RomanNumeralKit.h
│ ├── Info.plist
│ ├── Numeric System
│ ├── Subtractive Notation
│ │ ├── RomanNumeralConvertible.swift
│ │ ├── SubtractiveRomanNumeralSymbolConvertible.swift
│ │ ├── SubtractiveRomanNumeralSymbolsConvertible.swift
│ │ ├── SubtractiveRomanNumeralSymbol.swift
│ │ └── RomanNumeral.swift
│ ├── Additive Notation
│ │ ├── AdditiveRomanNumeralConvertible.swift
│ │ ├── AdditiveRomanNumeralSymbolConvertible.swift
│ │ ├── AdditiveRomanNumeralSymbolsConvertible.swift
│ │ ├── AdditiveRomanNumeralSymbolCondenser.swift
│ │ └── AdditiveRomanNumeral.swift
│ ├── RomanNumeralSymbolError.swift
│ ├── RomanNumeralSymbolConvertible.swift
│ ├── RomanNumeralSymbolsConvertible.swift
│ ├── RomanNumeralError.swift
│ ├── RomanNumeralTallyMark.swift
│ ├── RomanNumeralArithmeticError.swift
│ ├── RomanNumeralSymbolProtocol.swift
│ ├── RomanNumeralTallyMarkGroup.swift
│ ├── RomanNumeralProtocol.swift
│ └── RomanNumeralSymbol.swift
│ └── Extensions
│ └── Foundation
│ ├── DateComponents+RomanNumeral.swift
│ ├── Int+RomanNumeral.swift
│ ├── String+RomanNumeral.swift
│ ├── Sequence+Sorting.swift
│ └── Calendar+RomanNumeral.swift
├── CHANGELOG.md
├── RomanNumeralKit.podspec
├── Tests
└── RomanNumeralKitTests
│ ├── Info.plist
│ ├── Numeric System
│ ├── RomanNumeralErrorTests.swift
│ ├── RomanNumeralArithmeticErrorTests.swift
│ ├── RomanNumeralSymbolErrorTests.swift
│ ├── RomanNumeralTallyMarkGroupTests.swift
│ ├── RomanNumeralSymbolTests.swift
│ └── Subtractive Notation
│ │ └── SubtractiveRomanNumeralSymbolTests.swift
│ └── Extensions
│ └── Foundation
│ ├── IntTests+RomanNumeral.swift
│ ├── StringTests+RomanNumeral.swift
│ ├── DateComponentsTests+RomanNumeral.swift
│ └── CalendarTests+RomanNumeral.swift
├── LICENSE
├── Package.swift
├── .gitignore
├── .swiftformat
├── .travis.yml
└── README.md
/.swift-version:
--------------------------------------------------------------------------------
1 | 5.1
--------------------------------------------------------------------------------
/.swiftlint.yml:
--------------------------------------------------------------------------------
1 | disabled_rules:
2 | - todo
--------------------------------------------------------------------------------
/codecov.yml:
--------------------------------------------------------------------------------
1 | ignore:
2 | - "Tests/**/*"
3 | - "Sources/RomanNumeralKit/Numeric System/RomanNumerals.swift"
--------------------------------------------------------------------------------
/RomanNumeralKit.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/RomanNumeralKit.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Sources/RomanNumeralKit/RomanNumeralKit.h:
--------------------------------------------------------------------------------
1 | //
2 | // RomanNumeralKit.h
3 | // RomanNumeralKit
4 | //
5 | // Created by Kyle Hughes on 4/16/18.
6 | // Copyright © 2018 Kyle Hughes. All rights reserved.
7 | //
8 |
9 | #import // `FOUNDATION_EXPORT`
10 |
11 | //! Project version number for RomanNumeralKit.
12 | FOUNDATION_EXPORT double RomanNumeralKitVersionNumber;
13 |
14 | //! Project version string for RomanNumeralKit.
15 | FOUNDATION_EXPORT const unsigned char RomanNumeralKitVersionString[];
16 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | All notable changes to this project will be documented in this file.
4 |
5 | ## [1.2.1] - 2021-06-24
6 |
7 | - Ignored `Info.plist` files in Swift package definition.
8 |
9 | ## [1.2.0] - 2021-03-13
10 |
11 | ### Changed
12 |
13 | - Removed development dependencies from Swift Package definition.
14 |
15 | ## [1.1.0] - 2020-09-05
16 |
17 | ### Changed
18 |
19 | - Improved performance of arithmetic functions.
20 | - Removed `dynamic` specifier from library product.
21 |
22 | ## [1.0.0] - 2019-08-28
23 |
24 | ### Added
25 |
26 | - Initial release of RomanNumeralKit.
27 |
--------------------------------------------------------------------------------
/RomanNumeralKit.podspec:
--------------------------------------------------------------------------------
1 | Pod::Spec.new do |s|
2 | s.name = "RomanNumeralKit"
3 | s.version = "1.2.1"
4 | s.license = "MIT"
5 | s.summary = "First-class Roman numeral support for Swift."
6 | s.homepage = "https://github.com/kylehughes/RomanNumeralKit"
7 | s.authors = "Kyle Hughes"
8 | s.source = { :git => "https://github.com/kylehughes/RomanNumeralKit.git", :tag => s.version }
9 |
10 | s.ios.deployment_target = "10.0"
11 | s.osx.deployment_target = "10.12"
12 | s.tvos.deployment_target = "10.0"
13 | s.watchos.deployment_target = "3.0"
14 |
15 | s.swift_version = "5.0"
16 |
17 | s.source_files = "Sources/RomanNumeralKit/**/*.swift"
18 | end
19 |
--------------------------------------------------------------------------------
/Tests/RomanNumeralKitTests/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.2.1
19 | CFBundleVersion
20 | 1
21 |
22 |
23 |
--------------------------------------------------------------------------------
/RomanNumeralKit.xcodeproj/xcuserdata/kyle.xcuserdatad/xcschemes/xcschememanagement.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SchemeUserState
6 |
7 | RomanNumeralKit.xcscheme_^#shared#^_
8 |
9 | orderHint
10 | 0
11 |
12 | RomanNumeralKit_macOS.xcscheme
13 |
14 | orderHint
15 | 1
16 |
17 |
18 | SuppressBuildableAutocreation
19 |
20 | 27667F9F2084B37D00AB8A66
21 |
22 | primary
23 |
24 |
25 | 27667FA82084B37D00AB8A66
26 |
27 | primary
28 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/Sources/RomanNumeralKit/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleDisplayName
8 | RomanNumeralKit
9 | CFBundleExecutable
10 | $(EXECUTABLE_NAME)
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | $(PRODUCT_NAME)
17 | CFBundlePackageType
18 | FMWK
19 | CFBundleShortVersionString
20 | 1.2.1
21 | CFBundleVersion
22 | $(CURRENT_PROJECT_VERSION)
23 | NSPrincipalClass
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright 2018 Kyle Hughes
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4 |
5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6 |
7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:5.3
2 |
3 | import PackageDescription
4 |
5 | let package = Package(
6 | name: "RomanNumeralKit",
7 | platforms: [
8 | .macOS(.v10_12),
9 | .iOS(.v10),
10 | .tvOS(.v10),
11 | .watchOS(.v3),
12 | ],
13 | products: [
14 | .library(
15 | name: "RomanNumeralKit",
16 | targets: [
17 | "RomanNumeralKit",
18 | ]
19 | ),
20 | ],
21 | dependencies: [
22 | ],
23 | targets: [
24 | .target(
25 | name: "RomanNumeralKit",
26 | dependencies: [
27 | ],
28 | exclude: [
29 | "Info.plist",
30 | ]
31 | ),
32 | .testTarget(
33 | name: "RomanNumeralKitTests",
34 | dependencies: [
35 | "RomanNumeralKit",
36 | ],
37 | exclude: [
38 | "Info.plist",
39 | ]
40 | ),
41 | ],
42 | swiftLanguageVersions: [
43 | .v5,
44 | ]
45 | )
46 |
--------------------------------------------------------------------------------
/RomanNumeralKit.xcodeproj/xcshareddata/IDETemplateMacros.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | FILEHEADER
6 |
7 | // ___FILENAME___
8 | // ___PRODUCTNAME___
9 | //
10 | // Copyright © ___YEAR___ ___ORGANIZATIONNAME___.
11 | //
12 | // Permission is hereby granted, free of charge, to any person obtaining a copy
13 | // of this software and associated documentation files (the "Software"), to deal
14 | // in the Software without restriction, including without limitation the rights
15 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
16 | // copies of the Software, and to permit persons to whom the Software is
17 | // furnished to do so, subject to the following conditions:
18 | //
19 | // The above copyright notice and this permission notice shall be included in
20 | // all copies or substantial portions of the Software.
21 | //
22 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
28 | // THE SOFTWARE.
29 | //
30 |
31 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Xcode
2 |
3 | ## Build generated
4 | build/
5 | DerivedData/
6 |
7 | ## Various settings
8 | *.pbxuser
9 | !default.pbxuser
10 | *.mode1v3
11 | !default.mode1v3
12 | *.mode2v3
13 | !default.mode2v3
14 | *.perspectivev3
15 | !default.perspectivev3
16 | xcuserdata/
17 |
18 | ## Other
19 | *.moved-aside
20 | *.xccheckout
21 | *.xcscmblueprint
22 |
23 | ## Obj-C/Swift specific
24 | *.hmap
25 | *.ipa
26 | *.dSYM.zip
27 | *.dSYM
28 |
29 | ## Playgrounds
30 | timeline.xctimeline
31 | playground.xcworkspace
32 |
33 | # Swift Package Manager
34 | #
35 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
36 | # Packages/
37 | # Package.pins
38 | # Package.resolved
39 | .swiftpm/
40 | .build/
41 |
42 | # CocoaPods
43 | #
44 | # We recommend against adding the Pods directory to your .gitignore. However
45 | # you should judge for yourself, the pros and cons are mentioned at:
46 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
47 | #
48 | # Pods/
49 |
50 | # Carthage
51 | #
52 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
53 | # Carthage/Checkouts
54 |
55 | Carthage/Build
56 |
57 | # fastlane
58 | #
59 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
60 | # screenshots whenever they are needed.
61 | # For more information about the recommended setup visit:
62 | # https://docs.fastlane.tools/best-practices/source-control/#source-control
63 |
64 | fastlane/report.xml
65 | fastlane/Preview.html
66 | fastlane/screenshots/**/*.png
67 | fastlane/test_output
--------------------------------------------------------------------------------
/Sources/RomanNumeralKit/Numeric System/Subtractive Notation/RomanNumeralConvertible.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RomanNumeralConvertible.swift
3 | // RomanNumeralKit
4 | //
5 | // Copyright © 2019 Kyle Hughes.
6 | //
7 | // Permission is hereby granted, free of charge, to any person obtaining a copy
8 | // of this software and associated documentation files (the "Software"), to deal
9 | // in the Software without restriction, including without limitation the rights
10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | // copies of the Software, and to permit persons to whom the Software is
12 | // furnished to do so, subject to the following conditions:
13 | //
14 | // The above copyright notice and this permission notice shall be included in
15 | // all copies or substantial portions of the Software.
16 | //
17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23 | // THE SOFTWARE.
24 | //
25 |
26 | /**
27 | A type that can be represented as a Roman numeral using subtractive notation.
28 | */
29 | public protocol RomanNumeralConvertible {
30 | // MARK: Instance Interface
31 |
32 | /// The Roman numeral representation of this instance using subtractive notation.
33 | var romanNumeral: RomanNumeral? { get }
34 | }
35 |
--------------------------------------------------------------------------------
/Sources/RomanNumeralKit/Numeric System/Additive Notation/AdditiveRomanNumeralConvertible.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AdditiveRomanNumeralConvertible.swift
3 | // RomanNumeralKit
4 | //
5 | // Copyright © 2019 Kyle Hughes.
6 | //
7 | // Permission is hereby granted, free of charge, to any person obtaining a copy
8 | // of this software and associated documentation files (the "Software"), to deal
9 | // in the Software without restriction, including without limitation the rights
10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | // copies of the Software, and to permit persons to whom the Software is
12 | // furnished to do so, subject to the following conditions:
13 | //
14 | // The above copyright notice and this permission notice shall be included in
15 | // all copies or substantial portions of the Software.
16 | //
17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23 | // THE SOFTWARE.
24 | //
25 |
26 | /**
27 | A type that can be represented as a Roman numeral using additive notation.
28 | */
29 | public protocol AdditiveRomanNumeralConvertible {
30 | // MARK: Instance Interface
31 |
32 | /// The Roman numeral representation of this instance using additive notation.
33 | var additiveRomanNumeral: AdditiveRomanNumeral? { get }
34 | }
35 |
--------------------------------------------------------------------------------
/Sources/RomanNumeralKit/Extensions/Foundation/DateComponents+RomanNumeral.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DateComponents+RomanNumeral.swift
3 | // RomanNumeralKit
4 | //
5 | // Copyright © 2019 Kyle Hughes.
6 | //
7 | // Permission is hereby granted, free of charge, to any person obtaining a copy
8 | // of this software and associated documentation files (the "Software"), to deal
9 | // in the Software without restriction, including without limitation the rights
10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | // copies of the Software, and to permit persons to whom the Software is
12 | // furnished to do so, subject to the following conditions:
13 | //
14 | // The above copyright notice and this permission notice shall be included in
15 | // all copies or substantial portions of the Software.
16 | //
17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23 | // THE SOFTWARE.
24 | //
25 |
26 | import Foundation
27 |
28 | extension DateComponents {
29 | // MARK: Public Instance Interface
30 |
31 | /// The year component, if present, represented as a Roman numeral.
32 | public var yearAsRomanNumeral: RomanNumeral? {
33 | guard let year = year else {
34 | return nil
35 | }
36 |
37 | return try? RomanNumeral(from: year)
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/Sources/RomanNumeralKit/Extensions/Foundation/Int+RomanNumeral.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Int+RomanNumeral.swift
3 | // RomanNumeralKit
4 | //
5 | // Copyright © 2019 Kyle Hughes.
6 | //
7 | // Permission is hereby granted, free of charge, to any person obtaining a copy
8 | // of this software and associated documentation files (the "Software"), to deal
9 | // in the Software without restriction, including without limitation the rights
10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | // copies of the Software, and to permit persons to whom the Software is
12 | // furnished to do so, subject to the following conditions:
13 | //
14 | // The above copyright notice and this permission notice shall be included in
15 | // all copies or substantial portions of the Software.
16 | //
17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23 | // THE SOFTWARE.
24 | //
25 |
26 | // MARK: - AdditiveRomanNumeralConvertible Extension
27 |
28 | extension Int: AdditiveRomanNumeralConvertible {
29 | // MARK: Public Instance Interface
30 |
31 | public var additiveRomanNumeral: AdditiveRomanNumeral? {
32 | try? AdditiveRomanNumeral(from: self)
33 | }
34 | }
35 |
36 | // MARK: - RomanNumeralConvertible Extension
37 |
38 | extension Int: RomanNumeralConvertible {
39 | // MARK: Public Instance Interface
40 |
41 | public var romanNumeral: RomanNumeral? {
42 | try? RomanNumeral(from: self)
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/Sources/RomanNumeralKit/Extensions/Foundation/String+RomanNumeral.swift:
--------------------------------------------------------------------------------
1 | //
2 | // String+RomanNumeral.swift
3 | // RomanNumeralKit
4 | //
5 | // Copyright © 2019 Kyle Hughes.
6 | //
7 | // Permission is hereby granted, free of charge, to any person obtaining a copy
8 | // of this software and associated documentation files (the "Software"), to deal
9 | // in the Software without restriction, including without limitation the rights
10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | // copies of the Software, and to permit persons to whom the Software is
12 | // furnished to do so, subject to the following conditions:
13 | //
14 | // The above copyright notice and this permission notice shall be included in
15 | // all copies or substantial portions of the Software.
16 | //
17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23 | // THE SOFTWARE.
24 | //
25 |
26 | // MARK: - AdditiveRomanNumeralConvertible Extension
27 |
28 | extension String: AdditiveRomanNumeralConvertible {
29 | // MARK: Public Instance Interface
30 |
31 | public var additiveRomanNumeral: AdditiveRomanNumeral? {
32 | try? AdditiveRomanNumeral(from: self)
33 | }
34 | }
35 |
36 | // MARK: - RomanNumeralConvertible Extension
37 |
38 | extension String: RomanNumeralConvertible {
39 | // MARK: Public Instance Interface
40 |
41 | public var romanNumeral: RomanNumeral? {
42 | try? RomanNumeral(from: self)
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/Sources/RomanNumeralKit/Extensions/Foundation/Sequence+Sorting.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Sequence+Sorting.swift
3 | // RomanNumeralKit
4 | //
5 | // Copyright © 2019 Kyle Hughes.
6 | //
7 | // Permission is hereby granted, free of charge, to any person obtaining a copy
8 | // of this software and associated documentation files (the "Software"), to deal
9 | // in the Software without restriction, including without limitation the rights
10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | // copies of the Software, and to permit persons to whom the Software is
12 | // furnished to do so, subject to the following conditions:
13 | //
14 | // The above copyright notice and this permission notice shall be included in
15 | // all copies or substantial portions of the Software.
16 | //
17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23 | // THE SOFTWARE.
24 | //
25 |
26 | extension Sequence {
27 | // MARK: Internal Instance Interface
28 |
29 | internal func isSorted(by areInIncreasingOrder: (Element, Element) throws -> Bool) rethrows -> Bool {
30 | var iterator = makeIterator()
31 |
32 | guard var previous = iterator.next() else {
33 | return true
34 | }
35 |
36 | while let current = iterator.next() {
37 | guard try areInIncreasingOrder(previous, current) else {
38 | return false
39 | }
40 |
41 | previous = current
42 | }
43 |
44 | return true
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/Tests/RomanNumeralKitTests/Numeric System/RomanNumeralErrorTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RomanNumeralErrorTests.swift
3 | // RomanNumeralKitTests
4 | //
5 | // Copyright © 2019 Kyle Hughes.
6 | //
7 | // Permission is hereby granted, free of charge, to any person obtaining a copy
8 | // of this software and associated documentation files (the "Software"), to deal
9 | // in the Software without restriction, including without limitation the rights
10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | // copies of the Software, and to permit persons to whom the Software is
12 | // furnished to do so, subject to the following conditions:
13 | //
14 | // The above copyright notice and this permission notice shall be included in
15 | // all copies or substantial portions of the Software.
16 | //
17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23 | // THE SOFTWARE.
24 | //
25 |
26 | import XCTest
27 |
28 | @testable import RomanNumeralKit
29 |
30 | class RomanNumeralErrorTests: XCTestCase {
31 | // MARK: XCTestCase Implementation
32 |
33 | override func setUp() {
34 | super.setUp()
35 | }
36 |
37 | override func tearDown() {
38 | super.tearDown()
39 | }
40 | }
41 |
42 | // MARK: - LocalizedError Tests
43 |
44 | extension RomanNumeralErrorTests {
45 | func test_errorDescription_present() {
46 | for error in RomanNumeralError.allCases {
47 | XCTAssertNotNil(error.errorDescription)
48 | XCTAssertNotEqual(error.errorDescription, "")
49 | }
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/Sources/RomanNumeralKit/Numeric System/RomanNumeralSymbolError.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RomanNumeralSymbolError.swift
3 | // RomanNumeralKit
4 | //
5 | // Copyright © 2019 Kyle Hughes.
6 | //
7 | // Permission is hereby granted, free of charge, to any person obtaining a copy
8 | // of this software and associated documentation files (the "Software"), to deal
9 | // in the Software without restriction, including without limitation the rights
10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | // copies of the Software, and to permit persons to whom the Software is
12 | // furnished to do so, subject to the following conditions:
13 | //
14 | // The above copyright notice and this permission notice shall be included in
15 | // all copies or substantial portions of the Software.
16 | //
17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23 | // THE SOFTWARE.
24 | //
25 |
26 | import Foundation
27 |
28 | /**
29 | Errors related to Roman numeral symbols.
30 | */
31 | public enum RomanNumeralSymbolError: Error {
32 | /// The given `String` is not recognized as a valid Roman numeral symbol.
33 | case unrecognizedString(string: String)
34 | }
35 |
36 | // MARK: - LocalizedError Extension
37 |
38 | extension RomanNumeralSymbolError: LocalizedError {
39 | // MARK: Public Instance Interface
40 |
41 | public var errorDescription: String? {
42 | switch self {
43 | case let .unrecognizedString(string):
44 | return "\"\(string)\" is not recognized as a valid Roman numeral symbol."
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/Sources/RomanNumeralKit/Numeric System/RomanNumeralSymbolConvertible.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RomanNumeralSymbolConvertible.swift
3 | // RomanNumeralKitTests
4 | //
5 | // Copyright © 2019 Kyle Hughes.
6 | //
7 | // Permission is hereby granted, free of charge, to any person obtaining a copy
8 | // of this software and associated documentation files (the "Software"), to deal
9 | // in the Software without restriction, including without limitation the rights
10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | // copies of the Software, and to permit persons to whom the Software is
12 | // furnished to do so, subject to the following conditions:
13 | //
14 | // The above copyright notice and this permission notice shall be included in
15 | // all copies or substantial portions of the Software.
16 | //
17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23 | // THE SOFTWARE.
24 | //
25 |
26 | /**
27 | A type that can be represented as a subtractive Roman numeral symbol using subtractive notation.
28 | */
29 | public protocol RomanNumeralSymbolConvertible: RomanNumeralSymbolsConvertible {
30 | // MARK: Instance Interface
31 |
32 | /// The Roman numeral symbol representation of this instance using subtractive notation.
33 | var romanNumeralSymbol: RomanNumeralSymbol { get }
34 | }
35 |
36 | // MARK: - RomanNumeralSymbolsConvertible Extension
37 |
38 | extension RomanNumeralSymbolConvertible {
39 | // MARK: Public Instance Interface
40 |
41 | public var romanNumeralSymbols: [RomanNumeralSymbol] {
42 | [romanNumeralSymbol]
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/Tests/RomanNumeralKitTests/Numeric System/RomanNumeralArithmeticErrorTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RomanNumeralArithmeticErrorTests.swift
3 | // RomanNumeralKitTests
4 | //
5 | // Copyright © 2019 Kyle Hughes.
6 | //
7 | // Permission is hereby granted, free of charge, to any person obtaining a copy
8 | // of this software and associated documentation files (the "Software"), to deal
9 | // in the Software without restriction, including without limitation the rights
10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | // copies of the Software, and to permit persons to whom the Software is
12 | // furnished to do so, subject to the following conditions:
13 | //
14 | // The above copyright notice and this permission notice shall be included in
15 | // all copies or substantial portions of the Software.
16 | //
17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23 | // THE SOFTWARE.
24 | //
25 |
26 | import XCTest
27 |
28 | @testable import RomanNumeralKit
29 |
30 | class RomanNumeralArithmeticErrorTests: XCTestCase {
31 | // MARK: XCTestCase Implementation
32 |
33 | override func setUp() {
34 | super.setUp()
35 | }
36 |
37 | override func tearDown() {
38 | super.tearDown()
39 | }
40 | }
41 |
42 | // MARK: - LocalizedError Tests
43 |
44 | extension RomanNumeralArithmeticErrorTests {
45 | func test_errorDescription_present() {
46 | for error in RomanNumeralArithmeticError.allCases {
47 | XCTAssertNotNil(error.errorDescription)
48 | XCTAssertNotEqual(error.errorDescription, "")
49 | }
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/Tests/RomanNumeralKitTests/Numeric System/RomanNumeralSymbolErrorTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RomanNumeralSymbolErrorTests.swift
3 | // RomanNumeralKitTests
4 | //
5 | // Copyright © 2019 Kyle Hughes.
6 | //
7 | // Permission is hereby granted, free of charge, to any person obtaining a copy
8 | // of this software and associated documentation files (the "Software"), to deal
9 | // in the Software without restriction, including without limitation the rights
10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | // copies of the Software, and to permit persons to whom the Software is
12 | // furnished to do so, subject to the following conditions:
13 | //
14 | // The above copyright notice and this permission notice shall be included in
15 | // all copies or substantial portions of the Software.
16 | //
17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23 | // THE SOFTWARE.
24 | //
25 |
26 | import XCTest
27 |
28 | @testable import RomanNumeralKit
29 |
30 | class RomanNumeralSymbolErrorTests: XCTestCase {
31 | // MARK: XCTestCase Implementation
32 |
33 | override func setUp() {
34 | super.setUp()
35 | }
36 |
37 | override func tearDown() {
38 | super.tearDown()
39 | }
40 | }
41 |
42 | // MARK: - LocalizedError Tests
43 |
44 | extension RomanNumeralSymbolErrorTests {
45 | func test_errorDescription_present() {
46 | let unrecognizedStringError = RomanNumeralSymbolError.unrecognizedString(string: "R")
47 | XCTAssertNotNil(unrecognizedStringError.errorDescription)
48 | XCTAssertNotEqual(unrecognizedStringError.errorDescription, "")
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/Sources/RomanNumeralKit/Numeric System/Additive Notation/AdditiveRomanNumeralSymbolConvertible.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AdditiveRomanNumeralSymbolConvertible.swift
3 | // RomanNumeralKit
4 | //
5 | // Copyright © 2019 Kyle Hughes.
6 | //
7 | // Permission is hereby granted, free of charge, to any person obtaining a copy
8 | // of this software and associated documentation files (the "Software"), to deal
9 | // in the Software without restriction, including without limitation the rights
10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | // copies of the Software, and to permit persons to whom the Software is
12 | // furnished to do so, subject to the following conditions:
13 | //
14 | // The above copyright notice and this permission notice shall be included in
15 | // all copies or substantial portions of the Software.
16 | //
17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23 | // THE SOFTWARE.
24 | //
25 |
26 | /**
27 | A type that can be represented as a Roman numeral symbol using additive notation.
28 | */
29 | public protocol AdditiveRomanNumeralSymbolConvertible: AdditiveRomanNumeralSymbolsConvertible {
30 | // MARK: Instance Interface
31 |
32 | /// The Roman numeral symbol representation of this instance using additive notation.
33 | var additiveRomanNumeralSymbol: RomanNumeralSymbol { get }
34 | }
35 |
36 | // MARK: - AdditiveRomanNumeralSymbolsConvertible Extension
37 |
38 | extension AdditiveRomanNumeralSymbolConvertible {
39 | // MARK: Public Instance Interface
40 |
41 | public var additiveRomanNumeralSymbols: [RomanNumeralSymbol] {
42 | [additiveRomanNumeralSymbol]
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/Sources/RomanNumeralKit/Extensions/Foundation/Calendar+RomanNumeral.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Calendar+RomanNumeral.swift
3 | // RomanNumeralKit
4 | //
5 | // Copyright © 2019 Kyle Hughes.
6 | //
7 | // Permission is hereby granted, free of charge, to any person obtaining a copy
8 | // of this software and associated documentation files (the "Software"), to deal
9 | // in the Software without restriction, including without limitation the rights
10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | // copies of the Software, and to permit persons to whom the Software is
12 | // furnished to do so, subject to the following conditions:
13 | //
14 | // The above copyright notice and this permission notice shall be included in
15 | // all copies or substantial portions of the Software.
16 | //
17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23 | // THE SOFTWARE.
24 | //
25 |
26 | import Foundation
27 |
28 | extension Calendar {
29 | // MARK: Public Instance Interface
30 |
31 | /// The current year of the `Calendar` represented as a Roman numeral.
32 | public var currentYearAsRomanNumeral: RomanNumeral? {
33 | yearAsRomanNumeral(fromDate: Date())
34 | }
35 |
36 | /**
37 | Returns the year of the given date as a Roman numeral.
38 |
39 | - Parameter date: The date whose year should be represented as a Roman numeral.
40 | - Returns: The year of the given date as a Roman numeral.
41 | */
42 | public func yearAsRomanNumeral(fromDate date: Date) -> RomanNumeral? {
43 | let dateComponents = self.dateComponents([.year], from: date)
44 |
45 | return dateComponents.yearAsRomanNumeral
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/.swiftformat:
--------------------------------------------------------------------------------
1 | # Format Options
2 |
3 | --allman false
4 | --binarygrouping 4,8
5 | --closingparen balanced
6 | --commas always
7 | --comments indent
8 | --decimalgrouping 3,6
9 | --elseposition same-line
10 | --empty void
11 | --exponentcase lowercase
12 | --exponentgrouping disabled
13 | --fractiongrouping disabled
14 | --header ignore
15 | --hexgrouping 4,8
16 | --hexliteralcase uppercase
17 | --ifdef indent
18 | --indent 4
19 | --indentcase false
20 | --importgrouping testable-bottom
21 | --linebreaks lf
22 | --octalgrouping 4,8
23 | --operatorfunc spaced
24 | --patternlet hoist
25 | --ranges spaced
26 | --self remove
27 | --semicolons inline
28 | --stripunusedargs always
29 | --trimwhitespace always
30 | --xcodeindentation disabled
31 | --wraparguments before-first
32 | --wrapcollections before-first
33 |
34 | # Rules
35 |
36 | --disable andOperator
37 | --enable anyObjectProtocol
38 | --enable blankLinesAroundMark
39 | --enable blankLinesAtEndOfScope
40 | --enable blankLinesAtStartOfScope
41 | --enable blankLinesBetweenScopes
42 | --enable consecutiveBlankLines
43 | --enable consecutiveSpaces
44 | --enable duplicateImports
45 | --enable emptyBraces
46 | --enable isEmpty
47 | --enable leadingDelimiters
48 | --enable linebreakAtEndOfFile
49 | --enable redundantBackticks
50 | --enable redundantBreak
51 | --enable redundantExtensionACL
52 | --enable redundantFileprivate
53 | --enable redundantGet
54 | --enable redundantInit
55 | --enable redundantLet
56 | --enable redundantLetError
57 | --enable redundantNilInit
58 | --enable redundantObjc
59 | --enable redundantParens
60 | --enable redundantPattern
61 | --enable redundantRawValues
62 | --enable redundantReturn
63 | --enable redundantVoidReturnType
64 | --enable spaceAroundBraces
65 | --enable spaceAroundBrackets
66 | --enable spaceAroundComments
67 | --enable spaceAroundGenerics
68 | --enable spaceAroundParens
69 | --enable spaceInsideBraces
70 | --enable spaceInsideBrackets
71 | --enable spaceInsideComments
72 | --enable spaceInsideGenerics
73 | --enable spaceInsideParens
74 | --enable specifiers
75 | --enable strongOutlets
76 | --enable strongifiedSelf
77 | --enable todos
78 | --enable typeSugar
--------------------------------------------------------------------------------
/Sources/RomanNumeralKit/Numeric System/Additive Notation/AdditiveRomanNumeralSymbolsConvertible.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AdditiveRomanNumeralSymbolsConvertible.swift
3 | // RomanNumeralKit
4 | //
5 | // Copyright © 2019 Kyle Hughes.
6 | //
7 | // Permission is hereby granted, free of charge, to any person obtaining a copy
8 | // of this software and associated documentation files (the "Software"), to deal
9 | // in the Software without restriction, including without limitation the rights
10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | // copies of the Software, and to permit persons to whom the Software is
12 | // furnished to do so, subject to the following conditions:
13 | //
14 | // The above copyright notice and this permission notice shall be included in
15 | // all copies or substantial portions of the Software.
16 | //
17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23 | // THE SOFTWARE.
24 | //
25 |
26 | /**
27 | A type that can be represented as a collection of Roman numeral symbols using additive notation.
28 | */
29 | public protocol AdditiveRomanNumeralSymbolsConvertible: AdditiveRomanNumeralConvertible {
30 | // MARK: Instance Interface
31 |
32 | /// The representation of this instance as a collection of Roman numeral symbols using additive notation.
33 | var additiveRomanNumeralSymbols: [RomanNumeralSymbol] { get }
34 | }
35 |
36 | // MARK: - AdditiveRomanNumeralConvertible Extension
37 |
38 | extension AdditiveRomanNumeralSymbolsConvertible {
39 | // MARK: Public Instance Properties
40 |
41 | public var additiveRomanNumeral: AdditiveRomanNumeral? {
42 | try? AdditiveRomanNumeral(symbols: additiveRomanNumeralSymbols)
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: swift
2 | os: osx
3 | osx_image: xcode11.2
4 | branches:
5 | only:
6 | - mainline
7 |
8 | cache:
9 | directories:
10 | # SwiftPM
11 | - .build
12 |
13 | env:
14 | global:
15 | - LC_CTYPE=en_US.UTF-8
16 | - LANG=en_US.UTF-8
17 | - FRAMEWORK_SCHEME=RomanNumeralKit
18 | matrix:
19 | - DESTINATION="OS=6.1,name=Apple Watch Series 5 - 44mm" SCHEME="$FRAMEWORK_SCHEME" RUN_TESTS="NO"
20 | - DESTINATION="OS=5.3,name=Apple Watch Series 4 - 44mm" SCHEME="$FRAMEWORK_SCHEME" RUN_TESTS="NO"
21 | - DESTINATION="OS=4.2,name=Apple Watch Series 3 - 42mm" SCHEME="$FRAMEWORK_SCHEME" RUN_TESTS="NO"
22 | - DESTINATION="OS=3.2,name=Apple Watch - 38mm" SCHEME="$FRAMEWORK_SCHEME" RUN_TESTS="NO"
23 |
24 | - DESTINATION="OS=13.2.2,name=iPhone 11" SCHEME="$FRAMEWORK_SCHEME" RUN_TESTS="YES"
25 | - DESTINATION="OS=12.4,name=iPhone XS" SCHEME="$FRAMEWORK_SCHEME" RUN_TESTS="YES"
26 | - DESTINATION="OS=11.4,name=iPhone X" SCHEME="$FRAMEWORK_SCHEME" RUN_TESTS="YES"
27 | - DESTINATION="OS=10.3.1,name=iPhone 7" SCHEME="$FRAMEWORK_SCHEME" RUN_TESTS="YES"
28 |
29 | - DESTINATION="OS=13.2,name=Apple TV 4K" SCHEME="$FRAMEWORK_SCHEME" RUN_TESTS="YES"
30 | - DESTINATION="OS=12.4,name=Apple TV 4K" SCHEME="$FRAMEWORK_SCHEME" RUN_TESTS="YES"
31 | - DESTINATION="OS=11.4,name=Apple TV 4K" SCHEME="$FRAMEWORK_SCHEME" RUN_TESTS="YES"
32 | - DESTINATION="OS=10.2,name=Apple TV 1080p" SCHEME="$FRAMEWORK_SCHEME" RUN_TESTS="YES"
33 |
34 | - DESTINATION="arch=x86_64" SCHEME="$FRAMEWORK_SCHEME" RUN_TESTS="YES"
35 | script:
36 | - set -o pipefail
37 | - xcodebuild -version
38 | - xcodebuild -showsdks
39 | - instruments -s devices
40 |
41 | # Build framework in Debug and run tests if specified
42 | - if [ $RUN_TESTS == "YES" ]; then
43 | xcodebuild -scheme "$SCHEME" -destination "$DESTINATION" -configuration Debug ONLY_ACTIVE_ARCH=NO test | xcpretty -c;
44 | else
45 | xcodebuild -scheme "$SCHEME" -destination "$DESTINATION" -configuration Debug ONLY_ACTIVE_ARCH=NO build | xcpretty -c;
46 | fi
47 |
48 | after_success:
49 | - bash <(curl -s https://codecov.io/bash) -J 'RomanNumeralKit'
--------------------------------------------------------------------------------
/Tests/RomanNumeralKitTests/Extensions/Foundation/IntTests+RomanNumeral.swift:
--------------------------------------------------------------------------------
1 | //
2 | // IntTests+AdditiveRomanNumeralConvertible.swift
3 | // RomanNumeralKitTests
4 | //
5 | // Copyright © 2019 Kyle Hughes.
6 | //
7 | // Permission is hereby granted, free of charge, to any person obtaining a copy
8 | // of this software and associated documentation files (the "Software"), to deal
9 | // in the Software without restriction, including without limitation the rights
10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | // copies of the Software, and to permit persons to whom the Software is
12 | // furnished to do so, subject to the following conditions:
13 | //
14 | // The above copyright notice and this permission notice shall be included in
15 | // all copies or substantial portions of the Software.
16 | //
17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23 | // THE SOFTWARE.
24 | //
25 |
26 | import XCTest
27 |
28 | @testable import RomanNumeralKit
29 |
30 | class IntTestsPlusRomanNumeral: XCTestCase {
31 | // MARK: XCTestCase Implementation
32 |
33 | override func setUp() {
34 | super.setUp()
35 | }
36 |
37 | override func tearDown() {
38 | super.tearDown()
39 | }
40 | }
41 |
42 | // MARK: - AdditiveRomanNumeralConvertible Tests
43 |
44 | extension IntTestsPlusRomanNumeral {
45 | func test_additiveRomanNumeral() {
46 | XCTAssertEqual(1.additiveRomanNumeral, AdditiveRomanNumeral.minimum)
47 | XCTAssertEqual(3999.additiveRomanNumeral, AdditiveRomanNumeral.maximum)
48 | }
49 | }
50 |
51 | // MARK: - RomanNumeralConvertible Tests
52 |
53 | extension IntTestsPlusRomanNumeral {
54 | func test_romanNumeral() {
55 | XCTAssertEqual(1.romanNumeral, RomanNumeral.minimum)
56 | XCTAssertEqual(3999.romanNumeral, RomanNumeral.maximum)
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/Tests/RomanNumeralKitTests/Extensions/Foundation/StringTests+RomanNumeral.swift:
--------------------------------------------------------------------------------
1 | //
2 | // StringTests+RomanNumeralSymbol.swift
3 | // RomanNumeralKitTests
4 | //
5 | // Copyright © 2019 Kyle Hughes.
6 | //
7 | // Permission is hereby granted, free of charge, to any person obtaining a copy
8 | // of this software and associated documentation files (the "Software"), to deal
9 | // in the Software without restriction, including without limitation the rights
10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | // copies of the Software, and to permit persons to whom the Software is
12 | // furnished to do so, subject to the following conditions:
13 | //
14 | // The above copyright notice and this permission notice shall be included in
15 | // all copies or substantial portions of the Software.
16 | //
17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23 | // THE SOFTWARE.
24 | //
25 |
26 | import XCTest
27 |
28 | @testable import RomanNumeralKit
29 |
30 | class StringTestsPlusRomanNumeralSymbol: XCTestCase {
31 | // MARK: XCTestCase Implementation
32 |
33 | override func setUp() {
34 | super.setUp()
35 | }
36 |
37 | override func tearDown() {
38 | super.tearDown()
39 | }
40 | }
41 |
42 | // MARK: - AdditiveRomanNumeralConvertible Extension
43 |
44 | extension StringTestsPlusRomanNumeralSymbol {
45 | func test_additiveRomanNumeral() {
46 | XCTAssertEqual("I".additiveRomanNumeral, AdditiveRomanNumeral.minimum)
47 | XCTAssertEqual("MMMDCCCCLXXXXVIIII".additiveRomanNumeral, AdditiveRomanNumeral.maximum)
48 | }
49 | }
50 |
51 | // MARK: - RomanNumeralConvertible
52 |
53 | extension StringTestsPlusRomanNumeralSymbol {
54 | func test_romanNumeral() {
55 | XCTAssertEqual("I".romanNumeral, RomanNumeral.minimum)
56 | XCTAssertEqual("MMMCMXCIX".romanNumeral, RomanNumeral.maximum)
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/Sources/RomanNumeralKit/Numeric System/Subtractive Notation/SubtractiveRomanNumeralSymbolConvertible.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SubtractiveRomanNumeralSymbolConvertible.swift
3 | // RomanNumeralKit
4 | //
5 | // Copyright © 2019 Kyle Hughes.
6 | //
7 | // Permission is hereby granted, free of charge, to any person obtaining a copy
8 | // of this software and associated documentation files (the "Software"), to deal
9 | // in the Software without restriction, including without limitation the rights
10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | // copies of the Software, and to permit persons to whom the Software is
12 | // furnished to do so, subject to the following conditions:
13 | //
14 | // The above copyright notice and this permission notice shall be included in
15 | // all copies or substantial portions of the Software.
16 | //
17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23 | // THE SOFTWARE.
24 | //
25 |
26 | /**
27 | A type that can be represented as a subtractive Roman numeral symbol using subtractive notation.
28 |
29 | This differs from `RomanNumeralSymbolConvertible` because this representation is using the non-standard
30 | `SubtractiveRomanNumeralSymbol` representation.
31 | */
32 | public protocol SubtractiveRomanNumeralSymbolConvertible: SubtractiveRomanNumeralSymbolsConvertible {
33 | // MARK: Instance Interface
34 |
35 | /// The subtractive Roman numeral symbol representation of this instance using subtractive notation.
36 | var subtractiveRomanNumeralSymbol: SubtractiveRomanNumeralSymbol { get }
37 | }
38 |
39 | // MARK: - SubtractiveRomanNumeralSymbolsConvertible Extension
40 |
41 | extension SubtractiveRomanNumeralSymbolConvertible {
42 | // MARK: Public Instance Interface
43 |
44 | public var subtractiveRomanNumeralSymbols: [SubtractiveRomanNumeralSymbol] {
45 | [subtractiveRomanNumeralSymbol]
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/Sources/RomanNumeralKit/Numeric System/Subtractive Notation/SubtractiveRomanNumeralSymbolsConvertible.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SubtractiveRomanNumeralSymbolConvertible.swift
3 | // RomanNumeralKit
4 | //
5 | // Copyright © 2019 Kyle Hughes.
6 | //
7 | // Permission is hereby granted, free of charge, to any person obtaining a copy
8 | // of this software and associated documentation files (the "Software"), to deal
9 | // in the Software without restriction, including without limitation the rights
10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | // copies of the Software, and to permit persons to whom the Software is
12 | // furnished to do so, subject to the following conditions:
13 | //
14 | // The above copyright notice and this permission notice shall be included in
15 | // all copies or substantial portions of the Software.
16 | //
17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23 | // THE SOFTWARE.
24 | //
25 |
26 | /**
27 | A type that can be represented as a collection of subtractive Roman numeral symbols using subtractive notation.
28 |
29 | This differs from `RomanNumeralSymbolsConvertible` because this representation is using the non-standard
30 | `SubtractiveRomanNumeralSymbol` representation.
31 | */
32 | public protocol SubtractiveRomanNumeralSymbolsConvertible: RomanNumeralConvertible {
33 | // MARK: Instance Interface
34 |
35 | /// The representation of this instance as a collection of subtractive Roman numeral symbols using subtractive
36 | /// notation.
37 | var subtractiveRomanNumeralSymbols: [SubtractiveRomanNumeralSymbol] { get }
38 | }
39 |
40 | // MARK: - RomanNumeralConvertible Extension
41 |
42 | extension SubtractiveRomanNumeralSymbolsConvertible {
43 | // MARK: Public Instance Interface
44 |
45 | public var romanNumeral: RomanNumeral? {
46 | try? RomanNumeral(subtractiveSymbols: subtractiveRomanNumeralSymbols)
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/Sources/RomanNumeralKit/Numeric System/RomanNumeralSymbolsConvertible.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RomanNumeralSymbolsConvertible.swift
3 | // RomanNumeralKitTests
4 | //
5 | // Copyright © 2019 Kyle Hughes.
6 | //
7 | // Permission is hereby granted, free of charge, to any person obtaining a copy
8 | // of this software and associated documentation files (the "Software"), to deal
9 | // in the Software without restriction, including without limitation the rights
10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | // copies of the Software, and to permit persons to whom the Software is
12 | // furnished to do so, subject to the following conditions:
13 | //
14 | // The above copyright notice and this permission notice shall be included in
15 | // all copies or substantial portions of the Software.
16 | //
17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23 | // THE SOFTWARE.
24 | //
25 |
26 | /**
27 | A type that can be represented as a collection of Roman numeral symbols using subtractive notation.
28 | */
29 | public protocol RomanNumeralSymbolsConvertible: SubtractiveRomanNumeralSymbolsConvertible {
30 | // MARK: Instance Interface
31 |
32 | /// The representation of this instance as a collection of Roman numeral symbols using subtractive notation.
33 | var romanNumeralSymbols: [RomanNumeralSymbol] { get }
34 | }
35 |
36 | // MARK: - RomanNumeralConvertible Extension
37 |
38 | extension RomanNumeralSymbolsConvertible {
39 | // MARK: Public Instance Interface
40 |
41 | public var romanNumeral: RomanNumeral? {
42 | try? RomanNumeral(symbols: romanNumeralSymbols)
43 | }
44 | }
45 |
46 | // MARK: - SubtractiveRomanNumeralSymbolsConvertible Extension
47 |
48 | extension RomanNumeralSymbolsConvertible {
49 | // MARK: Public Instance Interface
50 |
51 | public var subtractiveRomanNumeralSymbols: [SubtractiveRomanNumeralSymbol] {
52 | AdditiveRomanNumeral.convert(toSymbolEquivalentSubtractiveSymbols: romanNumeralSymbols)
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/Sources/RomanNumeralKit/Numeric System/RomanNumeralError.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RomanNumeralError.swift
3 | // RomanNumeralKit
4 | //
5 | // Copyright © 2019 Kyle Hughes.
6 | //
7 | // Permission is hereby granted, free of charge, to any person obtaining a copy
8 | // of this software and associated documentation files (the "Software"), to deal
9 | // in the Software without restriction, including without limitation the rights
10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | // copies of the Software, and to permit persons to whom the Software is
12 | // furnished to do so, subject to the following conditions:
13 | //
14 | // The above copyright notice and this permission notice shall be included in
15 | // all copies or substantial portions of the Software.
16 | //
17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23 | // THE SOFTWARE.
24 | //
25 |
26 | import Foundation
27 |
28 | /**
29 | Errors related to Roman numerals.
30 |
31 | For errors specific to Roman numeral symbols please see `RomanNumeralSymbolError`.
32 | */
33 | public enum RomanNumeralError: Error, CaseIterable {
34 | /// The given symbols for the Roman numeral are out-of-order for the notation.
35 | case symbolsOutOfOrder
36 |
37 | /// A Roman numeral's value cannot be greater than its notation's maximum value.
38 | case valueGreaterThanMaximum
39 |
40 | /// A Roman numeral's value cannot be less than its notation's minimum value.
41 | case valueLessThanMinimum
42 | }
43 |
44 | // MARK: - LocalizedError Extension
45 |
46 | extension RomanNumeralError: LocalizedError {
47 | // MARK: Public Instance Interface
48 |
49 | public var errorDescription: String? {
50 | switch self {
51 | case .symbolsOutOfOrder:
52 | return "The given symbols for the Roman numeral are out-of-order for the notation."
53 | case .valueGreaterThanMaximum:
54 | return "A Roman numeral's value cannot be greater than its notation's maximum value."
55 | case .valueLessThanMinimum:
56 | return "A Roman numeral's value cannot be less than its notation's minimum value."
57 | }
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/Sources/RomanNumeralKit/Numeric System/RomanNumeralTallyMark.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RomanNumeralTallyMark.swift
3 | // RomanNumeralKit
4 | //
5 | // Copyright © 2019 Kyle Hughes.
6 | //
7 | // Permission is hereby granted, free of charge, to any person obtaining a copy
8 | // of this software and associated documentation files (the "Software"), to deal
9 | // in the Software without restriction, including without limitation the rights
10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | // copies of the Software, and to permit persons to whom the Software is
12 | // furnished to do so, subject to the following conditions:
13 | //
14 | // The above copyright notice and this permission notice shall be included in
15 | // all copies or substantial portions of the Software.
16 | //
17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23 | // THE SOFTWARE.
24 | //
25 |
26 | /**
27 | A tally mark used for counting.
28 |
29 | This is the atomic component that backs the value of each Roman numeral symbol. Tally marks are typically counted in
30 | groups (see `RomanNumeralTallyMarkGroup`).
31 |
32 | The tally mark should not be confused with the Roman numeral symbol `I`, although both share the same numeric value.
33 | The tally mark is represented using the ASCII "vertical bar" character.
34 |
35 | - SeeAlso: https://theasciicode.com.ar/ascii-printable-characters/vertical-bar-vbar-vertical-line-vertical-slash-ascii-code-124.html
36 | */
37 | public struct RomanNumeralTallyMark: Equatable {
38 | // MARK: Public Initialization
39 |
40 | /**
41 | Creates a tally mark.
42 | */
43 | public init() {}
44 | }
45 |
46 | // MARK: - CustomStringConvertible Extension
47 |
48 | extension RomanNumeralTallyMark: CustomStringConvertible {
49 | // MARK: Public Instance Interface
50 |
51 | public var description: String {
52 | "|"
53 | }
54 | }
55 |
56 | // MARK: - CustomDebugStringConvertible Extension
57 |
58 | extension RomanNumeralTallyMark: CustomDebugStringConvertible {
59 | // MARK: Public Instance Interface
60 |
61 | public var debugDescription: String {
62 | description
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/Tests/RomanNumeralKitTests/Extensions/Foundation/DateComponentsTests+RomanNumeral.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DateComponentsTests+RomanNumeral.swift
3 | // RomanNumeralKitTests
4 | //
5 | // Copyright © 2019 Kyle Hughes.
6 | //
7 | // Permission is hereby granted, free of charge, to any person obtaining a copy
8 | // of this software and associated documentation files (the "Software"), to deal
9 | // in the Software without restriction, including without limitation the rights
10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | // copies of the Software, and to permit persons to whom the Software is
12 | // furnished to do so, subject to the following conditions:
13 | //
14 | // The above copyright notice and this permission notice shall be included in
15 | // all copies or substantial portions of the Software.
16 | //
17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23 | // THE SOFTWARE.
24 | //
25 |
26 | import XCTest
27 |
28 | @testable import RomanNumeralKit
29 |
30 | class DateComponentsTestsPlusRomanNumeral: XCTestCase {
31 | // MARK: XCTestCase Implementation
32 |
33 | override func setUp() {
34 | super.setUp()
35 | }
36 |
37 | override func tearDown() {
38 | super.tearDown()
39 | }
40 |
41 | // MARK: Tests
42 |
43 | func test_yearAsRomanNumeral_success() {
44 | // Given...
45 |
46 | let date = Date(timeIntervalSince1970: 1_012_737_600) // 2002-02-03
47 | let yearDateComponents = Calendar.current.dateComponents([.year], from: date)
48 |
49 | let expectedRomanNumeral = MMII
50 |
51 | // When...
52 |
53 | let actualRomanNumeral = yearDateComponents.yearAsRomanNumeral
54 |
55 | // Then...
56 |
57 | XCTAssertEqual(actualRomanNumeral, expectedRomanNumeral)
58 | }
59 |
60 | func test_yearAsRomanNumeral_noYear() {
61 | // Given...
62 |
63 | let date = Date(timeIntervalSince1970: 1_012_737_600) // 2002-02-03
64 | let yearDateComponents = Calendar.current.dateComponents([.month], from: date)
65 |
66 | // Then...
67 |
68 | XCTAssertNil(yearDateComponents.yearAsRomanNumeral)
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/Tests/RomanNumeralKitTests/Extensions/Foundation/CalendarTests+RomanNumeral.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CalendarTests+RomanNumeral.swift
3 | // RomanNumeralKitTests
4 | //
5 | // Copyright © 2019 Kyle Hughes.
6 | //
7 | // Permission is hereby granted, free of charge, to any person obtaining a copy
8 | // of this software and associated documentation files (the "Software"), to deal
9 | // in the Software without restriction, including without limitation the rights
10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | // copies of the Software, and to permit persons to whom the Software is
12 | // furnished to do so, subject to the following conditions:
13 | //
14 | // The above copyright notice and this permission notice shall be included in
15 | // all copies or substantial portions of the Software.
16 | //
17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23 | // THE SOFTWARE.
24 | //
25 |
26 | import XCTest
27 |
28 | @testable import RomanNumeralKit
29 |
30 | class CalendarTestsPlusRomanNumeral: XCTestCase {
31 | // MARK: XCTestCase Implementation
32 |
33 | override func setUp() {
34 | super.setUp()
35 | }
36 |
37 | override func tearDown() {
38 | super.tearDown()
39 | }
40 |
41 | // MARK: Tests
42 |
43 | func test_currentYearAsRomanNumeral() {
44 | // Given...
45 |
46 | let currentYear = Calendar.current.component(.year, from: Date())
47 | let expectedRomanNumeral = try? RomanNumeral(from: currentYear)
48 |
49 | // When...
50 |
51 | let actualRomanNumeral = Calendar.current.currentYearAsRomanNumeral
52 |
53 | // Then...
54 |
55 | XCTAssertNotNil(actualRomanNumeral)
56 | XCTAssertEqual(actualRomanNumeral, expectedRomanNumeral)
57 | }
58 |
59 | func test_yearAsRomanNumeral() {
60 | // Given...
61 |
62 | let date = Date(timeIntervalSince1970: 712_800_001) // 1992-08-03
63 |
64 | let expectedRomanNumeral = MCMXCII
65 |
66 | // When...
67 |
68 | let actualRomanNumeral = Calendar.current.yearAsRomanNumeral(fromDate: date)
69 |
70 | // Then...
71 |
72 | XCTAssertEqual(actualRomanNumeral, expectedRomanNumeral)
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/Sources/RomanNumeralKit/Numeric System/RomanNumeralArithmeticError.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RomanNumeralArithmeticError.swift
3 | // RomanNumeralKit
4 | //
5 | // Copyright © 2019 Kyle Hughes.
6 | //
7 | // Permission is hereby granted, free of charge, to any person obtaining a copy
8 | // of this software and associated documentation files (the "Software"), to deal
9 | // in the Software without restriction, including without limitation the rights
10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | // copies of the Software, and to permit persons to whom the Software is
12 | // furnished to do so, subject to the following conditions:
13 | //
14 | // The above copyright notice and this permission notice shall be included in
15 | // all copies or substantial portions of the Software.
16 | //
17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23 | // THE SOFTWARE.
24 | //
25 |
26 | import Foundation
27 |
28 | /**
29 | Errors related to Roman numerals arithmetic operations.
30 | */
31 | public enum RomanNumeralArithmeticError: Error, CaseIterable {
32 | /// An unknown error occurred during the addition operation of two Roman numerals.
33 | case ambiguousAdditionError
34 |
35 | /// An unknown error occurred during the multiplication operation of two Roman numerals.
36 | case ambiguousMultiplicationError
37 |
38 | /// An unknown error occurred during the subtraction operation of two Roman numerals.
39 | case ambiguousSubtractionError
40 |
41 | /// Subtraction that will result in a negative value is not supported.
42 | case subtractionWhereRightValueIsGreaterThanLeftValue
43 | }
44 |
45 | // MARK: - LocalizedError Extension
46 |
47 | extension RomanNumeralArithmeticError: LocalizedError {
48 | // MARK: Public Instance Interface
49 |
50 | public var errorDescription: String? {
51 | switch self {
52 | case .ambiguousAdditionError:
53 | return "An unknown error occurred during the addition operation of two Roman numerals."
54 | case .ambiguousMultiplicationError:
55 | return "An unknown error occurred during the multiplication operation of two Roman numerals."
56 | case .ambiguousSubtractionError:
57 | return "An unknown error occurred during the subtraction operation of two Roman numerals."
58 | case .subtractionWhereRightValueIsGreaterThanLeftValue:
59 | return "Subtraction that will result in a negative value is not supported."
60 | }
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/Sources/RomanNumeralKit/Numeric System/RomanNumeralSymbolProtocol.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RomanNumeralSymbolProtocol.swift
3 | // RomanNumeralKit
4 | //
5 | // Copyright © 2019 Kyle Hughes.
6 | //
7 | // Permission is hereby granted, free of charge, to any person obtaining a copy
8 | // of this software and associated documentation files (the "Software"), to deal
9 | // in the Software without restriction, including without limitation the rights
10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | // copies of the Software, and to permit persons to whom the Software is
12 | // furnished to do so, subject to the following conditions:
13 | //
14 | // The above copyright notice and this permission notice shall be included in
15 | // all copies or substantial portions of the Software.
16 | //
17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23 | // THE SOFTWARE.
24 | //
25 |
26 | /**
27 | Roman numeral symbols are a collection of letters from the Latin alphabet that are used to represent numbers in the
28 | numeric system of ancient Rome. Each symbol is shorthand for a specific number of tally marks.
29 |
30 | The backing values of the Roman numeral symbols, as found in their `rawValue`, are `RomanNumeralTallyMarkGroups` which
31 | are used to collect all of the corresponding `RomanNumeralTallyMark`s. Any representations of the symbols as Western
32 | Arabic numerals (e.g. `Int`) are conversions made by counting these tally marks.
33 | */
34 | public protocol RomanNumeralSymbolProtocol: Comparable,
35 | CustomStringConvertible,
36 | CustomDebugStringConvertible,
37 | RawRepresentable where RawValue == RomanNumeralTallyMarkGroup {
38 | // MARK: Initialization
39 |
40 | /**
41 | Creates a Roman numeral symbol from a `String` value if there is a corresponding symbol for the `String`.
42 |
43 | - Parameter stringValue: The string representation of the desired Roman numeral symbol.
44 | - Throws: `RomanNumeralSymbolError.unrecognizedString` if the given string does not match a known Roman
45 | numeral symbol.
46 | */
47 | init(from stringValue: String) throws
48 |
49 | // MARK: Static Interface
50 |
51 | /// All of the Roman numeral symbols, represented in ascending order by value.
52 | static var allSymbolsAscending: [Self] { get }
53 |
54 | /// All of the Roman numeral symbols, represented in descending order by value.
55 | static var allSymbolsDescending: [Self] { get }
56 |
57 | // MARK: Instance Interface
58 |
59 | /**
60 | The Roman numeral symbol whose value is next-lowest.
61 |
62 | For example, `V` is the lesser symbol to `X`, but `V` is not the lesser symbol to `L`.
63 |
64 | - Note: Both `I` and `nulla` will return `nil` because 0 is technically not a recognized value.
65 | */
66 | var lesserSymbol: Self? { get }
67 |
68 | /// The `String` representation of the Roman numeral symbol.
69 | var stringValue: String { get }
70 | }
71 |
72 | // MARK: - Comparable Extension
73 |
74 | extension RomanNumeralSymbolProtocol {
75 | // MARK: Public Static Interface
76 |
77 | public static func < (lhs: Self, rhs: Self) -> Bool {
78 | lhs.rawValue.tallyMarks.count < rhs.rawValue.tallyMarks.count
79 | }
80 | }
81 |
82 | // MARK: - CustomDebugStringConvertible Extension
83 |
84 | extension RomanNumeralSymbolProtocol {
85 | // MARK: Public Instance Interface
86 |
87 | public var debugDescription: String {
88 | String(stringValue)
89 | }
90 | }
91 |
92 | // MARK: - CustomStringConvertible Extension
93 |
94 | extension RomanNumeralSymbolProtocol {
95 | // MARK: Public Instance Interface
96 |
97 | public var description: String {
98 | String(stringValue)
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/RomanNumeralKit.xcodeproj/xcshareddata/xcschemes/RomanNumeralKit.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
32 |
33 |
39 |
40 |
41 |
42 |
44 |
50 |
51 |
52 |
53 |
54 |
60 |
61 |
62 |
63 |
64 |
65 |
75 |
76 |
82 |
83 |
84 |
85 |
86 |
87 |
93 |
94 |
100 |
101 |
102 |
103 |
105 |
106 |
109 |
110 |
111 |
--------------------------------------------------------------------------------
/Sources/RomanNumeralKit/Numeric System/Additive Notation/AdditiveRomanNumeralSymbolCondenser.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AdditiveRomanNumeralSymbolCondenser.swift
3 | // RomanNumeralKit
4 | //
5 | // Copyright © 2019 Kyle Hughes.
6 | //
7 | // Permission is hereby granted, free of charge, to any person obtaining a copy
8 | // of this software and associated documentation files (the "Software"), to deal
9 | // in the Software without restriction, including without limitation the rights
10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | // copies of the Software, and to permit persons to whom the Software is
12 | // furnished to do so, subject to the following conditions:
13 | //
14 | // The above copyright notice and this permission notice shall be included in
15 | // all copies or substantial portions of the Software.
16 | //
17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23 | // THE SOFTWARE.
24 | //
25 |
26 | internal struct AdditiveRomanNumeralSymbolCondenser {
27 | private(set) var countForI: Int
28 | private(set) var countForV: Int
29 | private(set) var countForX: Int
30 | private(set) var countForL: Int
31 | private(set) var countForC: Int
32 | private(set) var countForD: Int
33 | private(set) var countForM: Int
34 |
35 | // MARK: Internal Initialization
36 |
37 | internal init() {
38 | countForI = 0
39 | countForV = 0
40 | countForX = 0
41 | countForL = 0
42 | countForC = 0
43 | countForD = 0
44 | countForM = 0
45 | }
46 |
47 | // MARK: Internal Instance Interface
48 |
49 | internal mutating func combine(symbols: [RomanNumeralSymbol]) {
50 | symbols.forEach { record(symbol: $0) }
51 | }
52 |
53 | internal mutating func finalize() -> [RomanNumeralSymbol] {
54 | RomanNumeralSymbol
55 | .allSymbolsAscending
56 | .forEach { condenseAndRecord(symbol: $0) }
57 |
58 | return RomanNumeralSymbol
59 | .allSymbolsDescending
60 | .map { ($0, getCount(forSymbol: $0)) }
61 | .map(Array.init)
62 | .reduce([], +)
63 | }
64 |
65 | // MARK: Private Static Interface
66 |
67 | private static func condense(symbol: RomanNumeralSymbol, ofCount count: Int) -> [RomanNumeralSymbol] {
68 | let allSymbols = RomanNumeralSymbol.allSymbolsAscending
69 |
70 | guard let symbolIndex = allSymbols.firstIndex(of: symbol) else {
71 | return []
72 | }
73 |
74 | let nextHighestSymbolIndex = symbolIndex + 1
75 |
76 | guard nextHighestSymbolIndex < allSymbols.count else {
77 | return Array(repeating: symbol, count: count)
78 | }
79 |
80 | let nextHighestSymbol = allSymbols[nextHighestSymbolIndex]
81 | let nextHighestSymbolAsCurrentSymbols = nextHighestSymbol.expandedIntoLesserSymbol
82 | let nextHighestSymbolQuantity = count / nextHighestSymbolAsCurrentSymbols.count
83 | let nextHighestSymbols = Array(repeating: nextHighestSymbol, count: nextHighestSymbolQuantity)
84 |
85 | let remainingSymbolQuanity = count % nextHighestSymbolAsCurrentSymbols.count
86 | let remainingSymbols = Array(repeating: symbol, count: remainingSymbolQuanity)
87 |
88 | return nextHighestSymbols + remainingSymbols
89 | }
90 |
91 | // MARK: Private Instance Interface
92 |
93 | private mutating func clearRecord(forSymbol symbol: RomanNumeralSymbol) {
94 | switch symbol {
95 | case .nulla:
96 | break
97 | case .I:
98 | countForI = 0
99 | case .V:
100 | countForV = 0
101 | case .X:
102 | countForX = 0
103 | case .L:
104 | countForL = 0
105 | case .C:
106 | countForC = 0
107 | case .D:
108 | countForD = 0
109 | case .M:
110 | countForM = 0
111 | }
112 | }
113 |
114 | private mutating func condenseAndRecord(symbol: RomanNumeralSymbol) {
115 | let count = getCount(forSymbol: symbol)
116 | let condensedSymbols = Self.condense(symbol: symbol, ofCount: count)
117 | clearRecord(forSymbol: symbol)
118 | condensedSymbols.forEach { record(symbol: $0) }
119 | }
120 |
121 | private mutating func getCount(forSymbol symbol: RomanNumeralSymbol) -> Int {
122 | switch symbol {
123 | case .nulla:
124 | return 0
125 | case .I:
126 | return countForI
127 | case .V:
128 | return countForV
129 | case .X:
130 | return countForX
131 | case .L:
132 | return countForL
133 | case .C:
134 | return countForC
135 | case .D:
136 | return countForD
137 | case .M:
138 | return countForM
139 | }
140 | }
141 |
142 | private mutating func record(symbol: RomanNumeralSymbol) {
143 | switch symbol {
144 | case .nulla:
145 | break
146 | case .I:
147 | countForI += 1
148 | case .V:
149 | countForV += 1
150 | case .X:
151 | countForX += 1
152 | case .L:
153 | countForL += 1
154 | case .C:
155 | countForC += 1
156 | case .D:
157 | countForD += 1
158 | case .M:
159 | countForM += 1
160 | }
161 | }
162 | }
163 |
--------------------------------------------------------------------------------
/Sources/RomanNumeralKit/Numeric System/RomanNumeralTallyMarkGroup.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RomanNumeralTallyMarkGroup.swift
3 | // RomanNumeralKit
4 | //
5 | // Copyright © 2019 Kyle Hughes.
6 | //
7 | // Permission is hereby granted, free of charge, to any person obtaining a copy
8 | // of this software and associated documentation files (the "Software"), to deal
9 | // in the Software without restriction, including without limitation the rights
10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | // copies of the Software, and to permit persons to whom the Software is
12 | // furnished to do so, subject to the following conditions:
13 | //
14 | // The above copyright notice and this permission notice shall be included in
15 | // all copies or substantial portions of the Software.
16 | //
17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23 | // THE SOFTWARE.
24 | //
25 |
26 | /**
27 | A collection of tally marks that serves as the backing value for a Roman numeral symbol.
28 |
29 | For example, `IIIII` contains five tally marks, so the value of the group is five.
30 | */
31 | public struct RomanNumeralTallyMarkGroup: Equatable {
32 | // MARK: Public Static Properties
33 |
34 | /// A tally mark group with 0 tally marks.
35 | public static let nulla = RomanNumeralTallyMarkGroup(numberOfTallyMarks: 0)
36 |
37 | /// A tally mark group with 1 tally mark.
38 | public static let one = RomanNumeralTallyMarkGroup(numberOfTallyMarks: 1)
39 |
40 | /// A tally mark group with 4 tally marks.
41 | public static let four = RomanNumeralTallyMarkGroup(numberOfTallyMarks: 4)
42 |
43 | /// A tally mark group with 5 tally marks.
44 | public static let five = RomanNumeralTallyMarkGroup(numberOfTallyMarks: 5)
45 |
46 | /// A tally mark group with 9 tally marks.
47 | public static let nine = RomanNumeralTallyMarkGroup(numberOfTallyMarks: 9)
48 |
49 | /// A tally mark group with 10 tally marks.
50 | public static let ten = RomanNumeralTallyMarkGroup(numberOfTallyMarks: 10)
51 |
52 | /// A tally mark group with 40 tally marks.
53 | public static let forty = RomanNumeralTallyMarkGroup(numberOfTallyMarks: 40)
54 |
55 | /// A tally mark group with 50 tally marks.
56 | public static let fifty = RomanNumeralTallyMarkGroup(numberOfTallyMarks: 50)
57 |
58 | /// A tally mark group with 90 tally marks.
59 | public static let ninety = RomanNumeralTallyMarkGroup(numberOfTallyMarks: 90)
60 |
61 | /// A tally mark group with 100 tally marks.
62 | public static let oneHundred = RomanNumeralTallyMarkGroup(numberOfTallyMarks: 100)
63 |
64 | /// A tally mark group with 400 tally marks.
65 | public static let fourHundred = RomanNumeralTallyMarkGroup(numberOfTallyMarks: 400)
66 |
67 | /// A tally mark group with 500 tally marks.
68 | public static let fiveHundred = RomanNumeralTallyMarkGroup(numberOfTallyMarks: 500)
69 |
70 | /// A tally mark group with 900 tally marks.
71 | public static let nineHundred = RomanNumeralTallyMarkGroup(numberOfTallyMarks: 900)
72 |
73 | /// A tally mark group with 1000 tally marks.
74 | public static let oneThousand = RomanNumeralTallyMarkGroup(numberOfTallyMarks: 1000)
75 |
76 | // MARK: Public Instance Properties
77 |
78 | /// The tally marks that are in the group. The number of tally marks represents the value of the group.
79 | public let tallyMarks: [RomanNumeralTallyMark]
80 |
81 | // MARK: Public Initialization
82 |
83 | /**
84 | Creates a tally mark group that contains the given number of tally marks.
85 |
86 | - Parameter numberOfTallyMarks: The number of tally marks in the group.
87 | */
88 | public init(numberOfTallyMarks: Int) {
89 | tallyMarks = Array(repeating: RomanNumeralTallyMark(), count: numberOfTallyMarks)
90 | }
91 |
92 | /**
93 | Creates a tally mark group that contains the given tally marks.
94 |
95 | - Parameter tallyMarks: The tally marks in the group.
96 | */
97 | public init(tallyMarks: [RomanNumeralTallyMark]) {
98 | self.tallyMarks = tallyMarks
99 | }
100 | }
101 |
102 | // MARK: - Operators Extension
103 |
104 | extension RomanNumeralTallyMarkGroup {
105 | // MARK: Public Static Interface
106 |
107 | public static func + (
108 | left: RomanNumeralTallyMarkGroup,
109 | right: RomanNumeralTallyMarkGroup
110 | ) -> RomanNumeralTallyMarkGroup {
111 | RomanNumeralTallyMarkGroup(tallyMarks: left.tallyMarks + right.tallyMarks)
112 | }
113 | }
114 |
115 | // MARK: - Comparable Extension
116 |
117 | extension RomanNumeralTallyMarkGroup: Comparable {
118 | // MARK: Public Static Interface
119 |
120 | public static func < (lhs: RomanNumeralTallyMarkGroup, rhs: RomanNumeralTallyMarkGroup) -> Bool {
121 | lhs.tallyMarks.count < rhs.tallyMarks.count
122 | }
123 | }
124 |
125 | // MARK: - CustomDebugStringConvertible Extension
126 |
127 | extension RomanNumeralTallyMarkGroup: CustomDebugStringConvertible {
128 | public var debugDescription: String {
129 | tallyMarks.map { $0.debugDescription }.joined()
130 | }
131 | }
132 |
133 | // MARK: - CustomStringConvertible Extension
134 |
135 | extension RomanNumeralTallyMarkGroup: CustomStringConvertible {
136 | public var description: String {
137 | tallyMarks.map { $0.description }.joined()
138 | }
139 | }
140 |
141 | // MARK: - Sequence Extension
142 |
143 | extension RomanNumeralTallyMarkGroup: Sequence {
144 | // MARK: Public Typealiases
145 |
146 | public typealias Iterator = IndexingIterator<[RomanNumeralTallyMark]>
147 |
148 | // MARK: Public Instance Interface
149 |
150 | public func makeIterator() -> IndexingIterator<[RomanNumeralTallyMark]> {
151 | tallyMarks.makeIterator()
152 | }
153 | }
154 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # RomanNumeralKit
2 |
3 | []()
4 | []()
5 | []()
6 |
7 | [](https://travis-ci.org/kylehughes/RomanNumeralKit)
8 | [](https://codecov.io/gh/kylehughes/RomanNumeralKit)
9 |
10 | First-class Roman numeral support for Swift.
11 |
12 | *When in Rome, code as the Romans code.*
13 |
14 | ## Introduction
15 |
16 | Meaningful usage of this framework requires understanding what Roman numerals are. Background information can be found [on Wikipedia](https://en.wikipedia.org/wiki/Roman_numerals).
17 |
18 | ### Features
19 |
20 | - [x] Constants provided for all 3,999 standard Roman numerals.
21 | - [x] Support for subtractive and additive notations.
22 | - [x] Arithmetic using Roman-numeral-oriented algorithms - no integer calculations!
23 | - [x] Conversions to-and-from popular types (e.g. `String`, `Int`).
24 | - [x] Extensions for real-world usage (e.g. copyright text).
25 | - [x] Conformance to all applicable numeric protocols.
26 |
27 | ### Limitations
28 |
29 | #### Fixed Numerical Range
30 |
31 | Standard Roman numerals as we understand them were limited to values from 1 to 3,999. There is no concept of 0. Modern scholars have proposed extensions of the numeric system to support values greater than 3,999 but we do not recognize any of these extensions and decry the proposers to be heretics.
32 |
33 | Most programs do not deal with numbers higher than 3,999, and the world won't exist past the year 3,999 in the Gregorian calendar, so there is no need to worry.
34 |
35 | #### iPhone Model Names
36 |
37 | RomanNumeralKit does not support conversions to-and-from recent iPhone model names such as "Xs".
38 |
39 | ## Requirements
40 |
41 | - iOS 10.0+ / macOS 10.12+ / tvOS 10.0+ / watchOS 3.0+
42 | - Xcode 10.2+
43 | - Swift 5+
44 |
45 | ## Installation
46 |
47 | ### CocoaPods
48 |
49 | Add RomanNumeralKit to your `Podfile`:
50 |
51 | ```ruby
52 | pod 'RomanNumeralKit', '~> 1.0.0`
53 | ```
54 |
55 | Please visit the [CocoaPods website](https://cocoapods.org/) for general CocoaPods usage and installation instructions.
56 |
57 | ### Swift Package Manager
58 |
59 | Add RomanNumeralKit to the `dependencies` value of your `Package.swift`:
60 |
61 | ```swift
62 | dependencies: [
63 | .package(url: "https://github.com/kylehughes/RomanNumeralKit.git", from: "1.0.0")
64 | ]
65 | ```
66 |
67 | ## Usage
68 |
69 | Import `RomanNumeralKit` at the top of the Swift file you want to use it in.
70 |
71 | ```swift
72 | import RomanNumeralKit
73 | ```
74 |
75 | ### Constants
76 |
77 | Constants are provided for all valid Roman numerals, from 1 to 3,999. You should never need to use an initializer
78 | unless you are doing conversions from other types.
79 |
80 | All constants can be accessed directly by their using their uppercase Unicode characters.
81 |
82 | ```swift
83 | print(MMCDIX) // Prints "MMCDIX"
84 | print(MMCDIX.symbols) // Prints "[M, M, C, D, I, X]"
85 |
86 | XCTAssertEqual(MMCDIX, RomanNumeral(.M, .M, .C, .D, .I, .X)) // True
87 | ```
88 |
89 | ### Conversions
90 |
91 | We provide convenient mechanisms to convert `RomanNumeral`s to-and-from popular types.
92 |
93 | It should be noted that these are true conversions: the backing values of `RomanNumeral` instances are groups of tally
94 | marks. We do not hold `Int`references because it would not be in the spirit of the framework.
95 |
96 | #### Constructors
97 |
98 | Constructors are provided to convert `Int`s and `String`s to `RomanNumeral`s.
99 |
100 | ```swift
101 | print(RomanNumeral(from: 2409)) // Prints "MMCDIX"
102 | print(RomanNumeral(from: "MMCDIX")) // Prints "MMCDIX"
103 | ```
104 |
105 | We also support conversions from `Int` and `String` literals when the `RomanNumeral` type can be inferred.
106 |
107 | ```swift
108 | let numeralFromInt: RomanNumeral = 2409
109 | let numeralFromString: RomanNumeral = "MMCDIX"
110 |
111 | print(numeralFromInt) // Prints "MMCDIX"
112 | print(numeralFromString) // Prints "MMCDIX"
113 | ```
114 |
115 | #### Properties
116 |
117 | Instance-level properties are provided to convert `RomanNumeral`s into `Int` and `String` values.
118 |
119 | ```swift
120 | print(MMCDIX.intValue) // Prints "2409"
121 | print(MMCDIX.stringValue) // Prints "MMCDIX"
122 | ```
123 |
124 | We also provide various `*Convertible` protocols to allow types to return different `RomanNumeral` and
125 | `RomanNumeralSymbol` representations of themselves.
126 |
127 | ### Arithmetic
128 |
129 | Addition, subtraction, and multiplication operations are supported (and required) thanks to our conformance to the
130 | `Numeric` protocol. We use algorithms that allow us to directly manipulate the Roman numeral symbols as opposed to
131 | doing conversions to-and-from `Int`s.
132 |
133 | ```swift
134 | XCTAssertEqual(MD + CMIX, MMCDIX) // True
135 | XCTAssertEqual(MMM - DXCI, MMCDIX) // True
136 | XCTAssertEqual(XI * CCXIX, MMCDIX) // True
137 | ```
138 |
139 | #### Performance
140 |
141 | Our committment to authenticity does have implications.
142 |
143 | The following table compares the performance `Int` arithmetic operations to Roman numeral arithmetic operations on a
144 | new MacBook Pro.
145 |
146 | | Operation (100x) | `Int` | `RomanNumeral` | % Slower |
147 | | ---------------- | ----------: | -------------: | -------------: |
148 | | Addition | 0.00000127s | 0.151s | 11,889,663.78% |
149 | | Subtraction | 0.00000151s | 0.0761s | 5,992,025.98% |
150 | | Multiplication | 0.00000204s | 0.0575s | 4,527,459.06% |
151 |
152 | It should be noted that this is much faster than any person from Ancient Rome could do arithmetic. Who can take issue
153 | with progress?
154 |
155 | ### Copyright Text
156 |
157 | The most useful feature we provide is automatic formatting of Copyright text.
158 |
159 | ```swift
160 | print(MDCCLXXVI.copyrightText) // Prints "Copyright © MDCCLXXVI"
161 | ```
162 |
163 | ### Additive Notation
164 |
165 | The default notation for this framework is subtractive notation - that is what instances of `RomanNumeral`s represent.
166 | We provide the `AdditiveRomanNumeral` struct for initialization of numerals using additive notation. We also support
167 | conversions between the notations.
168 |
169 | Both notations implement the `RomanNumeralProtocol` protocol and support the same general interface.
170 |
171 | ```swift
172 | let additiveNumeral = AdditiveNotation(.M, .M, .C, .C, .C, .C, .V, .I, .I, .I, .I)
173 |
174 | print(additiveNumeral) // Prints "MMCCCCVIIII"
175 | print(additiveNumeral.intValue) // Prints "2409"
176 |
177 | XCTAssertEqual(additiveRomanNumeral.romanNumeral, MMCDIX) // True
178 | XCTAssertEqual(MMCDIX.additiveRomanNumeral, additiveRomanNumeral) // True
179 | ```
180 |
181 | ### Extensions
182 |
183 | We provide a variety of extensions on existing Swift types to make common operations easier.
184 |
185 | #### `Calendar` & `DateComponent` Extensions
186 |
187 | `Calendar` objects, and the `DateComponents` they produce, are able to convert years into `RomanNumeral`s.
188 |
189 | ```swift
190 | if let currentYear = Calendar.current.currentYearAsRomanNumeral {
191 | print(currentYear) // Prints "MMXIX"
192 | print(currentYear.intValue) // Prints "2019"
193 | print(currentYear.copyrightText) // Prints "Copyright © MMXIX"
194 | }
195 |
196 | if let americasBirthYear = Calendar.current.yearAsRomanNumeral(fromDate: americasBirthDate) {
197 | print(americasBirthYear) // Prints "MDCCLXXVI"
198 | print(americasBirthYear.intValue) // Prints "1776"
199 | print(americasBirthYear.copyrightText) // Prints "Copyright © MDCCLXXVI"
200 | }
201 | ```
202 |
203 | #### `Int` & `String` Extensions
204 |
205 | We conform `Int` and `String` to the `*RomanNumeralConvertible` protocols to complete the ouroboros with these
206 | foundational types.
207 |
208 | ```swift
209 | print(2409.romanNumeral) // Prints "MMCDIX"
210 | print(2409.additiveRomanNumeral) // Prints "MMCCCCVIIII"
211 | print("MMCDIX".romanNumeral) // Prints "MMCDIX"
212 | print("MMCCCCVIIII".additiveRomanNumeral) // Prints "MMCCCCVIIII"
213 | ```
214 |
215 | ## Authors
216 |
217 | Kyle Hughes
218 |
219 | [![my twitter][social_twitter_image]][social_twitter_url]
220 |
221 | [social_twitter_image]: https://img.shields.io/badge/Twitter-@KyleHughes-blue.svg
222 | [social_twitter_url]: https://www.twitter.com/KyleHughes
223 |
224 | ## Contributions
225 |
226 | `RomanNumeralKit` is not accepting source contributions at this time.
227 |
228 | ## License
229 |
230 | `RomanNumeralKit` is available under the MIT License.
231 |
--------------------------------------------------------------------------------
/Tests/RomanNumeralKitTests/Numeric System/RomanNumeralTallyMarkGroupTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RomanNumeralTallyMarkGroupTests.swift
3 | // RomanNumeralKitTests
4 | //
5 | // Copyright © 2019 Kyle Hughes.
6 | //
7 | // Permission is hereby granted, free of charge, to any person obtaining a copy
8 | // of this software and associated documentation files (the "Software"), to deal
9 | // in the Software without restriction, including without limitation the rights
10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | // copies of the Software, and to permit persons to whom the Software is
12 | // furnished to do so, subject to the following conditions:
13 | //
14 | // The above copyright notice and this permission notice shall be included in
15 | // all copies or substantial portions of the Software.
16 | //
17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23 | // THE SOFTWARE.
24 | //
25 |
26 | import XCTest
27 |
28 | @testable import RomanNumeralKit
29 |
30 | class RomanNumeralTallyMarkGroupTests: XCTestCase {
31 | // MARK: XCTestCase Implementation
32 |
33 | override func setUp() {}
34 |
35 | override func tearDown() {}
36 |
37 | // MARK: Tests
38 |
39 | func test_init_numberOfTallyMarks() {
40 | XCTAssertEqual(RomanNumeralTallyMarkGroup(numberOfTallyMarks: 0).tallyMarks.count, 0)
41 | XCTAssertEqual(RomanNumeralTallyMarkGroup(numberOfTallyMarks: 1).tallyMarks.count, 1)
42 | XCTAssertEqual(RomanNumeralTallyMarkGroup(numberOfTallyMarks: 4).tallyMarks.count, 4)
43 | XCTAssertEqual(RomanNumeralTallyMarkGroup(numberOfTallyMarks: 5).tallyMarks.count, 5)
44 | XCTAssertEqual(RomanNumeralTallyMarkGroup(numberOfTallyMarks: 9).tallyMarks.count, 9)
45 | XCTAssertEqual(RomanNumeralTallyMarkGroup(numberOfTallyMarks: 10).tallyMarks.count, 10)
46 | XCTAssertEqual(RomanNumeralTallyMarkGroup(numberOfTallyMarks: 40).tallyMarks.count, 40)
47 | XCTAssertEqual(RomanNumeralTallyMarkGroup(numberOfTallyMarks: 50).tallyMarks.count, 50)
48 | XCTAssertEqual(RomanNumeralTallyMarkGroup(numberOfTallyMarks: 90).tallyMarks.count, 90)
49 | XCTAssertEqual(RomanNumeralTallyMarkGroup(numberOfTallyMarks: 100).tallyMarks.count, 100)
50 | XCTAssertEqual(RomanNumeralTallyMarkGroup(numberOfTallyMarks: 400).tallyMarks.count, 400)
51 | XCTAssertEqual(RomanNumeralTallyMarkGroup(numberOfTallyMarks: 500).tallyMarks.count, 500)
52 | XCTAssertEqual(RomanNumeralTallyMarkGroup(numberOfTallyMarks: 900).tallyMarks.count, 900)
53 | XCTAssertEqual(RomanNumeralTallyMarkGroup(numberOfTallyMarks: 1000).tallyMarks.count, 1000)
54 | }
55 |
56 | func test_init_tallyMarks() {
57 | // Given...
58 |
59 | let I = RomanNumeralTallyMark()
60 |
61 | // Then...
62 |
63 | XCTAssertEqual(RomanNumeralTallyMarkGroup(tallyMarks: [I]).tallyMarks, [I])
64 | XCTAssertEqual(RomanNumeralTallyMarkGroup(tallyMarks: [I, I, I, I, I]).tallyMarks, [I, I, I, I, I])
65 | }
66 | }
67 |
68 | // MARK: - Comparable Tests
69 |
70 | extension RomanNumeralTallyMarkGroupTests {
71 | func test_lessThan() {
72 | XCTAssert(RomanNumeralTallyMarkGroup.one < RomanNumeralTallyMarkGroup.four)
73 | XCTAssert(RomanNumeralTallyMarkGroup.four < RomanNumeralTallyMarkGroup.five)
74 | XCTAssert(RomanNumeralTallyMarkGroup.five < RomanNumeralTallyMarkGroup.nine)
75 | XCTAssert(RomanNumeralTallyMarkGroup.nine < RomanNumeralTallyMarkGroup.ten)
76 | XCTAssert(RomanNumeralTallyMarkGroup.ten < RomanNumeralTallyMarkGroup.forty)
77 | XCTAssert(RomanNumeralTallyMarkGroup.forty < RomanNumeralTallyMarkGroup.fifty)
78 | XCTAssert(RomanNumeralTallyMarkGroup.fifty < RomanNumeralTallyMarkGroup.ninety)
79 | XCTAssert(RomanNumeralTallyMarkGroup.ninety < RomanNumeralTallyMarkGroup.oneHundred)
80 | XCTAssert(RomanNumeralTallyMarkGroup.oneHundred < RomanNumeralTallyMarkGroup.fourHundred)
81 | XCTAssert(RomanNumeralTallyMarkGroup.fourHundred < RomanNumeralTallyMarkGroup.fiveHundred)
82 | XCTAssert(RomanNumeralTallyMarkGroup.fiveHundred < RomanNumeralTallyMarkGroup.nineHundred)
83 | XCTAssert(RomanNumeralTallyMarkGroup.nineHundred < RomanNumeralTallyMarkGroup.oneThousand)
84 |
85 | XCTAssertFalse(RomanNumeralTallyMarkGroup.four < RomanNumeralTallyMarkGroup.one)
86 | XCTAssertFalse(RomanNumeralTallyMarkGroup.five < RomanNumeralTallyMarkGroup.four)
87 | XCTAssertFalse(RomanNumeralTallyMarkGroup.nine < RomanNumeralTallyMarkGroup.five)
88 | XCTAssertFalse(RomanNumeralTallyMarkGroup.ten < RomanNumeralTallyMarkGroup.nine)
89 | XCTAssertFalse(RomanNumeralTallyMarkGroup.forty < RomanNumeralTallyMarkGroup.ten)
90 | XCTAssertFalse(RomanNumeralTallyMarkGroup.fifty < RomanNumeralTallyMarkGroup.forty)
91 | XCTAssertFalse(RomanNumeralTallyMarkGroup.ninety < RomanNumeralTallyMarkGroup.fifty)
92 | XCTAssertFalse(RomanNumeralTallyMarkGroup.oneHundred < RomanNumeralTallyMarkGroup.ninety)
93 | XCTAssertFalse(RomanNumeralTallyMarkGroup.fourHundred < RomanNumeralTallyMarkGroup.oneHundred)
94 | XCTAssertFalse(RomanNumeralTallyMarkGroup.fiveHundred < RomanNumeralTallyMarkGroup.fourHundred)
95 | XCTAssertFalse(RomanNumeralTallyMarkGroup.nineHundred < RomanNumeralTallyMarkGroup.fiveHundred)
96 | XCTAssertFalse(RomanNumeralTallyMarkGroup.oneThousand < RomanNumeralTallyMarkGroup.nineHundred)
97 |
98 | XCTAssertFalse(RomanNumeralTallyMarkGroup.one < RomanNumeralTallyMarkGroup.one)
99 | XCTAssertFalse(RomanNumeralTallyMarkGroup.four < RomanNumeralTallyMarkGroup.four)
100 | XCTAssertFalse(RomanNumeralTallyMarkGroup.five < RomanNumeralTallyMarkGroup.five)
101 | XCTAssertFalse(RomanNumeralTallyMarkGroup.nine < RomanNumeralTallyMarkGroup.nine)
102 | XCTAssertFalse(RomanNumeralTallyMarkGroup.ten < RomanNumeralTallyMarkGroup.ten)
103 | XCTAssertFalse(RomanNumeralTallyMarkGroup.forty < RomanNumeralTallyMarkGroup.forty)
104 | XCTAssertFalse(RomanNumeralTallyMarkGroup.fifty < RomanNumeralTallyMarkGroup.fifty)
105 | XCTAssertFalse(RomanNumeralTallyMarkGroup.ninety < RomanNumeralTallyMarkGroup.ninety)
106 | XCTAssertFalse(RomanNumeralTallyMarkGroup.oneHundred < RomanNumeralTallyMarkGroup.oneHundred)
107 | XCTAssertFalse(RomanNumeralTallyMarkGroup.fourHundred < RomanNumeralTallyMarkGroup.fourHundred)
108 | XCTAssertFalse(RomanNumeralTallyMarkGroup.fiveHundred < RomanNumeralTallyMarkGroup.fiveHundred)
109 | XCTAssertFalse(RomanNumeralTallyMarkGroup.nineHundred < RomanNumeralTallyMarkGroup.nineHundred)
110 | XCTAssertFalse(RomanNumeralTallyMarkGroup.oneThousand < RomanNumeralTallyMarkGroup.oneThousand)
111 | }
112 | }
113 |
114 | // MARK: - CustomDebugStringConvertible Tests
115 |
116 | extension RomanNumeralTallyMarkGroupTests {
117 | func test_customDebugStringConvertible() {
118 | XCTAssertEqual(
119 | RomanNumeralTallyMarkGroup.oneHundred.debugDescription,
120 | RomanNumeralTallyMarkGroup.oneHundred.tallyMarks.map { $0.debugDescription }.joined()
121 | )
122 | }
123 | }
124 |
125 | // MARK: - CustomStringConvertible Tests
126 |
127 | extension RomanNumeralTallyMarkGroupTests {
128 | func test_customStringConvertible() {
129 | XCTAssertEqual(
130 | RomanNumeralTallyMarkGroup.oneHundred.description,
131 | RomanNumeralTallyMarkGroup.oneHundred.tallyMarks.map { $0.description }.joined()
132 | )
133 | }
134 | }
135 |
136 | // MARK: - Operators Tests
137 |
138 | extension RomanNumeralTallyMarkGroupTests {
139 | func test_addition() {
140 | XCTAssertEqual(
141 | RomanNumeralTallyMarkGroup.one + RomanNumeralTallyMarkGroup.one,
142 | RomanNumeralTallyMarkGroup(numberOfTallyMarks: 2)
143 | )
144 | XCTAssertEqual(
145 | RomanNumeralTallyMarkGroup.fifty + RomanNumeralTallyMarkGroup.oneHundred,
146 | RomanNumeralTallyMarkGroup(numberOfTallyMarks: 150)
147 | )
148 | XCTAssertEqual(
149 | RomanNumeralTallyMarkGroup.oneThousand + RomanNumeralTallyMarkGroup.nineHundred,
150 | RomanNumeralTallyMarkGroup(numberOfTallyMarks: 1900)
151 | )
152 | }
153 | }
154 |
155 | // MARK: - Sequence Tests
156 |
157 | extension RomanNumeralTallyMarkGroupTests {
158 | func test_makeIterator() {
159 | // Given...
160 |
161 | let tallyMarkGroup = RomanNumeralTallyMarkGroup.oneHundred
162 |
163 | // When...
164 |
165 | var iterator = tallyMarkGroup.makeIterator()
166 |
167 | var numberOfIterations = 0
168 | while let _ = iterator.next() {
169 | numberOfIterations += 1
170 | }
171 |
172 | // Then...
173 |
174 | XCTAssertEqual(numberOfIterations, tallyMarkGroup.tallyMarks.count)
175 | }
176 | }
177 |
--------------------------------------------------------------------------------
/Sources/RomanNumeralKit/Numeric System/Subtractive Notation/SubtractiveRomanNumeralSymbol.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SubtractiveRomanNumeralSymbol.swift
3 | // RomanNumeralKit
4 | //
5 | // Copyright © 2019 Kyle Hughes.
6 | //
7 | // Permission is hereby granted, free of charge, to any person obtaining a copy
8 | // of this software and associated documentation files (the "Software"), to deal
9 | // in the Software without restriction, including without limitation the rights
10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | // copies of the Software, and to permit persons to whom the Software is
12 | // furnished to do so, subject to the following conditions:
13 | //
14 | // The above copyright notice and this permission notice shall be included in
15 | // all copies or substantial portions of the Software.
16 | //
17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23 | // THE SOFTWARE.
24 | //
25 |
26 | public enum SubtractiveRomanNumeralSymbol: CaseIterable {
27 | /// The Latin word meaning "none" which is used in lieu of the Western Arabic value "0".
28 | case nulla
29 |
30 | /// The symbol representing the Western Arabic value "1".
31 | case I
32 |
33 | /// The compound subtractive symbol representing the Western Arabic value "4".
34 | case IV
35 |
36 | /// The symbol representing the Western Arabic value "5".
37 | case V
38 |
39 | /// The compound subtractive symbol representing the Western Arabic value "90".
40 | case IX
41 |
42 | /// The symbol representing the Western Arabic value "10".
43 | case X
44 |
45 | /// The compound subtractive symbol representing the Western Arabic value "90".
46 | case XL
47 |
48 | /// The symbol representing the Western Arabic value "50".
49 | case L
50 |
51 | /// The compound subtractive symbol representing the Western Arabic value "90".
52 | case XC
53 |
54 | /// The symbol representing the Western Arabic value "100".
55 | case C
56 |
57 | /// The compound subtractive symbol representing the Western Arabic value "400".
58 | case CD
59 |
60 | /// The symbol representing the Western Arabic value "500".
61 | case D
62 |
63 | /// The compound subtractive symbol representing the Western Arabic value "900".
64 | case CM
65 |
66 | /// The symbol representing the Western Arabic value "1000".
67 | case M
68 |
69 | // MARK: Public Static Interface
70 |
71 | /// All of the Roman numeral symbols, represented in ascending order by value using additive notation.
72 | public static let allAdditiveRomanNumeralSymbolsAscending = allSymbolsAscending
73 | .map { $0.additiveRomanNumeralSymbols }
74 |
75 | /// All of the Roman numeral symbols, represented in descending order by value using additive notation.
76 | public static let allAdditiveRomanNumeralSymbolsDescending = allSymbolsDescending
77 | .map { $0.additiveRomanNumeralSymbols }
78 |
79 | /// All of the Roman numeral symbols, represented in ascending order by value using regular Roman numeral symbols.
80 | public static let allRomanNumeralSymbolsAscending = allSymbolsAscending.map { $0.romanNumeralSymbols }
81 |
82 | // MARK: Public Instance Interface
83 |
84 | /**
85 | The current subtractive symbol represented using `RomanNumeralSymbols`.
86 |
87 | These symbols are directly converted and not expressed in additive notation.
88 | */
89 | public var romanNumeralSymbols: [RomanNumeralSymbol] {
90 | switch self {
91 | case .nulla:
92 | return [.nulla]
93 | case .I:
94 | return [.I]
95 | case .IV:
96 | return [.I, .V]
97 | case .V:
98 | return [.V]
99 | case .IX:
100 | return [.I, .X]
101 | case .X:
102 | return [.X]
103 | case .XL:
104 | return [.X, .L]
105 | case .L:
106 | return [.L]
107 | case .XC:
108 | return [.X, .C]
109 | case .C:
110 | return [.C]
111 | case .CD:
112 | return [.C, .D]
113 | case .D:
114 | return [.D]
115 | case .CM:
116 | return [.C, .M]
117 | case .M:
118 | return [.M]
119 | }
120 | }
121 | }
122 |
123 | // MARK: - AdditiveRomanNumeralSymbolsConvertible Extension
124 |
125 | extension SubtractiveRomanNumeralSymbol: AdditiveRomanNumeralSymbolsConvertible {
126 | // MARK: Public Instance Interface
127 |
128 | public var additiveRomanNumeralSymbols: [RomanNumeralSymbol] {
129 | switch self {
130 | case .nulla:
131 | return [.nulla]
132 | case .I:
133 | return [.I]
134 | case .IV:
135 | return [.I, .I, .I, .I]
136 | case .V:
137 | return [.V]
138 | case .IX:
139 | return [.V, .I, .I, .I, .I]
140 | case .X:
141 | return [.X]
142 | case .XL:
143 | return [.X, .X, .X, .X]
144 | case .L:
145 | return [.L]
146 | case .XC:
147 | return [.L, .X, .X, .X, .X]
148 | case .C:
149 | return [.C]
150 | case .CD:
151 | return [.C, .C, .C, .C]
152 | case .D:
153 | return [.D]
154 | case .CM:
155 | return [.D, .C, .C, .C, .C]
156 | case .M:
157 | return [.M]
158 | }
159 | }
160 | }
161 |
162 | // MARK: - RawRepresentable Extension
163 |
164 | extension SubtractiveRomanNumeralSymbol: RawRepresentable {
165 | // MARK: Public Typealiases
166 |
167 | public typealias RawValue = RomanNumeralTallyMarkGroup
168 |
169 | // MARK: Public Initialization
170 |
171 | public init?(rawValue: RomanNumeralTallyMarkGroup) {
172 | switch rawValue {
173 | case .nulla:
174 | self = .nulla
175 | case .one:
176 | self = .I
177 | case .four:
178 | self = .IV
179 | case .five:
180 | self = .V
181 | case .nine:
182 | self = .IX
183 | case .ten:
184 | self = .X
185 | case .forty:
186 | self = .XL
187 | case .fifty:
188 | self = .L
189 | case .ninety:
190 | self = .XC
191 | case .oneHundred:
192 | self = .C
193 | case .fourHundred:
194 | self = .CD
195 | case .fiveHundred:
196 | self = .D
197 | case .nineHundred:
198 | self = .CM
199 | case .oneThousand:
200 | self = .M
201 | default:
202 | return nil
203 | }
204 | }
205 |
206 | // MARK: Public Instance Interface
207 |
208 | public var rawValue: RomanNumeralTallyMarkGroup {
209 | switch self {
210 | case .nulla:
211 | return .nulla
212 | case .I:
213 | return .one
214 | case .IV:
215 | return .four
216 | case .V:
217 | return .five
218 | case .IX:
219 | return .nine
220 | case .X:
221 | return .ten
222 | case .XL:
223 | return .forty
224 | case .L:
225 | return .fifty
226 | case .XC:
227 | return .ninety
228 | case .C:
229 | return .oneHundred
230 | case .CD:
231 | return .fourHundred
232 | case .D:
233 | return .fiveHundred
234 | case .CM:
235 | return .nineHundred
236 | case .M:
237 | return .oneThousand
238 | }
239 | }
240 | }
241 |
242 | // MARK: - RomanNumeralSymbolProtocol Extension
243 |
244 | extension SubtractiveRomanNumeralSymbol: RomanNumeralSymbolProtocol {
245 | // MARK: Public Static Properties
246 |
247 | public static let allSymbolsAscending: [SubtractiveRomanNumeralSymbol] = [.I, .IV, .V, .IX, .X, .XL, .L, .XC, .C, .CD, .D, .CM, .M]
248 | public static let allSymbolsDescending: [SubtractiveRomanNumeralSymbol] = allSymbolsAscending.reversed()
249 |
250 | // MARK: Public Initialization
251 |
252 | public init(from stringValue: String) throws {
253 | let potentialSymbol = SubtractiveRomanNumeralSymbol.allCases
254 | .filter { $0.stringValue == stringValue }
255 | .first
256 |
257 | guard let symbol = potentialSymbol else {
258 | throw RomanNumeralSymbolError.unrecognizedString(string: stringValue)
259 | }
260 |
261 | self = symbol
262 | }
263 |
264 | // MARK: Public Instance Interface
265 |
266 | public var stringValue: String {
267 | switch self {
268 | case .nulla:
269 | return "N"
270 | case .I:
271 | return "I"
272 | case .IV:
273 | return "IV"
274 | case .V:
275 | return "V"
276 | case .IX:
277 | return "IX"
278 | case .X:
279 | return "X"
280 | case .XL:
281 | return "XL"
282 | case .L:
283 | return "L"
284 | case .XC:
285 | return "XC"
286 | case .C:
287 | return "C"
288 | case .CD:
289 | return "CD"
290 | case .D:
291 | return "D"
292 | case .CM:
293 | return "CM"
294 | case .M:
295 | return "M"
296 | }
297 | }
298 |
299 | public var lesserSymbol: SubtractiveRomanNumeralSymbol? {
300 | switch self {
301 | case .nulla:
302 | return nil
303 | case .I:
304 | return nil
305 | case .IV:
306 | return .I
307 | case .V:
308 | return .IV
309 | case .IX:
310 | return .V
311 | case .X:
312 | return .IX
313 | case .XL:
314 | return .X
315 | case .L:
316 | return .XL
317 | case .XC:
318 | return .L
319 | case .C:
320 | return .XC
321 | case .CD:
322 | return .C
323 | case .D:
324 | return .CD
325 | case .CM:
326 | return .D
327 | case .M:
328 | return .CM
329 | }
330 | }
331 | }
332 |
333 | // MARK: - SubtractiveRomanNumeralSymbolConvertible Extension
334 |
335 | extension SubtractiveRomanNumeralSymbol: SubtractiveRomanNumeralSymbolConvertible {
336 | // MARK: Public Instance Interface
337 |
338 | public var subtractiveRomanNumeralSymbol: SubtractiveRomanNumeralSymbol {
339 | self
340 | }
341 | }
342 |
--------------------------------------------------------------------------------
/Tests/RomanNumeralKitTests/Numeric System/RomanNumeralSymbolTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RomanNumeralSymbolTests.swift
3 | // RomanNumeralKitTests
4 | //
5 | // Copyright © 2019 Kyle Hughes.
6 | //
7 | // Permission is hereby granted, free of charge, to any person obtaining a copy
8 | // of this software and associated documentation files (the "Software"), to deal
9 | // in the Software without restriction, including without limitation the rights
10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | // copies of the Software, and to permit persons to whom the Software is
12 | // furnished to do so, subject to the following conditions:
13 | //
14 | // The above copyright notice and this permission notice shall be included in
15 | // all copies or substantial portions of the Software.
16 | //
17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23 | // THE SOFTWARE.
24 | //
25 |
26 | import XCTest
27 |
28 | @testable import RomanNumeralKit
29 |
30 | class RomanNumeralSymbolTests: XCTestCase {
31 | // MARK: XCTestCase Implementation
32 |
33 | override func setUp() {}
34 |
35 | override func tearDown() {}
36 |
37 | // MARK: Tests
38 |
39 | func test_multiplication() {
40 | XCTAssertEqual(RomanNumeralSymbol.nulla * .nulla, [])
41 |
42 | for lhs in RomanNumeralSymbol.allSymbolsAscending {
43 | XCTAssertEqual(lhs * .nulla, [])
44 | }
45 |
46 | for lhs in RomanNumeralSymbol.allSymbolsAscending {
47 | for rhs in RomanNumeralSymbol.allSymbolsAscending {
48 | let expectedProduct = lhs.rawValue.tallyMarks.count * rhs.rawValue.tallyMarks.count
49 | let actualProduct = (lhs * rhs).map { $0.rawValue.tallyMarks.count }.reduce(0, +)
50 | XCTAssertEqual(actualProduct, expectedProduct)
51 | }
52 | }
53 | }
54 |
55 | func test_expandedIntoLesserSymbol() {
56 | XCTAssertEqual(RomanNumeralSymbol.nulla.expandedIntoLesserSymbol, [])
57 | XCTAssertEqual(RomanNumeralSymbol.I.expandedIntoLesserSymbol, [.I])
58 | XCTAssertEqual(RomanNumeralSymbol.V.expandedIntoLesserSymbol, [.I, .I, .I, .I, .I])
59 | XCTAssertEqual(RomanNumeralSymbol.X.expandedIntoLesserSymbol, [.V, .V])
60 | XCTAssertEqual(RomanNumeralSymbol.L.expandedIntoLesserSymbol, [.X, .X, .X, .X, .X])
61 | XCTAssertEqual(RomanNumeralSymbol.C.expandedIntoLesserSymbol, [.L, .L])
62 | XCTAssertEqual(RomanNumeralSymbol.D.expandedIntoLesserSymbol, [.C, .C, .C, .C, .C])
63 | XCTAssertEqual(RomanNumeralSymbol.M.expandedIntoLesserSymbol, [.D, .D])
64 | }
65 | }
66 |
67 | // MARK: - AdditiveRomanNumeralConvertible Tests
68 |
69 | extension RomanNumeralSymbolTests {
70 | func test_additiveRomanNumeral() {
71 | for symbol in RomanNumeralSymbol.allSymbolsAscending {
72 | XCTAssertEqual(symbol.additiveRomanNumeral, try AdditiveRomanNumeral(symbol: symbol))
73 | }
74 | }
75 | }
76 |
77 | // MARK: - AdditiveRomanNumeralSymbolConvertible Tests
78 |
79 | extension RomanNumeralSymbolTests {
80 | func test_basicRomanNumeralSymbolConvertible() {
81 | for symbol in RomanNumeralSymbol.allSymbolsAscending {
82 | XCTAssertEqual(symbol.additiveRomanNumeralSymbol, symbol)
83 | }
84 | }
85 | }
86 |
87 | // MARK: - AdditiveRomanNumeralSymbolsConvertible Tests
88 |
89 | extension RomanNumeralSymbolTests {
90 | func test_basicRomanNumeralSymbolsConvertible() {
91 | for symbol in RomanNumeralSymbol.allSymbolsAscending {
92 | XCTAssertEqual(symbol.additiveRomanNumeralSymbols, [symbol])
93 | }
94 | }
95 | }
96 |
97 | // MARK: - Comparable Tests
98 |
99 | extension RomanNumeralSymbolTests {
100 | func test_lessThan() {
101 | XCTAssert(RomanNumeralSymbol.I < RomanNumeralSymbol.V)
102 | XCTAssert(RomanNumeralSymbol.V < RomanNumeralSymbol.X)
103 | XCTAssert(RomanNumeralSymbol.X < RomanNumeralSymbol.L)
104 | XCTAssert(RomanNumeralSymbol.L < RomanNumeralSymbol.C)
105 | XCTAssert(RomanNumeralSymbol.C < RomanNumeralSymbol.D)
106 | XCTAssert(RomanNumeralSymbol.D < RomanNumeralSymbol.M)
107 |
108 | XCTAssertFalse(RomanNumeralSymbol.V < RomanNumeralSymbol.I)
109 | XCTAssertFalse(RomanNumeralSymbol.X < RomanNumeralSymbol.V)
110 | XCTAssertFalse(RomanNumeralSymbol.L < RomanNumeralSymbol.X)
111 | XCTAssertFalse(RomanNumeralSymbol.C < RomanNumeralSymbol.L)
112 | XCTAssertFalse(RomanNumeralSymbol.D < RomanNumeralSymbol.C)
113 | XCTAssertFalse(RomanNumeralSymbol.M < RomanNumeralSymbol.D)
114 |
115 | XCTAssertFalse(RomanNumeralSymbol.I < RomanNumeralSymbol.I)
116 | XCTAssertFalse(RomanNumeralSymbol.V < RomanNumeralSymbol.V)
117 | XCTAssertFalse(RomanNumeralSymbol.X < RomanNumeralSymbol.X)
118 | XCTAssertFalse(RomanNumeralSymbol.L < RomanNumeralSymbol.L)
119 | XCTAssertFalse(RomanNumeralSymbol.C < RomanNumeralSymbol.C)
120 | XCTAssertFalse(RomanNumeralSymbol.D < RomanNumeralSymbol.D)
121 | XCTAssertFalse(RomanNumeralSymbol.M < RomanNumeralSymbol.M)
122 | }
123 | }
124 |
125 | // MARK: - CustomDebugStringConvertible Tests
126 |
127 | extension RomanNumeralSymbolTests {
128 | func test_debugDescription() {
129 | for symbol in RomanNumeralSymbol.allCases {
130 | XCTAssertEqual(symbol.debugDescription, symbol.stringValue)
131 | }
132 | }
133 | }
134 |
135 | // MARK: - CustomStringConvertible Tests
136 |
137 | extension RomanNumeralSymbolTests {
138 | func test_description() {
139 | for symbol in RomanNumeralSymbol.allCases {
140 | XCTAssertEqual(symbol.description, symbol.stringValue)
141 | }
142 | }
143 | }
144 |
145 | // MARK: - RawRepresentable Tests
146 |
147 | extension RomanNumeralSymbolTests {
148 | func test_init_rawValue_success() {
149 | XCTAssertEqual(RomanNumeralSymbol(rawValue: .nulla), .nulla)
150 | XCTAssertEqual(RomanNumeralSymbol(rawValue: .one), .I)
151 | XCTAssertEqual(RomanNumeralSymbol(rawValue: .five), .V)
152 | XCTAssertEqual(RomanNumeralSymbol(rawValue: .ten), .X)
153 | XCTAssertEqual(RomanNumeralSymbol(rawValue: .fifty), .L)
154 | XCTAssertEqual(RomanNumeralSymbol(rawValue: .oneHundred), .C)
155 | XCTAssertEqual(RomanNumeralSymbol(rawValue: .fiveHundred), .D)
156 | XCTAssertEqual(RomanNumeralSymbol(rawValue: .oneThousand), .M)
157 | }
158 |
159 | func test_init_rawValue_invalid() {
160 | // Given...
161 |
162 | let two = RomanNumeralTallyMarkGroup(numberOfTallyMarks: 2)
163 | let eleven = RomanNumeralTallyMarkGroup(numberOfTallyMarks: 11)
164 | let oneHundredOne = RomanNumeralTallyMarkGroup(numberOfTallyMarks: 101)
165 | let oneThousandOne = RomanNumeralTallyMarkGroup(numberOfTallyMarks: 1001)
166 |
167 | // Then...
168 |
169 | XCTAssertNil(RomanNumeralSymbol(rawValue: two))
170 | XCTAssertNil(RomanNumeralSymbol(rawValue: eleven))
171 | XCTAssertNil(RomanNumeralSymbol(rawValue: oneHundredOne))
172 | XCTAssertNil(RomanNumeralSymbol(rawValue: oneThousandOne))
173 | }
174 | }
175 |
176 | // MARK: - RomanNumeralSymbolProtocol Tests
177 |
178 | extension RomanNumeralSymbolTests {
179 | func test_init_fromString_success() {
180 | XCTAssertEqual(try RomanNumeralSymbol(from: "N"), .nulla)
181 | XCTAssertEqual(try RomanNumeralSymbol(from: "I"), .I)
182 | XCTAssertEqual(try RomanNumeralSymbol(from: "V"), .V)
183 | XCTAssertEqual(try RomanNumeralSymbol(from: "X"), .X)
184 | XCTAssertEqual(try RomanNumeralSymbol(from: "L"), .L)
185 | XCTAssertEqual(try RomanNumeralSymbol(from: "C"), .C)
186 | XCTAssertEqual(try RomanNumeralSymbol(from: "D"), .D)
187 | XCTAssertEqual(try RomanNumeralSymbol(from: "M"), .M)
188 | }
189 |
190 | func test_init_fromString_invalidCharacter() {
191 | XCTAssertThrowsError(try RomanNumeralSymbol(from: "R")) {
192 | switch $0 as? RomanNumeralSymbolError {
193 | case .none:
194 | XCTFail()
195 | case let .some(symbolError):
196 | switch symbolError {
197 | case .unrecognizedString:
198 | break
199 | }
200 | }
201 | }
202 | }
203 |
204 | func test_allSymbolsAscending() {
205 | XCTAssertEqual(RomanNumeralSymbol.allSymbolsAscending, [.I, .V, .X, .L, .C, .D, .M])
206 | }
207 |
208 | func test_allSymbolsDescending() {
209 | XCTAssertEqual(RomanNumeralSymbol.allSymbolsDescending, [.M, .D, .C, .L, .X, .V, .I])
210 | }
211 |
212 | func test_stringValue() {
213 | XCTAssertEqual(RomanNumeralSymbol.nulla.stringValue, "N")
214 | XCTAssertEqual(RomanNumeralSymbol.I.stringValue, "I")
215 | XCTAssertEqual(RomanNumeralSymbol.V.stringValue, "V")
216 | XCTAssertEqual(RomanNumeralSymbol.X.stringValue, "X")
217 | XCTAssertEqual(RomanNumeralSymbol.L.stringValue, "L")
218 | XCTAssertEqual(RomanNumeralSymbol.C.stringValue, "C")
219 | XCTAssertEqual(RomanNumeralSymbol.D.stringValue, "D")
220 | XCTAssertEqual(RomanNumeralSymbol.M.stringValue, "M")
221 | }
222 |
223 | func test_lesserSymbol() {
224 | XCTAssertEqual(RomanNumeralSymbol.nulla.lesserSymbol, nil)
225 | XCTAssertEqual(RomanNumeralSymbol.I.lesserSymbol, nil)
226 | XCTAssertEqual(RomanNumeralSymbol.V.lesserSymbol, .I)
227 | XCTAssertEqual(RomanNumeralSymbol.X.lesserSymbol, .V)
228 | XCTAssertEqual(RomanNumeralSymbol.L.lesserSymbol, .X)
229 | XCTAssertEqual(RomanNumeralSymbol.C.lesserSymbol, .L)
230 | XCTAssertEqual(RomanNumeralSymbol.D.lesserSymbol, .C)
231 | XCTAssertEqual(RomanNumeralSymbol.M.lesserSymbol, .D)
232 | }
233 | }
234 |
235 | // MARK: - RomanNumeralSymbolConvertible Tests
236 |
237 | extension RomanNumeralSymbolTests {
238 | func test_romanNumeralSymbol() {
239 | for symbol in RomanNumeralSymbol.allSymbolsAscending {
240 | XCTAssertEqual(symbol.romanNumeralSymbol, symbol)
241 | }
242 | }
243 | }
244 |
245 | // MARK: - RomanNumeralSymbolsConvertible Tests
246 |
247 | extension RomanNumeralSymbolTests {
248 | func test_romanNumeralSymbols() {
249 | for symbol in RomanNumeralSymbol.allSymbolsAscending {
250 | XCTAssertEqual(symbol.romanNumeralSymbols, [symbol])
251 | }
252 | }
253 | }
254 |
255 | // MARK: - SubtractiveRomanNumeralSymbolsConvertible Tests
256 |
257 | extension RomanNumeralSymbolTests {
258 | func test_subtractiveRomanNumeralSymbols() {
259 | for symbol in RomanNumeralSymbol.allSymbolsAscending {
260 | let expectedSubtractiveSymbols: [SubtractiveRomanNumeralSymbol] = RomanNumeral
261 | .subtractiveSymbols(from: symbol.rawValue.tallyMarks.count)
262 | XCTAssertEqual(symbol.subtractiveRomanNumeralSymbols, expectedSubtractiveSymbols)
263 | }
264 | }
265 | }
266 |
--------------------------------------------------------------------------------
/Sources/RomanNumeralKit/Numeric System/RomanNumeralProtocol.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RomanNumeralProtocol.swift
3 | // RomanNumeralKit
4 | //
5 | // Copyright © 2019 Kyle Hughes.
6 | //
7 | // Permission is hereby granted, free of charge, to any person obtaining a copy
8 | // of this software and associated documentation files (the "Software"), to deal
9 | // in the Software without restriction, including without limitation the rights
10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | // copies of the Software, and to permit persons to whom the Software is
12 | // furnished to do so, subject to the following conditions:
13 | //
14 | // The above copyright notice and this permission notice shall be included in
15 | // all copies or substantial portions of the Software.
16 | //
17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23 | // THE SOFTWARE.
24 | //
25 |
26 | /**
27 | Roman numerals are a base-10 numeral system that originated in ancient Rome. They are traditionally composed of seven
28 | symbols with fixed integer values.
29 |
30 | Powers of ten - thousands, hundreds, tens, and units - are written separately from left to right.
31 |
32 | The rules for composing the symbols into Roman numerals, and the integer value represented by the Roman numerals,
33 | are a product of the notation used by the numeral. The most common notation used in modern times is subtractive
34 | notation (see `RomanNumeral`). A common alternative is additive notation (see `AdditiveRomanNumeral`).
35 |
36 | Conceptually, the Roman numeral is an abbreviation for the number of tally marks that are equal to the integer
37 | representation of the Roman numeral.
38 |
39 | See `RomanNumeralSymbolProtocol` and `RomanNumeralSymbol` for the definitions of the traditional symbols.
40 |
41 | - SeeAlso: https://en.wikipedia.org/wiki/Roman_numerals
42 | */
43 | public protocol RomanNumeralProtocol: CustomDebugStringConvertible,
44 | CustomStringConvertible,
45 | ExpressibleByStringLiteral,
46 | Numeric,
47 | Strideable,
48 | RomanNumeralSymbolsConvertible {
49 | // MARK: Typealiases
50 |
51 | associatedtype Stride = Int
52 |
53 | // MARK: Initialization
54 |
55 | /**
56 | Creates a new Roman numeral represented by the value of the given symbols.
57 |
58 | The given symbols must be in the appropriate order for the type of notation.
59 |
60 | The given symbols will be condensed to the most succint representation using the fewest number of symbols. The
61 | symbols that are ultimately produced by the Roman numeral may not be the same symbols as the ones passed in, but
62 | the value will be equivalent.
63 |
64 | The value of the resulting Roman numeral must be within the `minimum` and `maximum` values for the type of
65 | notation.
66 |
67 | - Parameter symbols: The symbols to construct the Roman numeral from.
68 | */
69 | init(symbols: [RomanNumeralSymbol]) throws
70 |
71 | // MARK: Static Interface
72 |
73 | /// The maximum value that can be expressed by the implementing notation.
74 | static var maximum: Self { get }
75 |
76 | /// The minimum value that can be expressed by the implementing notation.
77 | static var minimum: Self { get }
78 |
79 | /**
80 | Condense the given symbols to their most succint representation using the fewest number of symbols.
81 |
82 | For example *VV* will be condensed to *X*.
83 |
84 | - Parameter symbols: The given symbols to condense.
85 | - Returns: The condensed form of the given symbols.
86 | */
87 | static func condense(symbols: [RomanNumeralSymbol]) -> [RomanNumeralSymbol]
88 |
89 | /**
90 | Convert the given symbols into their cumulative integer value for the implementing notation.
91 |
92 | The order of the symbols should follow the rules of the implementing notation.
93 |
94 | - Parameter symbols: The symbols that should be represented as a cumulative integer value.
95 | - Returns: The cumulative integer value of the given symbols.
96 | */
97 | static func int(from symbols: [RomanNumeralSymbol]) -> Int
98 |
99 | /**
100 | Convert the given integer into the corresponding symbols for the implementing notation.
101 |
102 | The returned symbols may not comprise a valid Roman numeral if the given integer value is not within the bounds
103 | defined by the implementing notation.
104 |
105 | - Parameter intValue: The integer to convert into a collection of symbols.
106 | - Returns: The symbolic representation of the given integer.
107 | */
108 | static func symbols(from intValue: Int) -> [RomanNumeralSymbol]
109 |
110 | // MARK: Instance Interface
111 |
112 | /// The symbols that compose the Roman numeral.
113 | var symbols: [RomanNumeralSymbol] { get }
114 |
115 | /// The group of tally marks that back the value of the Roman numeral.
116 | var tallyMarkGroup: RomanNumeralTallyMarkGroup { get }
117 | }
118 |
119 | // MARK: - Public Extension
120 |
121 | public extension RomanNumeralProtocol {
122 | // MARK: Public Initialization
123 |
124 | /**
125 | Creates a new Roman numeral represented by the value of the given symbols.
126 |
127 | The given symbols must be in the appropriate order for the type of notation.
128 |
129 | The given symbols will be condensed to the most succint representation using the fewest number of symbols. The
130 | symbols that are ultimately produced by the Roman numeral may not be the same symbols as the ones passed in, but
131 | the value will be equivalent.
132 |
133 | The value of the resulting Roman numeral must be within the `minimum` and `maximum` values for the type of
134 | notation.
135 |
136 | - Parameter symbols: The symbols to construct the Roman numeral from.
137 | */
138 | init(_ symbols: RomanNumeralSymbol...) throws {
139 | try self.init(symbols: symbols)
140 | }
141 |
142 | /**
143 | Creates a new Roman numeral represented by the value of the given symbol.
144 |
145 | The value of the resulting Roman numeral must be within the `minimum` and `maximum` values for the type of
146 | notation.
147 |
148 | - Parameter symbol: The symbol to construct the Roman numeral from.
149 | */
150 | init(symbol: RomanNumeralSymbol) throws {
151 | try self.init(symbols: [symbol])
152 | }
153 |
154 | /**
155 | Creates a Roman numeral equivalent to the value of the given integer.
156 |
157 | The value of the resulting Roman numeral must be within the `minimum` and `maximum` values for the type of
158 | notation.
159 |
160 | - Parameter int: The integer value to construct an equivalent Roman numeral from.
161 | */
162 | init(from int: Int) throws {
163 | let symbols = Self.symbols(from: int)
164 |
165 | try self.init(symbols: symbols)
166 | }
167 |
168 | /**
169 | Creates a Roman numeral represented by the symbols found in the given string.
170 |
171 | The given symbols must be in the appropriate order for the type of notation.
172 |
173 | The given symbols will be condensed to the most succint representation using the fewest number of symbols. The
174 | symbols that are ultimately produced by the Roman numeral may not be the same symbols as the ones passed in, but
175 | the value will be equivalent.
176 |
177 | The value of the resulting Roman numeral must be within the `minimum` and `maximum` values for the type of
178 | notation.
179 |
180 | The string must exclusively be a valid Roman numeral with no extraneous characters.
181 |
182 | - Parameter string: The string representation of the symbols to construct the Roman numeral from.
183 | */
184 | init(from string: String) throws {
185 | let symbols = try Self.symbols(from: string)
186 |
187 | try self.init(symbols: symbols)
188 | }
189 |
190 | // MARK: Public Static Interface
191 |
192 | /**
193 | Convert the given symbols into their corresponding string representation.
194 |
195 | - Parameter symbols: The given symbol to concatenate into a single string.
196 | - Returns: The string representation of all of the given symbols.
197 | */
198 | static func string(from symbols: [RomanNumeralSymbol]) -> String {
199 | symbols.reduce("") { $0.appending(String($1.stringValue)) }
200 | }
201 |
202 | /**
203 | Convert the given string into the corresponding collection of symbols.
204 |
205 | The string must only contain characters that are valid Roman numeral symbols.
206 |
207 | - Parameter string: The given string to convert into individual symbols.
208 | - Returns: The symbols that were represented by the given string.
209 | */
210 | static func symbols(from string: String) throws -> [RomanNumeralSymbol] {
211 | try string
212 | .lazy
213 | .map { String($0) }
214 | .map { try RomanNumeralSymbol(from: $0) }
215 | }
216 |
217 | // MARK: Public Instance Interface
218 |
219 | /**
220 | The current Roman numeral formatted as a copyright declaration.
221 |
222 | For example: "Copyright © MMX"
223 | */
224 | var copyrightText: String {
225 | "Copyright © \(stringValue)"
226 | }
227 |
228 | /// The current Roman numeral converted into its integer equivalent.
229 | var intValue: Int {
230 | Self.int(from: symbols)
231 | }
232 |
233 | /// The current Roman numeral represented as a string.
234 | var stringValue: String {
235 | Self.string(from: symbols)
236 | }
237 | }
238 |
239 | // MARK: - Comparable Extension
240 |
241 | extension RomanNumeralProtocol {
242 | // MARK: Public Static Interface
243 |
244 | public static func < (lhs: Self, rhs: Self) -> Bool {
245 | guard lhs != rhs else {
246 | return false
247 | }
248 |
249 | return lhs.tallyMarkGroup < rhs.tallyMarkGroup
250 | }
251 | }
252 |
253 | // MARK: - CustomDebugStringConvertible Extension
254 |
255 | extension RomanNumeralProtocol {
256 | // MARK: Public Instance Interface
257 |
258 | public var debugDescription: String {
259 | stringValue
260 | }
261 | }
262 |
263 | // MARK: - CustomStringConvertible Extension
264 |
265 | extension RomanNumeralProtocol {
266 | // MARK: Public Instance Interface
267 |
268 | public var description: String {
269 | stringValue
270 | }
271 | }
272 |
273 | // MARK: - ExpressibleByIntegerLiteral Extension
274 |
275 | extension RomanNumeralProtocol {
276 | // MARK: Public Initialization
277 |
278 | public init(integerLiteral: Int) {
279 | do {
280 | try self.init(from: integerLiteral)
281 | } catch {
282 | switch error {
283 | case RomanNumeralError.valueGreaterThanMaximum:
284 | self = Self.maximum
285 | default:
286 | self = Self.minimum
287 | }
288 | }
289 | }
290 | }
291 |
292 | // MARK: - ExpressibleByStringLiteral Extension
293 |
294 | extension RomanNumeralProtocol {
295 | // MARK: Public Initialization
296 |
297 | public init(stringLiteral: String) {
298 | do {
299 | try self.init(from: stringLiteral)
300 | } catch {
301 | switch error {
302 | case RomanNumeralError.valueGreaterThanMaximum:
303 | self = Self.maximum
304 | default:
305 | self = Self.minimum
306 | }
307 | }
308 | }
309 | }
310 |
311 | // MARK: - Strideable Extension
312 |
313 | extension RomanNumeralProtocol {
314 | // MARK: Public Instance Interface
315 |
316 | public func advanced(by n: Int) -> Self {
317 | Self(integerLiteral: intValue + n)
318 | }
319 |
320 | public func distance(to other: Self) -> Int {
321 | intValue - other.intValue
322 | }
323 | }
324 |
--------------------------------------------------------------------------------
/Sources/RomanNumeralKit/Numeric System/RomanNumeralSymbol.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RomanNumeralSymbol.swift
3 | // RomanNumeralKit
4 | //
5 | // Copyright © 2019 Kyle Hughes.
6 | //
7 | // Permission is hereby granted, free of charge, to any person obtaining a copy
8 | // of this software and associated documentation files (the "Software"), to deal
9 | // in the Software without restriction, including without limitation the rights
10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | // copies of the Software, and to permit persons to whom the Software is
12 | // furnished to do so, subject to the following conditions:
13 | //
14 | // The above copyright notice and this permission notice shall be included in
15 | // all copies or substantial portions of the Software.
16 | //
17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23 | // THE SOFTWARE.
24 | //
25 |
26 | /**
27 | Roman numeral symbols are a collection of letters from the Latin alphabet that are used to represent numbers in the
28 | numeric system of ancient Rome. Each symbol is shorthand for a specific number of tally marks.
29 |
30 | Traditional Roman numerals employ seven symbols, each with a fixed integer value. The number 0 does not have its own
31 | Roman numeral symbol, but the word "nulla" (the Latin word meaning "none") is used in lieu of 0.
32 |
33 | The backing values of the Roman numeral symbols, as found in their `rawValue`, are `RomanNumeralTallyMarkGroups` which
34 | are used to collect all of the corresponding `RomanNumeralTallyMark`s. Any representations of the symbols as Arabic
35 | numerals (e.g. `Int`) are conversions made by counting these tally marks.
36 | */
37 | public enum RomanNumeralSymbol: CaseIterable {
38 | /// The Latin word meaning "none" which is used in lieu of the Western Arabic value "0".
39 | case nulla
40 |
41 | /// The symbol representing the Western Arabic value "1".
42 | case I
43 |
44 | /// The symbol representing the Western Arabic value "5".
45 | case V
46 |
47 | /// The symbol representing the Western Arabic value "10".
48 | case X
49 |
50 | /// The symbol representing the Western Arabic value "50".
51 | case L
52 |
53 | /// The symbol representing the Western Arabic value "100".
54 | case C
55 |
56 | /// The symbol representing the Western Arabic value "500".
57 | case D
58 |
59 | /// The symbol representing the Western Arabic value "1000".
60 | case M
61 |
62 | // MARK: Public Static Interface
63 |
64 | public static func * (
65 | lhs: RomanNumeralSymbol,
66 | rhs: RomanNumeralSymbol
67 | ) -> [RomanNumeralSymbol] {
68 | switch lhs {
69 | case .nulla:
70 | return []
71 | case .I:
72 | switch rhs {
73 | case .nulla:
74 | return []
75 | default:
76 | return [rhs]
77 | }
78 | case .V:
79 | switch rhs {
80 | case .nulla:
81 | return []
82 | case .I:
83 | return [.V]
84 | case .V:
85 | return [.X, .X, .V]
86 | case .X:
87 | return [.L]
88 | case .L:
89 | return [.C, .C, .L]
90 | case .C:
91 | return [.D]
92 | case .D:
93 | return [.M, M, .D]
94 | case .M:
95 | return Array(repeating: .M, count: 5)
96 | }
97 | case .X:
98 | switch rhs {
99 | case .nulla:
100 | return []
101 | case .I:
102 | return [.X]
103 | case .V:
104 | return [.L]
105 | case .X:
106 | return [.C]
107 | case .L:
108 | return [.D]
109 | case .C:
110 | return [.M]
111 | case .D:
112 | return Array(repeating: .M, count: 5)
113 | case .M:
114 | return Array(repeating: .M, count: 10)
115 | }
116 | case .L:
117 | switch rhs {
118 | case .nulla:
119 | return []
120 | case .I:
121 | return [.L]
122 | case .V:
123 | return [.C, .C, .L]
124 | case .X:
125 | return [.D]
126 | case .L:
127 | return [.M, .M, .D]
128 | case .C:
129 | return Array(repeating: .M, count: 5)
130 | case .D:
131 | return Array(repeating: .M, count: 25)
132 | case .M:
133 | return Array(repeating: .M, count: 50)
134 | }
135 | case .C:
136 | switch rhs {
137 | case .nulla:
138 | return []
139 | case .I:
140 | return [.C]
141 | case .V:
142 | return [.D]
143 | case .X:
144 | return [.M]
145 | case .L:
146 | return Array(repeating: .M, count: 5)
147 | case .C:
148 | return Array(repeating: .M, count: 10)
149 | case .D:
150 | return Array(repeating: .M, count: 50)
151 | case .M:
152 | return Array(repeating: .M, count: 100)
153 | }
154 | case .D:
155 | switch rhs {
156 | case .nulla:
157 | return []
158 | case .I:
159 | return [.D]
160 | case .V:
161 | return [.M, .M, .D]
162 | case .X:
163 | return Array(repeating: .M, count: 5)
164 | case .L:
165 | return Array(repeating: .M, count: 25)
166 | case .C:
167 | return Array(repeating: .M, count: 50)
168 | case .D:
169 | return Array(repeating: .M, count: 250)
170 | case .M:
171 | return Array(repeating: .M, count: 500)
172 | }
173 | case .M:
174 | switch rhs {
175 | case .nulla:
176 | return []
177 | case .I:
178 | return [.M]
179 | case .V:
180 | return Array(repeating: .M, count: 5)
181 | case .X:
182 | return Array(repeating: .M, count: 10)
183 | case .L:
184 | return Array(repeating: .M, count: 50)
185 | case .C:
186 | return Array(repeating: .M, count: 100)
187 | case .D:
188 | return Array(repeating: .M, count: 500)
189 | case .M:
190 | return Array(repeating: .M, count: 1000)
191 | }
192 | }
193 | }
194 |
195 | // MARK: Public Instance Interface
196 |
197 | /**
198 | The representation of the current Roman numeral symbol as a collection of its lesser symbol.
199 |
200 | For example, the expansion of `L` is equal to `XXXXX`.
201 |
202 | - Note: The expansion of `I` is its identity because it has no lesser symbol with a valid value. The expansion of
203 | `nulla` is an empty `Array` for the same reason.
204 | */
205 | public var expandedIntoLesserSymbol: [RomanNumeralSymbol] {
206 | switch self {
207 | case .nulla:
208 | return []
209 | case .I:
210 | return [.I]
211 | case .V:
212 | return [.I, .I, .I, .I, .I]
213 | case .X:
214 | return [.V, .V]
215 | case .L:
216 | return [.X, .X, .X, .X, .X]
217 | case .C:
218 | return [.L, .L]
219 | case .D:
220 | return [.C, .C, .C, .C, .C]
221 | case .M:
222 | return [.D, .D]
223 | }
224 | }
225 | }
226 |
227 | // MARK: - AdditiveRomanNumeralSymbolConvertible Extension
228 |
229 | extension RomanNumeralSymbol: AdditiveRomanNumeralSymbolConvertible {
230 | // MARK: Public Instance Interface
231 |
232 | public var additiveRomanNumeralSymbol: RomanNumeralSymbol {
233 | self
234 | }
235 | }
236 |
237 | // MARK: - RawRepresentable Extension
238 |
239 | extension RomanNumeralSymbol: RawRepresentable {
240 | // MARK: Public Typealiases
241 |
242 | public typealias RawValue = RomanNumeralTallyMarkGroup
243 |
244 | // MARK: Public Initialization
245 |
246 | public init?(rawValue: RomanNumeralTallyMarkGroup) {
247 | switch rawValue {
248 | case .nulla:
249 | self = .nulla
250 | case .one:
251 | self = .I
252 | case .five:
253 | self = .V
254 | case .ten:
255 | self = .X
256 | case .fifty:
257 | self = .L
258 | case .oneHundred:
259 | self = .C
260 | case .fiveHundred:
261 | self = .D
262 | case .oneThousand:
263 | self = .M
264 | default:
265 | return nil
266 | }
267 | }
268 |
269 | // MARK: Public Instance Interface
270 |
271 | public var rawValue: RomanNumeralTallyMarkGroup {
272 | switch self {
273 | case .nulla:
274 | return .nulla
275 | case .I:
276 | return .one
277 | case .V:
278 | return .five
279 | case .X:
280 | return .ten
281 | case .L:
282 | return .fifty
283 | case .C:
284 | return .oneHundred
285 | case .D:
286 | return .fiveHundred
287 | case .M:
288 | return .oneThousand
289 | }
290 | }
291 | }
292 |
293 | // MARK: - RomanNumeralSymbolConvertible Extension
294 |
295 | extension RomanNumeralSymbol: RomanNumeralSymbolConvertible {
296 | // MARK: Public Instance Interface
297 |
298 | public var romanNumeralSymbol: RomanNumeralSymbol {
299 | self
300 | }
301 | }
302 |
303 | // MARK: - RomanNumeralSymbolProtocol Extension
304 |
305 | extension RomanNumeralSymbol: RomanNumeralSymbolProtocol {
306 | // MARK: Public Static Properties
307 |
308 | public static let allSymbolsAscending: [RomanNumeralSymbol] = [.I, .V, .X, .L, .C, .D, .M]
309 |
310 | public static let allSymbolsDescending: [RomanNumeralSymbol] = allSymbolsAscending.reversed()
311 |
312 | // MARK: Public Initialization
313 |
314 | public init(from stringValue: String) throws {
315 | let potentialSymbol = RomanNumeralSymbol.allCases
316 | .filter { $0.stringValue == stringValue }
317 | .first
318 |
319 | guard let symbol = potentialSymbol else {
320 | throw RomanNumeralSymbolError.unrecognizedString(string: stringValue)
321 | }
322 |
323 | self = symbol
324 | }
325 |
326 | // MARK: Public Instance Interface
327 |
328 | public var stringValue: String {
329 | switch self {
330 | case .nulla:
331 | return "N"
332 | case .I:
333 | return "I"
334 | case .V:
335 | return "V"
336 | case .X:
337 | return "X"
338 | case .L:
339 | return "L"
340 | case .C:
341 | return "C"
342 | case .D:
343 | return "D"
344 | case .M:
345 | return "M"
346 | }
347 | }
348 |
349 | public var lesserSymbol: RomanNumeralSymbol? {
350 | switch self {
351 | case .nulla:
352 | return nil
353 | case .I:
354 | return nil
355 | case .V:
356 | return .I
357 | case .X:
358 | return .V
359 | case .L:
360 | return .X
361 | case .C:
362 | return .L
363 | case .D:
364 | return .C
365 | case .M:
366 | return .D
367 | }
368 | }
369 | }
370 |
371 | // MARK: - SubtractiveRomanNumeralSymbolConvertible Extension
372 |
373 | extension RomanNumeralSymbol: SubtractiveRomanNumeralSymbolConvertible {
374 | // MARK: Public Instance Interface
375 |
376 | public var subtractiveRomanNumeralSymbol: SubtractiveRomanNumeralSymbol {
377 | switch self {
378 | case .nulla:
379 | return .nulla
380 | case .I:
381 | return .I
382 | case .V:
383 | return .V
384 | case .X:
385 | return .X
386 | case .L:
387 | return .L
388 | case .C:
389 | return .C
390 | case .D:
391 | return .D
392 | case .M:
393 | return .M
394 | }
395 | }
396 | }
397 |
398 | // MARK: - SubtractiveRomanNumeralSymbolsConvertible Extension
399 |
400 | extension RomanNumeralSymbol: SubtractiveRomanNumeralSymbolsConvertible {
401 | // MARK: Public Instance Interface
402 |
403 | public var subtractiveRomanNumeralSymbols: [SubtractiveRomanNumeralSymbol] {
404 | [subtractiveRomanNumeralSymbol]
405 | }
406 | }
407 |
--------------------------------------------------------------------------------
/Tests/RomanNumeralKitTests/Numeric System/Subtractive Notation/SubtractiveRomanNumeralSymbolTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SubtractiveRomanNumeralSymbolTests.swift
3 | // RomanNumeralKitTests
4 | //
5 | // Copyright © 2019 Kyle Hughes.
6 | //
7 | // Permission is hereby granted, free of charge, to any person obtaining a copy
8 | // of this software and associated documentation files (the "Software"), to deal
9 | // in the Software without restriction, including without limitation the rights
10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | // copies of the Software, and to permit persons to whom the Software is
12 | // furnished to do so, subject to the following conditions:
13 | //
14 | // The above copyright notice and this permission notice shall be included in
15 | // all copies or substantial portions of the Software.
16 | //
17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23 | // THE SOFTWARE.
24 | //
25 |
26 | import XCTest
27 |
28 | @testable import RomanNumeralKit
29 |
30 | class SubtractiveRomanNumeralSymbolTests: XCTestCase {
31 | // MARK: XCTestCase Implementation
32 |
33 | override func setUp() {}
34 |
35 | override func tearDown() {}
36 |
37 | // MARK: Tests
38 |
39 | func test_romanNumeralSymbols() {
40 | XCTAssertEqual(SubtractiveRomanNumeralSymbol.nulla.romanNumeralSymbols, [.nulla])
41 | XCTAssertEqual(SubtractiveRomanNumeralSymbol.I.romanNumeralSymbols, [.I])
42 | XCTAssertEqual(SubtractiveRomanNumeralSymbol.IV.romanNumeralSymbols, [.I, .V])
43 | XCTAssertEqual(SubtractiveRomanNumeralSymbol.V.romanNumeralSymbols, [.V])
44 | XCTAssertEqual(SubtractiveRomanNumeralSymbol.IX.romanNumeralSymbols, [.I, .X])
45 | XCTAssertEqual(SubtractiveRomanNumeralSymbol.X.romanNumeralSymbols, [.X])
46 | XCTAssertEqual(SubtractiveRomanNumeralSymbol.XL.romanNumeralSymbols, [.X, .L])
47 | XCTAssertEqual(SubtractiveRomanNumeralSymbol.L.romanNumeralSymbols, [.L])
48 | XCTAssertEqual(SubtractiveRomanNumeralSymbol.XC.romanNumeralSymbols, [.X, .C])
49 | XCTAssertEqual(SubtractiveRomanNumeralSymbol.C.romanNumeralSymbols, [.C])
50 | XCTAssertEqual(SubtractiveRomanNumeralSymbol.CD.romanNumeralSymbols, [.C, .D])
51 | XCTAssertEqual(SubtractiveRomanNumeralSymbol.D.romanNumeralSymbols, [.D])
52 | XCTAssertEqual(SubtractiveRomanNumeralSymbol.CM.romanNumeralSymbols, [.C, .M])
53 | XCTAssertEqual(SubtractiveRomanNumeralSymbol.M.romanNumeralSymbols, [.M])
54 | }
55 | }
56 |
57 | // MARK: - AdditiveRomanNumeralSymbolsConvertible Tests
58 |
59 | extension SubtractiveRomanNumeralSymbolTests {
60 | func test_basicRomanNumeralSymbols() {
61 | for symbol in SubtractiveRomanNumeralSymbol.allSymbolsAscending {
62 | let expectedBasicRomanNumeralSymbols = AdditiveRomanNumeral.symbols(from: symbol.rawValue.tallyMarks.count)
63 | XCTAssertEqual(symbol.additiveRomanNumeralSymbols, expectedBasicRomanNumeralSymbols)
64 | }
65 | }
66 | }
67 |
68 | // MARK: - Comparable Tests
69 |
70 | extension SubtractiveRomanNumeralSymbolTests {
71 | func test_lessThan() {
72 | XCTAssert(SubtractiveRomanNumeralSymbol.I < SubtractiveRomanNumeralSymbol.IV)
73 | XCTAssert(SubtractiveRomanNumeralSymbol.IV < SubtractiveRomanNumeralSymbol.V)
74 | XCTAssert(SubtractiveRomanNumeralSymbol.V < SubtractiveRomanNumeralSymbol.IX)
75 | XCTAssert(SubtractiveRomanNumeralSymbol.IX < SubtractiveRomanNumeralSymbol.X)
76 | XCTAssert(SubtractiveRomanNumeralSymbol.X < SubtractiveRomanNumeralSymbol.XL)
77 | XCTAssert(SubtractiveRomanNumeralSymbol.XL < SubtractiveRomanNumeralSymbol.L)
78 | XCTAssert(SubtractiveRomanNumeralSymbol.L < SubtractiveRomanNumeralSymbol.XC)
79 | XCTAssert(SubtractiveRomanNumeralSymbol.XC < SubtractiveRomanNumeralSymbol.C)
80 | XCTAssert(SubtractiveRomanNumeralSymbol.C < SubtractiveRomanNumeralSymbol.CD)
81 | XCTAssert(SubtractiveRomanNumeralSymbol.CD < SubtractiveRomanNumeralSymbol.D)
82 | XCTAssert(SubtractiveRomanNumeralSymbol.D < SubtractiveRomanNumeralSymbol.CM)
83 | XCTAssert(SubtractiveRomanNumeralSymbol.CM < SubtractiveRomanNumeralSymbol.M)
84 |
85 | XCTAssertFalse(SubtractiveRomanNumeralSymbol.IV < SubtractiveRomanNumeralSymbol.I)
86 | XCTAssertFalse(SubtractiveRomanNumeralSymbol.V < SubtractiveRomanNumeralSymbol.IV)
87 | XCTAssertFalse(SubtractiveRomanNumeralSymbol.IX < SubtractiveRomanNumeralSymbol.V)
88 | XCTAssertFalse(SubtractiveRomanNumeralSymbol.X < SubtractiveRomanNumeralSymbol.IX)
89 | XCTAssertFalse(SubtractiveRomanNumeralSymbol.XL < SubtractiveRomanNumeralSymbol.X)
90 | XCTAssertFalse(SubtractiveRomanNumeralSymbol.L < SubtractiveRomanNumeralSymbol.XL)
91 | XCTAssertFalse(SubtractiveRomanNumeralSymbol.XC < SubtractiveRomanNumeralSymbol.L)
92 | XCTAssertFalse(SubtractiveRomanNumeralSymbol.C < SubtractiveRomanNumeralSymbol.XC)
93 | XCTAssertFalse(SubtractiveRomanNumeralSymbol.CD < SubtractiveRomanNumeralSymbol.C)
94 | XCTAssertFalse(SubtractiveRomanNumeralSymbol.D < SubtractiveRomanNumeralSymbol.CD)
95 | XCTAssertFalse(SubtractiveRomanNumeralSymbol.CM < SubtractiveRomanNumeralSymbol.D)
96 | XCTAssertFalse(SubtractiveRomanNumeralSymbol.M < SubtractiveRomanNumeralSymbol.CM)
97 |
98 | XCTAssertFalse(SubtractiveRomanNumeralSymbol.I < SubtractiveRomanNumeralSymbol.I)
99 | XCTAssertFalse(SubtractiveRomanNumeralSymbol.IV < SubtractiveRomanNumeralSymbol.IV)
100 | XCTAssertFalse(SubtractiveRomanNumeralSymbol.V < SubtractiveRomanNumeralSymbol.V)
101 | XCTAssertFalse(SubtractiveRomanNumeralSymbol.IX < SubtractiveRomanNumeralSymbol.IX)
102 | XCTAssertFalse(SubtractiveRomanNumeralSymbol.X < SubtractiveRomanNumeralSymbol.X)
103 | XCTAssertFalse(SubtractiveRomanNumeralSymbol.XL < SubtractiveRomanNumeralSymbol.XL)
104 | XCTAssertFalse(SubtractiveRomanNumeralSymbol.L < SubtractiveRomanNumeralSymbol.L)
105 | XCTAssertFalse(SubtractiveRomanNumeralSymbol.XC < SubtractiveRomanNumeralSymbol.XC)
106 | XCTAssertFalse(SubtractiveRomanNumeralSymbol.C < SubtractiveRomanNumeralSymbol.C)
107 | XCTAssertFalse(SubtractiveRomanNumeralSymbol.CD < SubtractiveRomanNumeralSymbol.CD)
108 | XCTAssertFalse(SubtractiveRomanNumeralSymbol.D < SubtractiveRomanNumeralSymbol.D)
109 | XCTAssertFalse(SubtractiveRomanNumeralSymbol.CM < SubtractiveRomanNumeralSymbol.CM)
110 | XCTAssertFalse(SubtractiveRomanNumeralSymbol.M < SubtractiveRomanNumeralSymbol.M)
111 | }
112 | }
113 |
114 | // MARK: - CustomDebugStringConvertible Tests
115 |
116 | extension SubtractiveRomanNumeralSymbolTests {
117 | func test_debugDescription() {
118 | for symbol in SubtractiveRomanNumeralSymbol.allCases {
119 | XCTAssertEqual(symbol.debugDescription, symbol.stringValue)
120 | }
121 | }
122 | }
123 |
124 | // MARK: - CustomStringConvertible Tests
125 |
126 | extension SubtractiveRomanNumeralSymbolTests {
127 | func test_description() {
128 | for symbol in SubtractiveRomanNumeralSymbol.allCases {
129 | XCTAssertEqual(symbol.description, symbol.stringValue)
130 | }
131 | }
132 | }
133 |
134 | // MARK: - RawRepresentable Tests
135 |
136 | extension SubtractiveRomanNumeralSymbolTests {
137 | func test_init_rawValue_success() {
138 | XCTAssertEqual(SubtractiveRomanNumeralSymbol(rawValue: .nulla), .nulla)
139 | XCTAssertEqual(SubtractiveRomanNumeralSymbol(rawValue: .one), .I)
140 | XCTAssertEqual(SubtractiveRomanNumeralSymbol(rawValue: .four), .IV)
141 | XCTAssertEqual(SubtractiveRomanNumeralSymbol(rawValue: .five), .V)
142 | XCTAssertEqual(SubtractiveRomanNumeralSymbol(rawValue: .nine), .IX)
143 | XCTAssertEqual(SubtractiveRomanNumeralSymbol(rawValue: .ten), .X)
144 | XCTAssertEqual(SubtractiveRomanNumeralSymbol(rawValue: .forty), .XL)
145 | XCTAssertEqual(SubtractiveRomanNumeralSymbol(rawValue: .fifty), .L)
146 | XCTAssertEqual(SubtractiveRomanNumeralSymbol(rawValue: .ninety), .XC)
147 | XCTAssertEqual(SubtractiveRomanNumeralSymbol(rawValue: .oneHundred), .C)
148 | XCTAssertEqual(SubtractiveRomanNumeralSymbol(rawValue: .fourHundred), .CD)
149 | XCTAssertEqual(SubtractiveRomanNumeralSymbol(rawValue: .fiveHundred), .D)
150 | XCTAssertEqual(SubtractiveRomanNumeralSymbol(rawValue: .nineHundred), .CM)
151 | XCTAssertEqual(SubtractiveRomanNumeralSymbol(rawValue: .oneThousand), .M)
152 | }
153 |
154 | func test_init_rawValue_invalid() {
155 | // Given...
156 |
157 | let two = RomanNumeralTallyMarkGroup(numberOfTallyMarks: 2)
158 | let eleven = RomanNumeralTallyMarkGroup(numberOfTallyMarks: 11)
159 | let oneHundredOne = RomanNumeralTallyMarkGroup(numberOfTallyMarks: 101)
160 | let oneThousandOne = RomanNumeralTallyMarkGroup(numberOfTallyMarks: 1001)
161 |
162 | // Then...
163 |
164 | XCTAssertNil(SubtractiveRomanNumeralSymbol(rawValue: two))
165 | XCTAssertNil(SubtractiveRomanNumeralSymbol(rawValue: eleven))
166 | XCTAssertNil(SubtractiveRomanNumeralSymbol(rawValue: oneHundredOne))
167 | XCTAssertNil(SubtractiveRomanNumeralSymbol(rawValue: oneThousandOne))
168 | }
169 | }
170 |
171 | // MARK: - RomanNumeralSymbolProtocol Tests
172 |
173 | extension SubtractiveRomanNumeralSymbolTests {
174 | func test_init_fromString_success() {
175 | XCTAssertEqual(try SubtractiveRomanNumeralSymbol(from: "N"), .nulla)
176 | XCTAssertEqual(try SubtractiveRomanNumeralSymbol(from: "I"), .I)
177 | XCTAssertEqual(try SubtractiveRomanNumeralSymbol(from: "IV"), .IV)
178 | XCTAssertEqual(try SubtractiveRomanNumeralSymbol(from: "V"), .V)
179 | XCTAssertEqual(try SubtractiveRomanNumeralSymbol(from: "IX"), .IX)
180 | XCTAssertEqual(try SubtractiveRomanNumeralSymbol(from: "X"), .X)
181 | XCTAssertEqual(try SubtractiveRomanNumeralSymbol(from: "XL"), .XL)
182 | XCTAssertEqual(try SubtractiveRomanNumeralSymbol(from: "L"), .L)
183 | XCTAssertEqual(try SubtractiveRomanNumeralSymbol(from: "XC"), .XC)
184 | XCTAssertEqual(try SubtractiveRomanNumeralSymbol(from: "C"), .C)
185 | XCTAssertEqual(try SubtractiveRomanNumeralSymbol(from: "CD"), .CD)
186 | XCTAssertEqual(try SubtractiveRomanNumeralSymbol(from: "D"), .D)
187 | XCTAssertEqual(try SubtractiveRomanNumeralSymbol(from: "CM"), .CM)
188 | XCTAssertEqual(try SubtractiveRomanNumeralSymbol(from: "M"), .M)
189 | }
190 |
191 | func test_init_fromString_invalidCharacter() {
192 | XCTAssertThrowsError(try SubtractiveRomanNumeralSymbol(from: "R")) {
193 | switch $0 as? RomanNumeralSymbolError {
194 | case .none:
195 | XCTFail()
196 | case let .some(symbolError):
197 | switch symbolError {
198 | case .unrecognizedString:
199 | break
200 | }
201 | }
202 | }
203 | }
204 |
205 | func test_stringValue() {
206 | XCTAssertEqual(SubtractiveRomanNumeralSymbol.nulla.stringValue, "N")
207 | XCTAssertEqual(SubtractiveRomanNumeralSymbol.I.stringValue, "I")
208 | XCTAssertEqual(SubtractiveRomanNumeralSymbol.IV.stringValue, "IV")
209 | XCTAssertEqual(SubtractiveRomanNumeralSymbol.V.stringValue, "V")
210 | XCTAssertEqual(SubtractiveRomanNumeralSymbol.IX.stringValue, "IX")
211 | XCTAssertEqual(SubtractiveRomanNumeralSymbol.X.stringValue, "X")
212 | XCTAssertEqual(SubtractiveRomanNumeralSymbol.XL.stringValue, "XL")
213 | XCTAssertEqual(SubtractiveRomanNumeralSymbol.L.stringValue, "L")
214 | XCTAssertEqual(SubtractiveRomanNumeralSymbol.XC.stringValue, "XC")
215 | XCTAssertEqual(SubtractiveRomanNumeralSymbol.C.stringValue, "C")
216 | XCTAssertEqual(SubtractiveRomanNumeralSymbol.CD.stringValue, "CD")
217 | XCTAssertEqual(SubtractiveRomanNumeralSymbol.D.stringValue, "D")
218 | XCTAssertEqual(SubtractiveRomanNumeralSymbol.CM.stringValue, "CM")
219 | XCTAssertEqual(SubtractiveRomanNumeralSymbol.M.stringValue, "M")
220 | }
221 |
222 | func test_lesserSymbol() {
223 | XCTAssertEqual(SubtractiveRomanNumeralSymbol.nulla.lesserSymbol, nil)
224 | XCTAssertEqual(SubtractiveRomanNumeralSymbol.I.lesserSymbol, nil)
225 | XCTAssertEqual(SubtractiveRomanNumeralSymbol.IV.lesserSymbol, .I)
226 | XCTAssertEqual(SubtractiveRomanNumeralSymbol.V.lesserSymbol, .IV)
227 | XCTAssertEqual(SubtractiveRomanNumeralSymbol.IX.lesserSymbol, .V)
228 | XCTAssertEqual(SubtractiveRomanNumeralSymbol.X.lesserSymbol, .IX)
229 | XCTAssertEqual(SubtractiveRomanNumeralSymbol.XL.lesserSymbol, .X)
230 | XCTAssertEqual(SubtractiveRomanNumeralSymbol.L.lesserSymbol, .XL)
231 | XCTAssertEqual(SubtractiveRomanNumeralSymbol.XC.lesserSymbol, .L)
232 | XCTAssertEqual(SubtractiveRomanNumeralSymbol.C.lesserSymbol, .XC)
233 | XCTAssertEqual(SubtractiveRomanNumeralSymbol.CD.lesserSymbol, .C)
234 | XCTAssertEqual(SubtractiveRomanNumeralSymbol.D.lesserSymbol, .CD)
235 | XCTAssertEqual(SubtractiveRomanNumeralSymbol.CM.lesserSymbol, .D)
236 | XCTAssertEqual(SubtractiveRomanNumeralSymbol.M.lesserSymbol, .CM)
237 | }
238 | }
239 |
240 | // MARK: - RomanNumeralConvertible Tests
241 |
242 | extension SubtractiveRomanNumeralSymbolTests {
243 | func test_subtractiveRomanNumeral() {
244 | for subtractiveSymbol in SubtractiveRomanNumeralSymbol.allSymbolsAscending {
245 | XCTAssertEqual(
246 | subtractiveSymbol.romanNumeral,
247 | try RomanNumeral(subtractiveSymbols: [subtractiveSymbol])
248 | )
249 | }
250 | }
251 | }
252 |
253 | // MARK: - SubtractiveRomanNumeralSymbolConvertible Tests
254 |
255 | extension SubtractiveRomanNumeralSymbolTests {
256 | func test_subtractiveRomanNumeralSymbolConvertible() {
257 | for subtractiveSymbol in SubtractiveRomanNumeralSymbol.allSymbolsAscending {
258 | XCTAssertEqual(subtractiveSymbol.subtractiveRomanNumeralSymbol, subtractiveSymbol)
259 | }
260 | }
261 | }
262 |
263 | // MARK: - SubtractiveRomanNumeralSymbolsConvertible Tests
264 |
265 | extension SubtractiveRomanNumeralSymbolTests {
266 | func test_subtractiveRomanNumeralSymbolsConvertible() {
267 | for subtractiveSymbol in SubtractiveRomanNumeralSymbol.allSymbolsAscending {
268 | XCTAssertEqual(subtractiveSymbol.subtractiveRomanNumeralSymbols, [subtractiveSymbol])
269 | }
270 | }
271 | }
272 |
--------------------------------------------------------------------------------
/Sources/RomanNumeralKit/Numeric System/Additive Notation/AdditiveRomanNumeral.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AdditiveRomanNumeral.swift
3 | // RomanNumeralKit
4 | //
5 | // Copyright © 2019 Kyle Hughes.
6 | //
7 | // Permission is hereby granted, free of charge, to any person obtaining a copy
8 | // of this software and associated documentation files (the "Software"), to deal
9 | // in the Software without restriction, including without limitation the rights
10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | // copies of the Software, and to permit persons to whom the Software is
12 | // furnished to do so, subject to the following conditions:
13 | //
14 | // The above copyright notice and this permission notice shall be included in
15 | // all copies or substantial portions of the Software.
16 | //
17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23 | // THE SOFTWARE.
24 | //
25 |
26 | /**
27 | A Roman numeral that represents its value using traditional symbols arranged in additive notation. The Roman numeral
28 | is condensed to its shortest representation using the fewest number of symbols.
29 |
30 | The value of an additive Roman numeral is accumalated by adding the symbols from left-to-right, where the symbols are
31 | arranged in order from greatest to least.
32 |
33 | For example, in additive notation *IIII* equals 4, and *VIIII* equals 9. There are no abbreviations like in
34 | subtractive notation.
35 |
36 | Conceptually, the Roman numeral is an abbreviation for the number of tally marks that are equal to the integer
37 | representation of the Roman numeral.
38 |
39 | See `RomanNumeralSymbolProtocol` and `RomanNumeralSymbol` for the definitions of the traditional symbols.
40 |
41 | - SeeAlso: https://en.wikipedia.org/wiki/Roman_numerals#Use_of_additive_notation
42 | */
43 | public struct AdditiveRomanNumeral: AdditiveRomanNumeralSymbolsConvertible {
44 | // MARK: Public Static Properties
45 |
46 | public static let maximum = AdditiveRomanNumeral(unsafeSymbols: [.M, .M, .M, .D, .C, .C, .C, .C, .L, .X, .X, .X, .X, .V, .I, .I, .I, .I])
47 | public static let minimum = AdditiveRomanNumeral(unsafeSymbols: [.I])
48 |
49 | // MARK: Public Instance Properties
50 |
51 | public private(set) var additiveRomanNumeralSymbols: [RomanNumeralSymbol]
52 |
53 | // MARK: Internal Initialization
54 |
55 | internal init(unsafeSymbols: [RomanNumeralSymbol]) {
56 | additiveRomanNumeralSymbols = unsafeSymbols
57 | }
58 |
59 | // MARK: Internal Static Interface
60 |
61 | /**
62 | Convert the given additive symbols into their `SubtractiveRomanNumeralSymbol` equivalents.
63 |
64 | The symbols will be converted directly and will come out in additive notation using the alternate symbol type.
65 |
66 | - Parameter additiveSymbols: The additive symbols to convert.
67 | - Returns: The `SubtractiveRomanNumeralSymbol` equivalent of the additive symbols.
68 | */
69 | internal static func convert(
70 | toSymbolEquivalentSubtractiveSymbols additiveSymbols: [RomanNumeralSymbol]
71 | ) -> [SubtractiveRomanNumeralSymbol] {
72 | var lastProcessedIndex = -1
73 |
74 | return additiveSymbols
75 | .lazy
76 | .enumerated()
77 | .compactMap { index, additiveSymbol in
78 | guard lastProcessedIndex < index else {
79 | return nil
80 | }
81 |
82 | let nextIndex = index + 1
83 |
84 | guard nextIndex < additiveSymbols.count else {
85 | lastProcessedIndex = index
86 |
87 | return additiveSymbol.subtractiveRomanNumeralSymbol
88 | }
89 |
90 | let nextAdditiveSymbol = additiveSymbols[nextIndex]
91 |
92 | guard
93 | let subtractiveSymbolIndex = SubtractiveRomanNumeralSymbol.allRomanNumeralSymbolsAscending
94 | .firstIndex(of: [additiveSymbol, nextAdditiveSymbol])
95 | else {
96 | lastProcessedIndex = index
97 |
98 | return additiveSymbol.subtractiveRomanNumeralSymbol
99 | }
100 |
101 | lastProcessedIndex = nextIndex
102 |
103 | return SubtractiveRomanNumeralSymbol.allSymbolsAscending[subtractiveSymbolIndex]
104 | }
105 | }
106 |
107 | /**
108 | Convert the given additive symbols into their `SubtractiveRomanNumeralSymbol` equivalent using subtractive
109 | notation.
110 |
111 | The symbols will come out in subtractive notation with the same value as their given additive counterparts.
112 |
113 | - Parameter additiveSymbols: The additive symbols to convert.
114 | - Returns: The `SubtractiveRomanNumeralSymbol` equivalent of the additive symbols in subtractive notation.
115 | */
116 | internal static func convert(
117 | toValueEquivalentSubtractiveSymbols additiveSymbols: [RomanNumeralSymbol]
118 | ) -> [SubtractiveRomanNumeralSymbol] {
119 | let enumeratedConvertedSubtractiveSymbols = SubtractiveRomanNumeralSymbol
120 | .allAdditiveRomanNumeralSymbolsDescending
121 | .enumerated()
122 |
123 | var remainingAdditiveSymbols = additiveSymbols.filter { $0 != .nulla }
124 | var subtractiveSymbols: [SubtractiveRomanNumeralSymbol] = []
125 |
126 | while !remainingAdditiveSymbols.isEmpty {
127 | for (index, convertedSubtractiveSymbols) in enumeratedConvertedSubtractiveSymbols {
128 | guard remainingAdditiveSymbols.starts(with: convertedSubtractiveSymbols) else {
129 | continue
130 | }
131 |
132 | let subtractiveSymbol = SubtractiveRomanNumeralSymbol.allSymbolsDescending[index]
133 | subtractiveSymbols.append(subtractiveSymbol)
134 | remainingAdditiveSymbols.removeSubrange(0 ..< convertedSubtractiveSymbols.count)
135 |
136 | break
137 | }
138 | }
139 |
140 | return subtractiveSymbols
141 | }
142 | }
143 |
144 | // MARK: - AdditiveArithmetic Extension
145 |
146 | extension AdditiveRomanNumeral {
147 | // MARK: Public Static Interface
148 |
149 | public static func + (
150 | lhs: AdditiveRomanNumeral,
151 | rhs: AdditiveRomanNumeral
152 | ) -> AdditiveRomanNumeral {
153 | let result: AdditiveRomanNumeral
154 | do {
155 | result = try add(lhs: lhs, rhs: rhs)
156 | } catch {
157 | switch error {
158 | case RomanNumeralError.valueGreaterThanMaximum:
159 | result = AdditiveRomanNumeral.maximum
160 | default:
161 | result = AdditiveRomanNumeral.minimum
162 | }
163 | }
164 |
165 | return result
166 | }
167 |
168 | public static func += (lhs: inout AdditiveRomanNumeral, rhs: AdditiveRomanNumeral) {
169 | lhs = lhs + rhs
170 | }
171 |
172 | public static func - (
173 | lhs: AdditiveRomanNumeral,
174 | rhs: AdditiveRomanNumeral
175 | ) -> AdditiveRomanNumeral {
176 | let result: AdditiveRomanNumeral
177 | do {
178 | result = try subtract(lhs: lhs, rhs: rhs)
179 | } catch {
180 | switch error {
181 | case RomanNumeralError.valueGreaterThanMaximum:
182 | result = AdditiveRomanNumeral.maximum
183 | default:
184 | result = AdditiveRomanNumeral.minimum
185 | }
186 | }
187 |
188 | return result
189 | }
190 |
191 | public static func -= (lhs: inout AdditiveRomanNumeral, rhs: AdditiveRomanNumeral) {
192 | lhs = lhs - rhs
193 | }
194 |
195 | // MARK: Internal Static Interface
196 |
197 | /**
198 | - SeeAlso: http://turner.faculty.swau.edu/mathematics/materialslibrary/roman/
199 | */
200 | internal static func add(
201 | lhs: AdditiveRomanNumeral,
202 | rhs: AdditiveRomanNumeral
203 | ) throws -> AdditiveRomanNumeral {
204 | let concatenatedSymbols = lhs.additiveRomanNumeralSymbols + rhs.additiveRomanNumeralSymbols
205 | let condensedSymbols = AdditiveRomanNumeral.condense(symbols: concatenatedSymbols)
206 |
207 | return try AdditiveRomanNumeral(symbols: condensedSymbols)
208 | }
209 |
210 | /**
211 | - SeeAlso: http://turner.faculty.swau.edu/mathematics/materialslibrary/roman/
212 | */
213 | internal static func subtract(
214 | lhs: AdditiveRomanNumeral,
215 | rhs: AdditiveRomanNumeral
216 | ) throws -> AdditiveRomanNumeral {
217 | var eliminationResult = eliminateCommonSymbols(
218 | betweenLeft: lhs.additiveRomanNumeralSymbols,
219 | andRight: rhs.additiveRomanNumeralSymbols
220 | )
221 |
222 | while !eliminationResult.right.isEmpty {
223 | let expandedRemainingLeftSymbols = try expandLargestSymbol(
224 | fromRight: eliminationResult.right,
225 | inLeft: eliminationResult.left
226 | )
227 | eliminationResult = eliminateCommonSymbols(
228 | betweenLeft: expandedRemainingLeftSymbols,
229 | andRight: eliminationResult.right
230 | )
231 | }
232 |
233 | return try AdditiveRomanNumeral(symbols: eliminationResult.left)
234 | }
235 |
236 | // MARK: Private Static Interface
237 |
238 | private static func eliminateCommonSymbols(
239 | betweenLeft left: [RomanNumeralSymbol],
240 | andRight right: [RomanNumeralSymbol]
241 | ) -> (left: [RomanNumeralSymbol], right: [RomanNumeralSymbol]) {
242 | var remainingLeftSymbols = left
243 | var remainingRightSymbols = right
244 |
245 | for rightSymbol in remainingRightSymbols.reversed() {
246 | guard
247 | let lastSymbolIndexInLeft = remainingLeftSymbols.lastIndex(of: rightSymbol),
248 | let rightSymbolIndex = remainingRightSymbols.lastIndex(of: rightSymbol)
249 | else {
250 | continue
251 | }
252 |
253 | remainingLeftSymbols.remove(at: lastSymbolIndexInLeft)
254 | remainingRightSymbols.remove(at: rightSymbolIndex)
255 | }
256 |
257 | return (left: remainingLeftSymbols, right: remainingRightSymbols)
258 | }
259 |
260 | private static func expandLargestSymbol(
261 | fromRight right: [RomanNumeralSymbol],
262 | inLeft left: [RomanNumeralSymbol]
263 | ) throws -> [RomanNumeralSymbol] {
264 | guard let greatestRightSymbol = right.max() else {
265 | throw RomanNumeralArithmeticError.ambiguousSubtractionError
266 | }
267 |
268 | let leftEnumeration = Array(left.enumerated())
269 |
270 | guard let greaterLeftSymbolEnumeration = leftEnumeration.last(where: { greatestRightSymbol < $1 }) else {
271 | throw RomanNumeralArithmeticError.ambiguousSubtractionError
272 | }
273 |
274 | var expandedGreaterLeftSymbols: [RomanNumeralSymbol] = greaterLeftSymbolEnumeration
275 | .element
276 | .expandedIntoLesserSymbol
277 |
278 | while
279 | let expandedGreaterLeftSymbol = expandedGreaterLeftSymbols.first,
280 | expandedGreaterLeftSymbol != greatestRightSymbol {
281 | expandedGreaterLeftSymbols = expandedGreaterLeftSymbols.flatMap { $0.expandedIntoLesserSymbol }
282 | }
283 |
284 | guard !expandedGreaterLeftSymbols.isEmpty else {
285 | throw RomanNumeralArithmeticError.subtractionWhereRightValueIsGreaterThanLeftValue
286 | }
287 |
288 | let greaterLeftSymbolRange = greaterLeftSymbolEnumeration.offset ... greaterLeftSymbolEnumeration.offset
289 |
290 | var newLeftSymbols = left
291 | newLeftSymbols.replaceSubrange(greaterLeftSymbolRange, with: expandedGreaterLeftSymbols)
292 |
293 | return newLeftSymbols
294 | }
295 | }
296 |
297 | // MARK: - AdditiveRomanNumeralConvertible Extension
298 |
299 | extension AdditiveRomanNumeral: AdditiveRomanNumeralConvertible {
300 | // MARK: Public Instance Interface
301 |
302 | public var additiveRomanNumeral: AdditiveRomanNumeral? {
303 | self
304 | }
305 | }
306 |
307 | // MARK: - Numeric Extension
308 |
309 | extension AdditiveRomanNumeral {
310 | // MARK: Public Typealiases
311 |
312 | public typealias Magnitude = UInt16
313 |
314 | // MARK: Public Initialization
315 |
316 | public init?(exactly source: T) where T: BinaryInteger {
317 | try? self.init(from: Int(source))
318 | }
319 |
320 | // MARK: Public Static Interface
321 |
322 | public static func * (lhs: AdditiveRomanNumeral, rhs: AdditiveRomanNumeral) -> AdditiveRomanNumeral {
323 | let result: AdditiveRomanNumeral
324 | do {
325 | result = try multiply(lhs: lhs, rhs: rhs)
326 | } catch {
327 | switch error {
328 | case RomanNumeralError.valueGreaterThanMaximum:
329 | result = AdditiveRomanNumeral.maximum
330 | default:
331 | result = AdditiveRomanNumeral.minimum
332 | }
333 | }
334 |
335 | return result
336 | }
337 |
338 | public static func *= (lhs: inout AdditiveRomanNumeral, rhs: AdditiveRomanNumeral) {
339 | lhs = lhs * rhs
340 | }
341 |
342 | // MARK: Public Instance Interface
343 |
344 | public var magnitude: UInt16 {
345 | UInt16(tallyMarkGroup.tallyMarks.count)
346 | }
347 |
348 | // MARK: Internal Static Interface
349 |
350 | /**
351 | - SeeAlso: http://turner.faculty.swau.edu/mathematics/materialslibrary/roman/
352 | */
353 | internal static func multiply(lhs: AdditiveRomanNumeral, rhs: AdditiveRomanNumeral) throws -> AdditiveRomanNumeral {
354 | var unsortedSymbolsFromProduct: [RomanNumeralSymbol] = []
355 | for lhsSymbol in lhs.symbols {
356 | for rhsSymbol in rhs.symbols {
357 | unsortedSymbolsFromProduct.append(contentsOf: lhsSymbol * rhsSymbol)
358 | }
359 | }
360 |
361 | let sortedSymbolsFromProduct = unsortedSymbolsFromProduct.sorted { $0 > $1 }
362 |
363 | return try AdditiveRomanNumeral(symbols: sortedSymbolsFromProduct)
364 | }
365 | }
366 |
367 | // MARK: - RomanNumeralProtocol Extension
368 |
369 | extension AdditiveRomanNumeral: RomanNumeralProtocol {
370 | // MARK: Public Initialization
371 |
372 | public init(symbols: [RomanNumeralSymbol]) throws {
373 | guard symbols.isSorted(by: >=) else {
374 | throw RomanNumeralError.symbolsOutOfOrder
375 | }
376 |
377 | let condensedSymbols = AdditiveRomanNumeral.condense(symbols: symbols)
378 | let potentialRomanNumeral = AdditiveRomanNumeral(unsafeSymbols: condensedSymbols)
379 |
380 | guard AdditiveRomanNumeral.minimum <= potentialRomanNumeral else {
381 | throw RomanNumeralError.valueLessThanMinimum
382 | }
383 |
384 | guard potentialRomanNumeral <= AdditiveRomanNumeral.maximum else {
385 | throw RomanNumeralError.valueGreaterThanMaximum
386 | }
387 |
388 | self = potentialRomanNumeral
389 | }
390 |
391 | // MARK: Public Static Interface
392 |
393 | public static func condense(symbols: [RomanNumeralSymbol]) -> [RomanNumeralSymbol] {
394 | var condenser = AdditiveRomanNumeralSymbolCondenser()
395 | condenser.combine(symbols: symbols)
396 |
397 | return condenser.finalize()
398 | }
399 |
400 | public static func int(from symbols: [RomanNumeralSymbol]) -> Int {
401 | symbols.reduce(0) { $0 + $1.rawValue.tallyMarks.count }
402 | }
403 |
404 | public static func symbols(from intValue: Int) -> [RomanNumeralSymbol] {
405 | var remainingIntValue = intValue
406 | var convertedSymbols: [RomanNumeralSymbol] = []
407 |
408 | RomanNumeralSymbol.allSymbolsDescending.forEach { symbol in
409 | let symbolCount: Int = remainingIntValue / symbol.rawValue.tallyMarks.count
410 | guard symbolCount > 0 else {
411 | return
412 | }
413 |
414 | let consecutiveSymbols = Array(repeating: symbol, count: symbolCount)
415 | convertedSymbols.append(contentsOf: consecutiveSymbols)
416 | remainingIntValue -= symbolCount * symbol.rawValue.tallyMarks.count
417 | }
418 |
419 | return convertedSymbols
420 | }
421 |
422 | // MARK: Public Instance Interface
423 |
424 | public var symbols: [RomanNumeralSymbol] {
425 | additiveRomanNumeralSymbols
426 | }
427 |
428 | public var tallyMarkGroup: RomanNumeralTallyMarkGroup {
429 | symbols.reduce(.nulla) { $0 + $1.rawValue }
430 | }
431 | }
432 |
433 | // MARK: - RomanNumeralSymbolsConvertible Extension
434 |
435 | extension AdditiveRomanNumeral {
436 | // MARK: Public Instance Interface
437 |
438 | public var romanNumeralSymbols: [RomanNumeralSymbol] {
439 | let subtractiveSymbols = AdditiveRomanNumeral.convert(
440 | toValueEquivalentSubtractiveSymbols: additiveRomanNumeralSymbols
441 | )
442 |
443 | return RomanNumeral.convert(toSymbolEquivalentSymbols: subtractiveSymbols)
444 | }
445 | }
446 |
--------------------------------------------------------------------------------
/Sources/RomanNumeralKit/Numeric System/Subtractive Notation/RomanNumeral.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RomanNumeral.swift
3 | // RomanNumeralKit
4 | //
5 | // Copyright © 2019 Kyle Hughes.
6 | //
7 | // Permission is hereby granted, free of charge, to any person obtaining a copy
8 | // of this software and associated documentation files (the "Software"), to deal
9 | // in the Software without restriction, including without limitation the rights
10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | // copies of the Software, and to permit persons to whom the Software is
12 | // furnished to do so, subject to the following conditions:
13 | //
14 | // The above copyright notice and this permission notice shall be included in
15 | // all copies or substantial portions of the Software.
16 | //
17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23 | // THE SOFTWARE.
24 | //
25 |
26 | /**
27 | A Roman numeral that represents its value using traditional symbols arranged in subtractive notation. The Roman numeral
28 | is condensed to its shortest representation using the fewest number of symbols.
29 |
30 | The value of a Roman numeral is generally accumalated by adding the symbols from left-to-right, where the symbols are
31 | arranged in order from greatest to least. Subtractive notation introduces an abbreviation for some groups of symbols
32 | in order to shorten the length of the overall Roman numeral. It is represented by a lesser symbol preceding a greater
33 | symbol in the writing of the Roman numeral and can typically be read as subtracting the lesser symbol from the greater
34 | symbol.
35 |
36 | For example, in subtractive notation *IIII* (4) becomes *IV`* (1 less than 5). Similarly, in subtractive
37 | notation *LXXXX* (90) becomes *XC* (10 less than 100).
38 |
39 | Only specific combinations of symbols are recognized as being valid subtractive syntax. They are as follows:
40 |
41 | - *IV* = 4
42 | - *IX* = 9
43 | - *XL* = 40
44 | - *XC* = 90
45 | - *CD* = 400
46 | - *CM* = 900
47 |
48 | The subtractive grouping of two symbols, when viewed as a singular value, still follows the rule that symbols are
49 | ordered from greatest to least.
50 |
51 | For example, *XC* must succeed all symbols (subtractive or singular) whose value is greater than 90 and must precede
52 | all symbols (subtractive or singular) whose value is less than 90. So *MDCXCIV* (1,694) is a valid Roman numeral but
53 | *MDXCCIV* is not.
54 |
55 | Conceptually, the Roman numeral is an abbreviation for the number of tally marks that are equal to the integer
56 | representation of the Roman numeral.
57 |
58 | See `RomanNumeralSymbolProtocol` and `RomanNumeralSymbol` for the definitions of the traditional symbols. Internally,
59 | `RomanNumeral` represents its value using `SubtractiveRomanNumeralSymbol`s to explicitly define the subtractive cases
60 | and make value conversions easier.
61 |
62 | - SeeAlso: https://en.wikipedia.org/wiki/Roman_numerals#Description
63 | */
64 | public struct RomanNumeral: SubtractiveRomanNumeralSymbolsConvertible {
65 | // MARK: Public Static Properties
66 |
67 | public static let maximum = RomanNumeral(unsafeSymbols: [.M, .M, .M, .CM, .XC, .IX])
68 | public static let minimum = RomanNumeral(unsafeSymbols: [.I])
69 |
70 | // MARK: Public Instance Properties
71 |
72 | public private(set) var subtractiveRomanNumeralSymbols: [SubtractiveRomanNumeralSymbol]
73 |
74 | // MARK: Public Initialization
75 |
76 | /**
77 | Creates a new Roman numeral represented by the value of the given subtractive symbols.
78 |
79 | The given symbols must be in the appropriate order for subtractive notation.
80 |
81 | The given symbols will be condensed to the most succint representation using the fewest number of symbols. The
82 | symbols that are ultimately produced by the Roman numeral may not be the same symbols as the ones passed in, but
83 | the value will be equivalent.
84 |
85 | The value of the resulting Roman numeral must be within the `minimum` and `maximum` values for subtractive
86 | notation.
87 |
88 | This differs from the protocol-defined `init(symbols:)` by using the special `SubtractiveRomanNumeralSymbol` type.
89 | Both are valid ways to construct subtractive Roman numerals.
90 |
91 | - Parameter subtractiveSymbols: The subtractive symbols to construct the Roman numeral from.
92 | */
93 | public init(subtractiveSymbols: [SubtractiveRomanNumeralSymbol]) throws {
94 | guard subtractiveSymbols.isSorted(by: >=) else {
95 | throw RomanNumeralError.symbolsOutOfOrder
96 | }
97 |
98 | let condensedSubtractiveSymbols = RomanNumeral.condense(subtractiveSymbols: subtractiveSymbols)
99 | let potentialRomanNumeral = RomanNumeral(unsafeSymbols: condensedSubtractiveSymbols)
100 |
101 | guard RomanNumeral.minimum <= potentialRomanNumeral else {
102 | throw RomanNumeralError.valueLessThanMinimum
103 | }
104 |
105 | guard potentialRomanNumeral <= RomanNumeral.maximum else {
106 | throw RomanNumeralError.valueGreaterThanMaximum
107 | }
108 |
109 | self = potentialRomanNumeral
110 | }
111 |
112 | // MARK: Internal Initialization
113 |
114 | internal init(unsafeSymbols: [SubtractiveRomanNumeralSymbol]) {
115 | subtractiveRomanNumeralSymbols = unsafeSymbols
116 | }
117 |
118 | // MARK: Public Static Interface
119 |
120 | /**
121 | Condense the given subtractive symbols to their most succint representation using the fewest number of symbols.
122 |
123 | For example *VV* will be condensed to *X*.
124 |
125 | This differs from the protocol-defined `condense(symbols:)` by using the special `SubtractiveRomanNumeralSymbol`
126 | type. Both are valid ways to condense Roman numeral symbols.
127 |
128 | - Parameter subtractiveSymbols: The given subtractive symbols to condense.
129 | - Returns: The condensed form of the given subtractive symbols.
130 | */
131 | public static func condense(subtractiveSymbols: [SubtractiveRomanNumeralSymbol]) -> [SubtractiveRomanNumeralSymbol] {
132 | let symbols = RomanNumeral.convert(toValueEquivalentSymbols: subtractiveSymbols)
133 | let condensedSymbols = AdditiveRomanNumeral.condense(symbols: symbols)
134 |
135 | return AdditiveRomanNumeral.convert(toValueEquivalentSubtractiveSymbols: condensedSymbols)
136 | }
137 |
138 | /**
139 | Convert the given subtractive symbols into their cumulative integer value.
140 |
141 | The order of the symbols should follow subtractive notation rules.
142 |
143 | This differs from the protocol-defined `int(from:)` by using the special `SubtractiveRomanNumeralSymbol` type.
144 | Both are valid ways to convert symbols into integers.
145 |
146 | - Parameter symbols: The subtractive symbols that should be represented as a cumulative integer value.
147 | - Returns: The cumulative integer value of the given subtractive symbols.
148 | */
149 | public static func int(from subtractiveSymbols: [SubtractiveRomanNumeralSymbol]) -> Int {
150 | subtractiveSymbols.reduce(0) { $0 + $1.rawValue.tallyMarks.count }
151 | }
152 |
153 | /**
154 | Convert the given integer into the corresponding subtractive symbols.
155 |
156 | The returned symbols may not comprise a valid Roman numeral if the given integer value is not within the bounds
157 | defined by `minimum` and `maximum`.
158 |
159 | This differs from the protocol-defined `symbols(from:)` by using the special `SubtractiveRomanNumeralSymbol` type.
160 | Both are valid ways to convert integers into symbols.
161 |
162 | - Parameter intValue: The integer to convert into a collection of subtractive symbols.
163 | - Returns: The symbolic representation of the given integer.
164 | */
165 | public static func subtractiveSymbols(from intValue: Int) -> [SubtractiveRomanNumeralSymbol] {
166 | var remainingIntValue = intValue
167 | var convertedSubtractiveSymbols: [SubtractiveRomanNumeralSymbol] = []
168 |
169 | SubtractiveRomanNumeralSymbol.allSymbolsDescending.forEach { symbol in
170 | let symbolCount: Int = remainingIntValue / symbol.rawValue.tallyMarks.count
171 |
172 | guard symbolCount > 0 else {
173 | return
174 | }
175 |
176 | let consecutiveSymbols = Array(repeating: symbol, count: symbolCount)
177 | convertedSubtractiveSymbols.append(contentsOf: consecutiveSymbols)
178 | remainingIntValue -= symbolCount * symbol.rawValue.tallyMarks.count
179 | }
180 |
181 | return convertedSubtractiveSymbols
182 | }
183 |
184 | // MARK: Internal Static Interface
185 |
186 | /**
187 | Convert the given subtractive symbols into their `RomanNumeralSymbol` equivalents.
188 |
189 | The symbols will be converted directly and will come out in subtractive notation using the alternate symbol type.
190 |
191 | - Parameter subtractiveSymbols: The subtractive symbols to convert.
192 | - Returns: The `RomanNumeralSymbol` equivalent of the subtractive symbols.
193 | */
194 | internal static func convert(
195 | toSymbolEquivalentSymbols subtractiveSymbols: [SubtractiveRomanNumeralSymbol]
196 | ) -> [RomanNumeralSymbol] {
197 | subtractiveSymbols.flatMap { $0.romanNumeralSymbols }
198 | }
199 |
200 | /**
201 | Convert the given subtractive symbols into their `RomanNumeralSymbol` equivalent using additive notation.
202 |
203 | The symbols will come out in additive notation with the same value as their given subtractive counterparts.
204 |
205 | - Parameter subtractiveSymbols: The subtractive symbols to convert.
206 | - Returns: The `RomanNumeralSymbol` equivalent of the subtractive symbols in additive notation.
207 | */
208 | internal static func convert(
209 | toValueEquivalentSymbols subtractiveSymbols: [SubtractiveRomanNumeralSymbol]
210 | ) -> [RomanNumeralSymbol] {
211 | subtractiveSymbols.flatMap { $0.additiveRomanNumeralSymbols }
212 | }
213 | }
214 |
215 | // MARK: - AdditiveArithmetic Extension
216 |
217 | extension RomanNumeral {
218 | // MARK: Public Static Interface
219 |
220 | public static func + (
221 | lhs: RomanNumeral,
222 | rhs: RomanNumeral
223 | ) -> RomanNumeral {
224 | let result: RomanNumeral
225 | do {
226 | result = try add(lhs: lhs, rhs: rhs)
227 | } catch {
228 | switch error {
229 | case RomanNumeralError.valueGreaterThanMaximum:
230 | result = RomanNumeral.maximum
231 | default:
232 | result = RomanNumeral.minimum
233 | }
234 | }
235 |
236 | return result
237 | }
238 |
239 | public static func += (lhs: inout RomanNumeral, rhs: RomanNumeral) {
240 | lhs = lhs + rhs
241 | }
242 |
243 | public static func - (
244 | lhs: RomanNumeral,
245 | rhs: RomanNumeral
246 | ) -> RomanNumeral {
247 | let result: RomanNumeral
248 | do {
249 | result = try subtract(lhs: lhs, rhs: rhs)
250 | } catch {
251 | switch error {
252 | case RomanNumeralError.valueGreaterThanMaximum:
253 | result = RomanNumeral.maximum
254 | default:
255 | result = RomanNumeral.minimum
256 | }
257 | }
258 |
259 | return result
260 | }
261 |
262 | public static func -= (lhs: inout RomanNumeral, rhs: RomanNumeral) {
263 | lhs = lhs - rhs
264 | }
265 |
266 | // MARK: Internal Static Interface
267 |
268 | /**
269 | - SeeAlso: http://turner.faculty.swau.edu/mathematics/materialslibrary/roman/
270 | */
271 | internal static func add(
272 | lhs: RomanNumeral,
273 | rhs: RomanNumeral
274 | ) throws -> RomanNumeral {
275 | guard
276 | let lhsAdditiveRomanNumeral = lhs.additiveRomanNumeral,
277 | let rhsAdditiveRomanNumeral = rhs.additiveRomanNumeral
278 | else {
279 | throw RomanNumeralArithmeticError.ambiguousAdditionError
280 | }
281 |
282 | guard
283 | let result = try AdditiveRomanNumeral.add(
284 | lhs: lhsAdditiveRomanNumeral,
285 | rhs: rhsAdditiveRomanNumeral
286 | )
287 | .romanNumeral
288 | else {
289 | throw RomanNumeralArithmeticError.ambiguousAdditionError
290 | }
291 |
292 | return result
293 | }
294 |
295 | /**
296 | - SeeAlso: http://turner.faculty.swau.edu/mathematics/materialslibrary/roman/
297 | */
298 | internal static func subtract(
299 | lhs: RomanNumeral,
300 | rhs: RomanNumeral
301 | ) throws -> RomanNumeral {
302 | guard
303 | let leftAdditiveRomanNumeral = lhs.additiveRomanNumeral,
304 | let rightAdditiveRomanNumeral = rhs.additiveRomanNumeral
305 | else {
306 | throw RomanNumeralArithmeticError.ambiguousSubtractionError
307 | }
308 |
309 | guard
310 | let result = try AdditiveRomanNumeral.subtract(
311 | lhs: leftAdditiveRomanNumeral,
312 | rhs: rightAdditiveRomanNumeral
313 | )
314 | .romanNumeral
315 | else {
316 | throw RomanNumeralArithmeticError.ambiguousSubtractionError
317 | }
318 |
319 | return result
320 | }
321 | }
322 |
323 | // MARK: - AdditiveRomanNumeralSymbolsConvertible Extension
324 |
325 | extension RomanNumeral: AdditiveRomanNumeralSymbolsConvertible {
326 | // MARK: - Public Instance Interface
327 |
328 | public var additiveRomanNumeralSymbols: [RomanNumeralSymbol] {
329 | RomanNumeral.convert(toValueEquivalentSymbols: subtractiveRomanNumeralSymbols)
330 | }
331 | }
332 |
333 | // MARK: - Numeric Extension
334 |
335 | extension RomanNumeral {
336 | // MARK: Public Typealiases
337 |
338 | public typealias Magnitude = UInt16
339 |
340 | // MARK: Public Initialization
341 |
342 | public init?(exactly source: T) where T: BinaryInteger {
343 | try? self.init(from: Int(source))
344 | }
345 |
346 | // MARK: Public Static Interface
347 |
348 | public static func * (lhs: RomanNumeral, rhs: RomanNumeral) -> RomanNumeral {
349 | let result: RomanNumeral
350 | do {
351 | result = try multiply(lhs: lhs, rhs: rhs)
352 | } catch {
353 | switch error {
354 | case RomanNumeralError.valueGreaterThanMaximum:
355 | result = RomanNumeral.maximum
356 | default:
357 | result = RomanNumeral.minimum
358 | }
359 | }
360 |
361 | return result
362 | }
363 |
364 | public static func *= (lhs: inout RomanNumeral, rhs: RomanNumeral) {
365 | lhs = lhs * rhs
366 | }
367 |
368 | // MARK: Public Instance Interface
369 |
370 | public var magnitude: UInt16 {
371 | UInt16(tallyMarkGroup.tallyMarks.count)
372 | }
373 |
374 | // MARK: Internal Static Interface
375 |
376 | /**
377 | - SeeAlso: http://turner.faculty.swau.edu/mathematics/materialslibrary/roman/
378 | */
379 | internal static func multiply(
380 | lhs: RomanNumeral,
381 | rhs: RomanNumeral
382 | ) throws -> RomanNumeral {
383 | guard
384 | let lhsAdditiveRomanNumeral = lhs.additiveRomanNumeral,
385 | let rhsAdditiveRomanNumeral = rhs.additiveRomanNumeral
386 | else {
387 | throw RomanNumeralArithmeticError.ambiguousSubtractionError
388 | }
389 |
390 | guard
391 | let result = try AdditiveRomanNumeral.multiply(
392 | lhs: lhsAdditiveRomanNumeral,
393 | rhs: rhsAdditiveRomanNumeral
394 | )
395 | .romanNumeral
396 | else {
397 | throw RomanNumeralArithmeticError.ambiguousSubtractionError
398 | }
399 |
400 | return result
401 | }
402 | }
403 |
404 | // MARK: - RomanNumeralProtocol Extension
405 |
406 | extension RomanNumeral: RomanNumeralProtocol {
407 | // MARK: Public Initialization
408 |
409 | public init(symbols: [RomanNumeralSymbol]) throws {
410 | let convertedSubtractiveSymbols = AdditiveRomanNumeral.convert(toSymbolEquivalentSubtractiveSymbols: symbols)
411 | try self.init(subtractiveSymbols: convertedSubtractiveSymbols)
412 | }
413 |
414 | // MARK: Public Static Interface
415 |
416 | public static func condense(symbols: [RomanNumeralSymbol]) -> [RomanNumeralSymbol] {
417 | let convertedSubtractiveSymbols = AdditiveRomanNumeral.convert(toSymbolEquivalentSubtractiveSymbols: symbols)
418 | let condensedSubtractiveSymbols = condense(subtractiveSymbols: convertedSubtractiveSymbols)
419 |
420 | return RomanNumeral.convert(toSymbolEquivalentSymbols: condensedSubtractiveSymbols)
421 | }
422 |
423 | public static func int(from symbols: [RomanNumeralSymbol]) -> Int {
424 | let convertedSubtractiveSymbols = AdditiveRomanNumeral.convert(toSymbolEquivalentSubtractiveSymbols: symbols)
425 |
426 | return int(from: convertedSubtractiveSymbols)
427 | }
428 |
429 | public static func symbols(from intValue: Int) -> [RomanNumeralSymbol] {
430 | subtractiveSymbols(from: intValue).flatMap { $0.romanNumeralSymbols }
431 | }
432 |
433 | // MARK: Public Instance Interface
434 |
435 | public var symbols: [RomanNumeralSymbol] {
436 | RomanNumeral.convert(toSymbolEquivalentSymbols: subtractiveRomanNumeralSymbols)
437 | }
438 |
439 | public var tallyMarkGroup: RomanNumeralTallyMarkGroup {
440 | subtractiveRomanNumeralSymbols.reduce(.nulla) { $0 + $1.rawValue }
441 | }
442 | }
443 |
444 | // MARK: - RomanNumeralSymbolsConvertible Extension
445 |
446 | extension RomanNumeral {
447 | // MARK: Public Instance Interface
448 |
449 | public var romanNumeralSymbols: [RomanNumeralSymbol] {
450 | RomanNumeral.convert(toSymbolEquivalentSymbols: subtractiveRomanNumeralSymbols)
451 | }
452 | }
453 |
454 | // MARK: - RomanNumeralConvertible Extension
455 |
456 | extension RomanNumeral: RomanNumeralConvertible {
457 | // MARK: - Public Instance Interface
458 |
459 | public var romanNumeral: RomanNumeral? {
460 | self
461 | }
462 | }
463 |
--------------------------------------------------------------------------------