在Swift中“钳制”两个值之间的数字的标准方法

时间:2016-03-20 05:41:11

标签: swift

假设:

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

我想知道将这些值限制在给定范围内的最简单,最快捷的方式,比如0...5,这样:

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

我知道我可以做到以下几点:

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

或类似的东西:

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

但是我想知道在Swift中是否有其他方法可以做到这一点 - 特别是我想知道(并且在SO上记录,因为似乎没有关于在Swift中钳位数字的问题)是否存在是Swift标准库中的任何内容,专门用于此目的。

可能没有,如果是这样,那也是我很乐意接受的答案。 :)

8 个答案:

答案 0 :(得分:65)

Swift 4/5

Comparable/Strideable的扩展名与ClosedRange.clamped(to:_) -> ClosedRange类似于标准Swift库。

extension Comparable {
    func clamped(to limits: ClosedRange<Self>) -> Self {
        return min(max(self, limits.lowerBound), limits.upperBound)
    }
}

extension Strideable where Stride: SignedInteger {
    func clamped(to limits: CountableClosedRange<Self>) -> Self {
        return min(max(self, limits.lowerBound), limits.upperBound)
    }
}

<强>用法:

15.clamped(to: 0...10) // returns 10
3.0.clamped(to: 0.0...10.0) // returns 3.0
"a".clamped(to: "g"..."y") // returns "g"

// this also works (thanks to Strideable extension)
let range: CountableClosedRange<Int> = 0...10
15.clamped(to: range) // returns 10

答案 1 :(得分:40)

ClosedInterval类型已经有

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

将另一个 interval 作为参数的方法。有一个 关于Swift进化邮件列表的提案

添加另一个方法,将单值钳位到给定的时间间隔:

/// 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,也适用于所有人 类型Comparable(例如IntCGFloatString,...):

(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
    }
}

答案 2 :(得分:15)

使用与Apple相同的语法来执行min和max运算符:

public func clamp<T>(_ value: T, minValue: T, maxValue: T) -> T where T : Comparable {
    return min(max(value, minValue), maxValue)
}

你可以这样使用:

let clamped = clamp(newValue, minValue: 0, maxValue: 1)

这种方法很酷的是,任何值都定义了执行操作所必需的类型,因此编译器自己处理它。

答案 3 :(得分:9)

2020。非常简单的方法。

extension Comparable {
    func clamped(_ f: Self, _ t: Self)  ->  Self {
        var r = self
        if r < f { r = f }
        if r > t { r = t }
        // (use SIMPLE, EXPLICIT code here to make it utterly clear
        // whether we are inclusive, what form of equality, etc etc)
        return r
    }

虽然我在Swift中真正的爱系列,但我确实认为钳制函数的绝对标准语法(“每种计算机语言已有50年的历史了”)更简单,更好: >

x = x.clamped(0.5, 5.0)

直到它内置到Swift中,我真的认为这是最好的。

哲学角:

IMO钳位函数中的两个值实际上不是“范围” -它们只是“两个值”。

(例如:在游戏代码中,两个动态值有时处于“错误的顺序”(即所需的结果在外面)是完全相同的(在结果中只是值)。

答案 4 :(得分:6)

在Swift 3中有新的CountableClosedRangeCountableRangeRangeClosedRange协议。它们具有相同的upperBoundlowerBound属性。因此,您可以通过声明自定义协议,使用Range方法一次扩展所有clamp协议:

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

答案 5 :(得分:5)

在关注@Fattie的回答和我的评论后,以下是我的建议:

extension Comparable {
    func clamped(_ a: Self, _ b: Self) -> Self {
        max(min(self, a), b)
    }
}

答案 6 :(得分:2)

使用 Swift 5.1 ,惯用的方式是使用property wrappers。来自NSHipster的修饰示例:

@propertyWrapper
struct Clamping<Value: Comparable> {
  var value: Value
  let range: ClosedRange<Value>

  init(wrappedValue: Value, _ range: ClosedRange<Value>) {
    precondition(range.contains(wrappedValue))
    self.value = wrappedValue
    self.range = range
  }

  var wrappedValue: Value {
    get { value }
    set { value = min(max(range.lowerBound, newValue), range.upperBound) }
  }
}

用法:

@Clamping(0...5) var a: Float = 4.2
@Clamping(0...5) var b: Float = -1.3
@Clamping(0...5) var c: Float = 6.4

答案 7 :(得分:1)

最短(但可能不是最有效)的夹紧方式是:

let clamped = [0, a, 5].sorted()[1]

来源:用户{@ {3}}中的