Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement Comparable.clamped(to:) and Comparable.clamp(to:) #32

Merged
merged 5 commits into from
May 8, 2019
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).

## [Unreleased]
### Addedge
- None.
### Added
- New `Comparable.clamped(to:)` and `Comparable.clamp(to:)` interfaces for any `Comparable`, e. g. `Int`.
### Changed
- None.
### Deprecated
Expand All @@ -18,7 +18,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a
- None.

## [3.0.0] - 2019-04-30
### Addedge
### Added
- New `Withable` protocol to init/copy objects and set properties in a convenient way on a single line.
### Changed
- Upgraded to Swift 5 & Xcode 10.2.
Expand Down
79 changes: 79 additions & 0 deletions Frameworks/HandySwift/Extensions/ComparableExtension.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
//
// Created by Frederick Pietschmann on 07.05.19.
// Copyright © 2019 Flinesoft. All rights reserved.
//

import Foundation

extension Comparable {
// MARK: - Clamp: Returning Variants
/// Returns `self` clamped to the given limits.
///
/// - Parameter limits: The closed range determining minimum & maxmimum value.
/// - Returns:
/// - `self`, if it is inside the given limits.
/// - `lowerBound` of the given limits, if `self` is smaller than it.
/// - `upperBound` of the given limits, if `self` is greater than it.
public func clamped(to limits: ClosedRange<Self>) -> Self {
return limits.lowerBound > self ? limits.lowerBound
: limits.upperBound < self ? limits.upperBound
: self
// swiftlint:disable:last too_much_unindentation
}

/// Returns `self` clamped to the given limits.
///
/// - Parameter limits: The partial range (from) determining the minimum value.
/// - Returns:
/// - `self`, if it is inside the given limits.
/// - `lowerBound` of the given limits, if `self` is smaller than it.
public func clamped(to limits: PartialRangeFrom<Self>) -> Self {
return limits.lowerBound > self ? limits.lowerBound
: self
// swiftlint:disable:last too_much_unindentation
}

/// Returns `self` clamped to the given limits.
///
/// - Parameter limits: The partial range (through) determining the maximum value.
/// - Returns:
/// - `self`, if it is inside the given limits.
/// - `upperBound` of the given limits, if `self` is greater than it.
public func clamped(to limits: PartialRangeThrough<Self>) -> Self {
return limits.upperBound < self ? limits.upperBound
: self
// swiftlint:disable:last too_much_unindentation
}

// MARK: Mutating Variants
/// Clamps `self` to the given limits.
///
/// - `self`, if it is inside the given limits.
/// - `lowerBound` of the given limits, if `self` is smaller than it.
/// - `upperBound` of the given limits, if `self` is greater than it.
///
/// - Parameter limits: The closed range determining minimum & maxmimum value.
public mutating func clamp(to limits: ClosedRange<Self>) {
self = clamped(to: limits)
}

/// Clamps `self` to the given limits.
///
/// - `self`, if it is inside the given limits.
/// - `lowerBound` of the given limits, if `self` is smaller than it.
///
/// - Parameter limits: The partial range (from) determining the minimum value.
public mutating func clamp(to limits: PartialRangeFrom<Self>) {
self = clamped(to: limits)
}

/// Clamps `self` to the given limits.
///
/// - `self`, if it is inside the given limits.
/// - `upperBound` of the given limits, if `self` is greater than it.
///
/// - Parameter limits: The partial range (through) determining the maximum value.
public mutating func clamp(to limits: PartialRangeThrough<Self>) {
self = clamped(to: limits)
}
}
16 changes: 16 additions & 0 deletions HandySwift.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,12 @@
CC4AE0CF2087D8A7009931F6 /* RegexTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC4AE0CD2087D895009931F6 /* RegexTests.swift */; };
CC4AE0D02087D8A8009931F6 /* RegexTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC4AE0CD2087D895009931F6 /* RegexTests.swift */; };
CC4AE0D12087D8A9009931F6 /* RegexTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC4AE0CD2087D895009931F6 /* RegexTests.swift */; };
CC66E047228199A0007ABF61 /* ComparableExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC66E046228199A0007ABF61 /* ComparableExtension.swift */; };
CC66E048228199DE007ABF61 /* ComparableExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC66E046228199A0007ABF61 /* ComparableExtension.swift */; };
CC66E049228199DF007ABF61 /* ComparableExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC66E046228199A0007ABF61 /* ComparableExtension.swift */; };
CC66E12522819FFF007ABF61 /* ComparableExtensionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC66E12322819FCF007ABF61 /* ComparableExtensionTests.swift */; };
CC66E1262281A000007ABF61 /* ComparableExtensionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC66E12322819FCF007ABF61 /* ComparableExtensionTests.swift */; };
CC66E1272281A000007ABF61 /* ComparableExtensionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC66E12322819FCF007ABF61 /* ComparableExtensionTests.swift */; };
/* End PBXBuildFile section */

/* Begin PBXContainerItemProxy section */
Expand Down Expand Up @@ -170,6 +176,8 @@
C5CFB6AB20B0A70300830511 /* Unowned.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Unowned.swift; sourceTree = "<group>"; };
CC120CC1205FDB9300C37D7C /* Regex.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Regex.swift; sourceTree = "<group>"; };
CC4AE0CD2087D895009931F6 /* RegexTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RegexTests.swift; sourceTree = "<group>"; };
CC66E046228199A0007ABF61 /* ComparableExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComparableExtension.swift; sourceTree = "<group>"; };
CC66E12322819FCF007ABF61 /* ComparableExtensionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComparableExtensionTests.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
Expand Down Expand Up @@ -271,6 +279,7 @@
children = (
82CAE2951C2EE64900F934A7 /* ArrayExtension.swift */,
3F95C8D120F22A3C0045AFD0 /* CollectionExtension.swift */,
CC66E046228199A0007ABF61 /* ComparableExtension.swift */,
8280D7DB1C4A6EC9001172EF /* DictionaryExtension.swift */,
A1F221631E3CC05100419B06 /* DispatchTimeIntervalExtension.swift */,
823B2B4C1C24ABA4007B3CDD /* IntExtension.swift */,
Expand All @@ -286,6 +295,7 @@
children = (
82CAE2971C2EE95200F934A7 /* ArrayExtensionTests.swift */,
3F95C8D320F22DC60045AFD0 /* CollectionExtensionTests.swift */,
CC66E12322819FCF007ABF61 /* ComparableExtensionTests.swift */,
8280D7DF1C4A6FF3001172EF /* DictionaryExtensionTests.swift */,
827599631E520FB800787F99 /* DispatchTimeIntervalExtensionTests.swift */,
823B2B4F1C24AC00007B3CDD /* IntExtensionTests.swift */,
Expand Down Expand Up @@ -687,6 +697,7 @@
8218E4D62211D193007AAAF3 /* NSRangeExtension.swift in Sources */,
3F95C8D220F22A3C0045AFD0 /* CollectionExtension.swift in Sources */,
823B2B4D1C24ABA4007B3CDD /* IntExtension.swift in Sources */,
CC66E047228199A0007ABF61 /* ComparableExtension.swift in Sources */,
C5C89B9420B0A0C10048B07C /* Weak.swift in Sources */,
8251AA2022786D460022B277 /* Withable.swift in Sources */,
82CAE2921C2ED1A200F934A7 /* StringExtension.swift in Sources */,
Expand All @@ -713,6 +724,7 @@
82812A9F1D06926800CD5B6C /* GlobalsTests.swift in Sources */,
8218E4DA2211D270007AAAF3 /* NSRangeExtensionTests.swift in Sources */,
8280D7E01C4A6FF3001172EF /* DictionaryExtensionTests.swift in Sources */,
CC66E12522819FFF007ABF61 /* ComparableExtensionTests.swift in Sources */,
3F95C8D520F22DEE0045AFD0 /* CollectionExtensionTests.swift in Sources */,
CC4AE0CF2087D8A7009931F6 /* RegexTests.swift in Sources */,
);
Expand All @@ -729,6 +741,7 @@
8218E4D72211D193007AAAF3 /* NSRangeExtension.swift in Sources */,
825EFE051C33358400558497 /* StringExtension.swift in Sources */,
82E21E8E211AF9960061EB1B /* CollectionExtension.swift in Sources */,
CC66E048228199DE007ABF61 /* ComparableExtension.swift in Sources */,
C5CFB6AF20B0A78F00830511 /* Weak.swift in Sources */,
8251AA2122786D460022B277 /* Withable.swift in Sources */,
A1F221651E3CC05100419B06 /* DispatchTimeIntervalExtension.swift in Sources */,
Expand All @@ -755,6 +768,7 @@
82812AA01D06926800CD5B6C /* GlobalsTests.swift in Sources */,
8218E4DB2211D270007AAAF3 /* NSRangeExtensionTests.swift in Sources */,
8280D7E11C4A6FF3001172EF /* DictionaryExtensionTests.swift in Sources */,
CC66E1262281A000007ABF61 /* ComparableExtensionTests.swift in Sources */,
3F95C8D620F22DEF0045AFD0 /* CollectionExtensionTests.swift in Sources */,
CC4AE0D02087D8A8009931F6 /* RegexTests.swift in Sources */,
);
Expand All @@ -771,6 +785,7 @@
8218E4D82211D193007AAAF3 /* NSRangeExtension.swift in Sources */,
825EFE0B1C33358500558497 /* StringExtension.swift in Sources */,
82E21E8F211AF9970061EB1B /* CollectionExtension.swift in Sources */,
CC66E049228199DF007ABF61 /* ComparableExtension.swift in Sources */,
C5CFB6B020B0A79000830511 /* Weak.swift in Sources */,
8251AA2222786D460022B277 /* Withable.swift in Sources */,
A1F221661E3CC05100419B06 /* DispatchTimeIntervalExtension.swift in Sources */,
Expand All @@ -797,6 +812,7 @@
82812AA11D06926800CD5B6C /* GlobalsTests.swift in Sources */,
8218E4DC2211D270007AAAF3 /* NSRangeExtensionTests.swift in Sources */,
8280D7E21C4A6FF3001172EF /* DictionaryExtensionTests.swift in Sources */,
CC66E1272281A000007ABF61 /* ComparableExtensionTests.swift in Sources */,
3F95C8D720F22DEF0045AFD0 /* CollectionExtensionTests.swift in Sources */,
CC4AE0D12087D8A9009931F6 /* RegexTests.swift in Sources */,
);
Expand Down
33 changes: 33 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,39 @@ let intArray = 5.timesMake { Int(randomBelow: 1_000)! }
// => [481, 16, 680, 87, 912]
```

### ComparableExtension

#### clamped(to:)

Apply a limiting range as the bounds of a `Comparable`.
Supports `ClosedRange` (`a ... b`), `PartialRangeFrom` (`a...`) and `PartialRangeThrough` (`...b`) as the `limits`.

``` Swift
let myNum = 3
myNum.clamped(to: 0 ... 6) // => 3
myNum.clamped(to: 0 ... 2) // => 2
myNum.clamped(to: 4 ... 6) // => 4
myNum.clamped(to: 5...) // => 4
myNum.clamped(to: ...2) // => 2

let myString = "d"
myString.clamped(to: "a" ... "g") // => "d"
myString.clamped(to: "a" ... "c") // => "c"
myString.clamped(to: "e" ... "g") // => "e"
myString.clamped(to: "f"...) // => "f"
myString.clamped(to: ..."c") // => "c"
```

#### clamp(to:)

In-place `mutating` variant of `clamped(to:)`

``` Swift
var myNum = 3
myNum.clamp(to: 0...2)
myNum // => 2
```

### StringExtension

#### .stripped()
Expand Down
119 changes: 119 additions & 0 deletions Tests/HandySwiftTests/Extensions/ComparableExtensionTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
//
// Created by Frederick Pietschmann on 07.05.19.
// Copyright © 2019 Flinesoft. All rights reserved.
//

import Foundation

@testable import HandySwift
import XCTest

class ComparableExtensionTests: XCTestCase {
// MARK: Returning Variants
func testClampedClosedRange() {
let myNum = 3
XCTAssertEqual(myNum.clamped(to: 0 ... 4), 3)
XCTAssertEqual(myNum.clamped(to: 0 ... 2), 2)
XCTAssertEqual(myNum.clamped(to: 4 ... 6), 4)

let myString = "d"
XCTAssertEqual(myString.clamped(to: "a" ... "e"), "d")
XCTAssertEqual(myString.clamped(to: "a" ... "c"), "c")
XCTAssertEqual(myString.clamped(to: "e" ... "g"), "e")
}

func testClampedPartialRangeFrom() {
let myNum = 3
XCTAssertEqual(myNum.clamped(to: 2...), 3)
XCTAssertEqual(myNum.clamped(to: 4...), 4)

let myString = "d"
XCTAssertEqual(myString.clamped(to: "c"...), "d")
XCTAssertEqual(myString.clamped(to: "e"...), "e")
}

func testClampedPartialRangeThrough() {
let myNum = 3
XCTAssertEqual(myNum.clamped(to: ...4), 3) // swiftlint:disable:this tuple_index
XCTAssertEqual(myNum.clamped(to: ...2), 2) // swiftlint:disable:this tuple_index

let myString = "d"
XCTAssertEqual(myString.clamped(to: ..."e"), "d")
XCTAssertEqual(myString.clamped(to: ..."c"), "c")
}

// MARK: Mutating Variants
func testClampClosedRange() {
let myNum = 3

var myNumCopy = myNum
myNumCopy.clamp(to: 0 ... 4)
XCTAssertEqual(myNumCopy, 3)

myNumCopy = myNum
myNumCopy.clamp(to: 0 ... 2)
XCTAssertEqual(myNumCopy, 2)

myNumCopy = myNum
myNumCopy.clamp(to: 4 ... 6)
XCTAssertEqual(myNumCopy, 4)

let myString = "d"

var myStringCopy = myString
myStringCopy.clamp(to: "a" ... "e")
XCTAssertEqual(myStringCopy, "d")

myStringCopy = myString
myStringCopy.clamp(to: "a" ... "c")
XCTAssertEqual(myStringCopy, "c")

myStringCopy = myString
myStringCopy.clamp(to: "e" ... "g")
XCTAssertEqual(myStringCopy, "e")
}

func testClampPartialRangeFrom() {
let myNum = 3

var myNumCopy = myNum
myNumCopy.clamp(to: 2...)
XCTAssertEqual(myNumCopy, 3)

myNumCopy = myNum
myNumCopy.clamp(to: 4...)
XCTAssertEqual(myNumCopy, 4)

let myString = "d"

var myStringCopy = myString
myStringCopy.clamp(to: "c"...)
XCTAssertEqual(myStringCopy, "d")

myStringCopy = myString
myStringCopy.clamp(to: "e"...)
XCTAssertEqual(myStringCopy, "e")
}

func testClampPartialRangeThrough() {
let myNum = 3

var myNumCopy = myNum
myNumCopy.clamp(to: ...4) // swiftlint:disable:this tuple_index
XCTAssertEqual(myNumCopy, 3)

myNumCopy = myNum
myNumCopy.clamp(to: ...2) // swiftlint:disable:this tuple_index
XCTAssertEqual(myNumCopy, 2)

let myString = "d"

var myStringCopy = myString
myStringCopy.clamp(to: ..."e")
XCTAssertEqual(myStringCopy, "d")

myStringCopy = myString
myStringCopy.clamp(to: ..."c")
XCTAssertEqual(myStringCopy, "c")
}
}
26 changes: 26 additions & 0 deletions UsageExamples.playground/Contents.swift
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,32 @@ stringArray

let intArray = 5.timesMake { Int(randomBelow: 1_000)! }

//: ## ComparableExtension
//: ### clamped(to:)
//: Apply a limiting range as the bounds of a `Comparable`.
//: Supports `ClosedRange` (`a ... b`), `PartialRangeFrom` (`a...`) and `PartialRangeThrough` (`...b`) as the `limits`.

let myNum = 3
myNum.clamped(to: 0 ... 6)
myNum.clamped(to: 0 ... 2)
myNum.clamped(to: 4 ... 6)
myNum.clamped(to: 5...)
myNum.clamped(to: ...2)

let myString = "d"
myString.clamped(to: "a" ... "g")
myString.clamped(to: "a" ... "c")
myString.clamped(to: "e" ... "g")
myString.clamped(to: "f"...)
myString.clamped(to: ..."c")

//: ### clamp(to:)
//: In-place `mutating` variant of `clamped(to:)`

var myNum = 3
myNum.clamp(to: 0 ... 2)
myNum

//: ## StringExtension
//: ### string.strip
//: Returns string with whitespace characters stripped from start and end.
Expand Down