Стандартный способ "зажимать" число между двумя значениями в 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, предназначенной специально для этой цели.
Не может быть, и если да, то и ответа я с радостью соглашусь.:)
Ответы
Ответ 1
ClosedInterval уже имеет
func clamp(_ intervalToClamp: ClosedInterval<Bound>) -> ClosedInterval<Bound>
который принимает в качестве аргумента другой интервал. Eсть
предложение о списке рассылки 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
, но и для всех
типы 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
}
}
Ответ 2
Свифт 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
Ответ 3
Используя тот же синтаксис, что и 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)
Приятная вещь в этом подходе заключается в том, что любое значение определяет необходимый тип для выполнения операции, поэтому компилятор обрабатывает это сам.
Ответ 4
В Swift 3 есть новые протоколы CountableClosedRange
, CountableRange
, Range
, ClosedRange
. Они имеют те же свойства upperBound
и lowerBound
. Таким образом, вы можете распространять все протоколы 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
В Swift 5.1 идиоматическим способом достижения желаемого зажима будет использование упаковщиков свойств. Доработанный пример из 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
Ответ 6
Я думаю, что переопределение свойства является одним из способов достижения ограничения
var retryCount = 4 {
willSet {
defer {
retryCount = min(newValue, 4)
}
}
}