Skip to content

Commit

Permalink
Added clamp / clamped for Comparables.
Browse files Browse the repository at this point in the history
  • Loading branch information
wadetregaskis committed Mar 7, 2024
1 parent 844ebbd commit 7cbcc2d
Show file tree
Hide file tree
Showing 3 changed files with 214 additions and 0 deletions.
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ A miscellaneous collection of extensions to Apple's Foundation framework.

* `asHexString(uppercase:delimiterEvery:delimiter:)` formats a `Data` into hex (as a `String`), e.g. `Data(bytes: "woot", count: 4).asHexString(delimiterEvery: 1)` -> `"77 6F 6F 74"`.
* It also has a shorthand version `asHexString` for convenience (omitting the parentheses), if you don't need to customise its defaults.

### Comparable (e.g. numbers, strings, anything you can put into a range, etc).

* `clamp` and `clamped` to conform a value into a given range (supporting all finite range types), e.g. `5.clamped(..<0)` -> `-1`.
* Note: up-to-but-not-including ranges (`..<`) are only supported on types that are _also_ `Strideable`.

### Data

Expand Down
83 changes: 83 additions & 0 deletions Sources/FoundationExtensions/Comparable.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
// Created by Wade Tregaskis on 2024-03-06

extension Comparable {
mutating func clamp(_ range: PartialRangeFrom<Self>) {
if self < range.lowerBound {
self = range.lowerBound
}
}

func clamped(_ range: PartialRangeFrom<Self>) -> Self {
if self < range.lowerBound {
range.lowerBound
} else {
self
}
}

mutating func clamp(_ range: PartialRangeThrough<Self>) {
if self > range.upperBound {
self = range.upperBound
}
}

func clamped(_ range: PartialRangeThrough<Self>) -> Self {
if self > range.upperBound {
range.upperBound
} else {
self
}
}

mutating func clamp(_ range: ClosedRange<Self>) {
if self < range.lowerBound {
self = range.lowerBound
} else if self > range.upperBound {
self = range.upperBound
}
}

func clamped(_ range: ClosedRange<Self>) -> Self {
if self < range.lowerBound {
range.lowerBound
} else if self > range.upperBound {
range.upperBound
} else {
self
}
}
}

extension Comparable where Self: Strideable {
mutating func clamp(_ range: Range<Self>) {
if self < range.lowerBound {
self = range.lowerBound
} else if self >= range.upperBound {
self = range.upperBound.advanced(by: -1)
}
}

func clamped(_ range: Range<Self>) -> Self {
if self < range.lowerBound {
range.lowerBound
} else if self >= range.upperBound {
range.upperBound.advanced(by: -1)
} else {
self
}
}

mutating func clamp(_ range: PartialRangeUpTo<Self>) {
if self >= range.upperBound {
self = range.upperBound.advanced(by: -1)
}
}

func clamped(_ range: PartialRangeUpTo<Self>) -> Self {
if self >= range.upperBound {
range.upperBound.advanced(by: -1)
} else {
self
}
}
}
126 changes: 126 additions & 0 deletions Tests/FoundationExtensionsTests/ComparableTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
// Created by Wade Tregaskis on 2024-03-02.

import XCTest
@testable import FoundationExtensions


final class ComparableTests: XCTestCase {
func testClamp_PartialRangeFrom() throws {
var x = 5

x.clamp(0...)
XCTAssertEqual(x, 5)

x.clamp(5...)
XCTAssertEqual(x, 5)

x.clamp(6...)
XCTAssertEqual(x, 6)

x.clamp(50...)
XCTAssertEqual(x, 50)
}

func testClamped_PartialRangeFrom() throws {
XCTAssertEqual(5.clamped(0...), 5)
XCTAssertEqual(5.clamped(5...), 5)
XCTAssertEqual(5.clamped(6...), 6)
XCTAssertEqual(5.clamped(50...), 50)
}

func testClamp_PartialRangeThrough() throws {
var x = 5

x.clamp(...10)
XCTAssertEqual(x, 5)

x.clamp(...5)
XCTAssertEqual(x, 5)

x.clamp(...4)
XCTAssertEqual(x, 4)

x.clamp(...0)
XCTAssertEqual(x, 0)
}

func testClamped_PartialRangeThrough() throws {
XCTAssertEqual(5.clamped(...10), 5)
XCTAssertEqual(5.clamped(...5), 5)
XCTAssertEqual(5.clamped(...4), 4)
XCTAssertEqual(5.clamped(...0), 0)
}

func testClamp_ClosedRange() throws {
var x = 5

x.clamp(0...10)
XCTAssertEqual(x, 5)

x.clamp(0...5)
XCTAssertEqual(x, 5)

x.clamp(5...10)
XCTAssertEqual(x, 5)

x.clamp(0...4)
XCTAssertEqual(x, 4)

x.clamp(5...10)
XCTAssertEqual(x, 5)
}

func testClamped_ClosedRange() throws {
XCTAssertEqual(5.clamped(0...10), 5)
XCTAssertEqual(5.clamped(0...5), 5)
XCTAssertEqual(5.clamped(5...10), 5)
XCTAssertEqual(5.clamped(0...4), 4)
XCTAssertEqual(5.clamped(6...10), 6)
}

func testClamp_Range() throws {
var x = 5

x.clamp(0..<10)
XCTAssertEqual(x, 5)

x.clamp(0..<6)
XCTAssertEqual(x, 5)

x.clamp(0..<5)
XCTAssertEqual(x, 4)

x.clamp(4..<10)
XCTAssertEqual(x, 4)

x.clamp(5..<10)
XCTAssertEqual(x, 5)
}

func testClamped_Range() throws {
XCTAssertEqual(5.clamped(0..<10), 5)
XCTAssertEqual(5.clamped(0..<6), 5)
XCTAssertEqual(5.clamped(0..<5), 4)
XCTAssertEqual(5.clamped(5..<10), 5)
XCTAssertEqual(5.clamped(6..<10), 6)
}

func testClamp_PartialRangeUpTo() throws {
var x = 5

x.clamp(..<10)
XCTAssertEqual(x, 5)

x.clamp(..<6)
XCTAssertEqual(x, 5)

x.clamp(..<5)
XCTAssertEqual(x, 4)
}

func testClamped_PartialRangeUpTo() throws {
XCTAssertEqual(5.clamped(..<10), 5)
XCTAssertEqual(5.clamped(..<6), 5)
XCTAssertEqual(5.clamped(..<5), 4)
}
}

0 comments on commit 7cbcc2d

Please sign in to comment.