"clamp" Swift 中两个值之间的数字的标准方法
Standard way to "clamp" a number between two values in Swift
鉴于:
let a = 4.2
let b = -1.3
let c = 6.4
我想知道将这些值限制在给定范围内的最简单、Swift最简单的方法,比如 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 中是否还有其他方法可以做到这一点——特别是,我想知道(并记录在案,因为似乎没有关于钳制的问题Swift 中的数字)Swift 标准库中是否有专门用于此目的的内容。
可能没有,如果有,我也乐意接受。
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
类型(如 Int
、CGFloat
、String
、...):
(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
}
}
在 Swift 3 中有新的 CountableClosedRange
、CountableRange
、Range
、ClosedRange
协议。它们具有相同的 upperBound
和 lowerBound
属性。因此,您可以通过声明自定义协议来使用 clamp
方法一次扩展所有 Range
协议:
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
Swift 4/5
Comparable/Strideable
的扩展类似于标准 Swift 库中的 ClosedRange.clamped(to:_) -> ClosedRange
。
extension Comparable {
func clamped(to limits: ClosedRange<Self>) -> Self {
return min(max(self, limits.lowerBound), limits.upperBound)
}
}
#if swift(<5.1)
extension Strideable where Stride: SignedInteger {
func clamped(to limits: CountableClosedRange<Self>) -> Self {
return min(max(self, limits.lowerBound), limits.upperBound)
}
}
#endif
用法:
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
使用与 Apple 相同的语法来执行最小和最大运算符:
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)
这种方法的妙处在于,任何值都定义了执行操作所需的类型,因此编译器会自行处理。
使用 Swift 5.1,实现所需钳位的惯用方法是 property wrappers. A touched-up example from 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
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 中的范围,但我真的认为 clamp 函数的绝对标准语法(“50 年来每种计算机语言都有”)更简单更好:
x = x.clamped(0.5, 5.0)
直到它内置于 Swift,我真的认为那是最好的。
哲学角:
IMO clamp 函数中的两个值 并不是真正的 'range' - 它们只是“两个值”。
(举个例子:在游戏代码中,有时两个动态值的顺序“错误”(即,期望的结果是外部的东西)或相同(结果是就是那个值)。)
关于结束命名的意见...
对于我们所做的一切,我们坚持明确说明是包容还是排斥。例如,如果有电话
randomIntUpTo( 13 )
事实上我们会命名它
randomIntUpToExclusive( 13 )
或者确实是“包容性”,如果是这样的话。或者取决于语言,例如
randomInt(fromInclusive: upToExclusive: )
或任何情况。这样绝对不会出现统一错误,也无需讨论任何内容。所有代码名称都应该是自我记录的。所以确实,对我们来说,上面的函数将被命名为
func clamped(fromExclusive: Self, toExclusive: Self)
或任何描述。
但这就是我们。但这是正确的做法:)
根据@Fattie 的回答和我的评论,为了清楚起见,这里是我的建议:
extension Comparable {
func clamped(_ a: Self, _ b: Self) -> Self {
min(max(self, a), b)
}
}
最短(但可能不是最有效)的钳制方法是:
let clamped = [0, a, 5].sorted()[1]
中的用户 tobr
扩展 FixedWidthInteger
并创建实例泛型方法以接受 RangeExpression
并处理边缘情况:
extension FixedWidthInteger {
func clamped<R: RangeExpression>(with range: R) -> Self where R.Bound == Self {
switch range {
case let range as ClosedRange<Self>:
return Swift.min(range.upperBound, Swift.max(range.lowerBound, self))
case let range as PartialRangeFrom<Self>:
return Swift.max(range.lowerBound, self)
case let range as PartialRangeThrough<Self>:
return Swift.min(range.upperBound, self)
case let range as Range<Self>:
return Swift.min(range.dropLast().upperBound, Swift.max(range.lowerBound, self))
case let range as PartialRangeUpTo<Self>:
return Swift.min(range.upperBound.advanced(by: -1), self)
default: return self
}
}
}
游乐场测试:
100.clamped(with: 1...) // 100
100.clamped(with: ..<100) // 99
100.clamped(with: ...100) // 100
100.clamped(with: 1..<100) // 99
100.clamped(with: 1...100) // 100
0.clamped(with: 1...) // 1
0.clamped(with: ..<100) // 0
0.clamped(with: ...100) // 0
0.clamped(with: 1..<100) // 1
0.clamped(with: 1...100) // 1
要使用 FloatingPoint 实现获得相同的结果,您可以将其 nextDown 属性 用于边缘情况:
extension BinaryFloatingPoint {
func clamped<R: RangeExpression>(with range: R) -> Self where R.Bound == Self {
switch range {
case let range as ClosedRange<Self>:
return Swift.min(range.upperBound, Swift.max(range.lowerBound, self))
case let range as PartialRangeFrom<Self>:
return Swift.max(range.lowerBound, self)
case let range as PartialRangeThrough<Self>:
return Swift.min(range.upperBound, self)
case let range as Range<Self>:
return Swift.min(range.upperBound.nextDown, Swift.max(range.lowerBound, self))
case let range as PartialRangeUpTo<Self>:
return Swift.min(range.upperBound.nextDown, self)
default: return self
}
}
}
游乐场测试:
let value = 100.0
value.clamped(with: 1...) // 100
value.clamped(with: ..<100) // 99.99999999999999
value.clamped(with: ...100) // 100
value.clamped(with: 1..<100) // 99.99999999999999
value.clamped(with: 1...100) // 100
鉴于:
let a = 4.2
let b = -1.3
let c = 6.4
我想知道将这些值限制在给定范围内的最简单、Swift最简单的方法,比如 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 中是否还有其他方法可以做到这一点——特别是,我想知道(并记录在案,因为似乎没有关于钳制的问题Swift 中的数字)Swift 标准库中是否有专门用于此目的的内容。
可能没有,如果有,我也乐意接受。
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
类型(如 Int
、CGFloat
、String
、...):
(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
}
}
在 Swift 3 中有新的 CountableClosedRange
、CountableRange
、Range
、ClosedRange
协议。它们具有相同的 upperBound
和 lowerBound
属性。因此,您可以通过声明自定义协议来使用 clamp
方法一次扩展所有 Range
协议:
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
Swift 4/5
Comparable/Strideable
的扩展类似于标准 Swift 库中的 ClosedRange.clamped(to:_) -> ClosedRange
。
extension Comparable {
func clamped(to limits: ClosedRange<Self>) -> Self {
return min(max(self, limits.lowerBound), limits.upperBound)
}
}
#if swift(<5.1)
extension Strideable where Stride: SignedInteger {
func clamped(to limits: CountableClosedRange<Self>) -> Self {
return min(max(self, limits.lowerBound), limits.upperBound)
}
}
#endif
用法:
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
使用与 Apple 相同的语法来执行最小和最大运算符:
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)
这种方法的妙处在于,任何值都定义了执行操作所需的类型,因此编译器会自行处理。
使用 Swift 5.1,实现所需钳位的惯用方法是 property wrappers. A touched-up example from 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
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 中的范围,但我真的认为 clamp 函数的绝对标准语法(“50 年来每种计算机语言都有”)更简单更好:
x = x.clamped(0.5, 5.0)
直到它内置于 Swift,我真的认为那是最好的。
哲学角:
IMO clamp 函数中的两个值 并不是真正的 'range' - 它们只是“两个值”。
(举个例子:在游戏代码中,有时两个动态值的顺序“错误”(即,期望的结果是外部的东西)或相同(结果是就是那个值)。)
关于结束命名的意见...
对于我们所做的一切,我们坚持明确说明是包容还是排斥。例如,如果有电话
randomIntUpTo( 13 )
事实上我们会命名它
randomIntUpToExclusive( 13 )
或者确实是“包容性”,如果是这样的话。或者取决于语言,例如
randomInt(fromInclusive: upToExclusive: )
或任何情况。这样绝对不会出现统一错误,也无需讨论任何内容。所有代码名称都应该是自我记录的。所以确实,对我们来说,上面的函数将被命名为
func clamped(fromExclusive: Self, toExclusive: Self)
或任何描述。
但这就是我们。但这是正确的做法:)
根据@Fattie 的回答和我的评论,为了清楚起见,这里是我的建议:
extension Comparable {
func clamped(_ a: Self, _ b: Self) -> Self {
min(max(self, a), b)
}
}
最短(但可能不是最有效)的钳制方法是:
let clamped = [0, a, 5].sorted()[1]
中的用户 tobr
扩展 FixedWidthInteger
并创建实例泛型方法以接受 RangeExpression
并处理边缘情况:
extension FixedWidthInteger {
func clamped<R: RangeExpression>(with range: R) -> Self where R.Bound == Self {
switch range {
case let range as ClosedRange<Self>:
return Swift.min(range.upperBound, Swift.max(range.lowerBound, self))
case let range as PartialRangeFrom<Self>:
return Swift.max(range.lowerBound, self)
case let range as PartialRangeThrough<Self>:
return Swift.min(range.upperBound, self)
case let range as Range<Self>:
return Swift.min(range.dropLast().upperBound, Swift.max(range.lowerBound, self))
case let range as PartialRangeUpTo<Self>:
return Swift.min(range.upperBound.advanced(by: -1), self)
default: return self
}
}
}
游乐场测试:
100.clamped(with: 1...) // 100
100.clamped(with: ..<100) // 99
100.clamped(with: ...100) // 100
100.clamped(with: 1..<100) // 99
100.clamped(with: 1...100) // 100
0.clamped(with: 1...) // 1
0.clamped(with: ..<100) // 0
0.clamped(with: ...100) // 0
0.clamped(with: 1..<100) // 1
0.clamped(with: 1...100) // 1
要使用 FloatingPoint 实现获得相同的结果,您可以将其 nextDown 属性 用于边缘情况:
extension BinaryFloatingPoint {
func clamped<R: RangeExpression>(with range: R) -> Self where R.Bound == Self {
switch range {
case let range as ClosedRange<Self>:
return Swift.min(range.upperBound, Swift.max(range.lowerBound, self))
case let range as PartialRangeFrom<Self>:
return Swift.max(range.lowerBound, self)
case let range as PartialRangeThrough<Self>:
return Swift.min(range.upperBound, self)
case let range as Range<Self>:
return Swift.min(range.upperBound.nextDown, Swift.max(range.lowerBound, self))
case let range as PartialRangeUpTo<Self>:
return Swift.min(range.upperBound.nextDown, self)
default: return self
}
}
}
游乐场测试:
let value = 100.0
value.clamped(with: 1...) // 100
value.clamped(with: ..<100) // 99.99999999999999
value.clamped(with: ...100) // 100
value.clamped(with: 1..<100) // 99.99999999999999
value.clamped(with: 1...100) // 100