85

Given:

let a = 4.2
let b = -1.3
let c = 6.4

I want to know the simplest, Swiftiest way to clamp these values to a given range, say 0...5, such that:

a -> 4.2
b -> 0
c -> 5

I know I can do the following:

let clamped = min(max(a, 0), 5)

Or something like:

let clamped = (a < 0) ? 0 : ((a > 5) ? 5 : a)

But I was wondering if there were any other ways to do this in Swift—in particular, I want to know (and document on SO, since there doesn't appear to be a question about clamping numbers in Swift) whether there is anything in the Swift standard library intended specifically for this purpose.

There may not be, and if so, that's also an answer I'll happily accept.

4

9 に答える 9

59

ClosedIntervalタイプにはすでに

func clamp(_ intervalToClamp: ClosedInterval<Bound>) -> ClosedInterval<Bound>

別の間隔を引数として取るメソッド。Swift evolution メーリングリストに提案があります

特定の間隔に単一の値をクランプする別のメソッドを追加するには:

/// Returns `value` clamped to `self`.
func clamp(value: Bound) -> Bound

それはまさにあなたが必要とするものです。

clamp()で既存のメソッドの実装を使用する

例として、この追加のclamp()メソッドは次のように実装できます。

extension ClosedInterval {
    func clamp(value : Bound) -> Bound {
        return self.start > value ? self.start
            : self.end < value ? self.end
            : value
    }
}

例:

(0.0 ... 5.0).clamp(4.2)    // 4.2
(0.0 ... 5.0).clamp(-1.3)   // 0.0
(0.0 ... 5.0).clamp(6.4)    // 5.0

ClosedIntervalジェネリック型です

public struct ClosedInterval<Bound : Comparable> { ... }

したがって、これは( 、、、 ... など)だけでDoubleなく、すべてのタイプに対して機能します。ComparableIntCGFloatString

(1 ... 3).clamp(10)      // 3
("a" ... "z").clamp("ä") // "ä"

Swift 3 (Xcode 8) の更新:ClosedIntervalは に名前が変更されClosedRange、そのプロパティは次のようになりましたlower/upperBound

extension ClosedRange {
    func clamp(_ value : Bound) -> Bound {
        return self.lowerBound > value ? self.lowerBound
            : self.upperBound < value ? self.upperBound
            : value
    }
}
于 2016-03-20T07:58:33.907 に答える
8

Swift 3 には、新しいCountableClosedRangeCountableRange、プロトコルがRangeあります。ClosedRangeそれらは同じプロパティupperBoundを持っています。したがって、カスタム プロトコルを宣言することにより、メソッドを使用してすべてのプロトコルを一度にlowerBound拡張できます。Rangeclamp

protocol ClampableRange {

    associatedtype Bound : Comparable

    var upperBound: Bound { get }

    var lowerBound: Bound { get }

}

extension ClampableRange {

    func clamp(_ value: Bound) -> Bound {
        return min(max(lowerBound, value), upperBound)
    }  

}

extension Range : ClampableRange {}
extension ClosedRange : ClampableRange {}
extension CountableRange : ClampableRange {}
extension CountableClosedRange : ClampableRange {}

使用法:

(0...10).clamp(12) // 10
(0..<100).clamp(-2) // 0
("a"..."c").clamp("z") // c
于 2016-08-05T12:56:28.023 に答える