Подтвердить что ты не робот

Стандартный способ "зажимать" число между двумя значениями в 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, предназначенной специально для этой цели.

Не может быть, и если да, то и ответа я с радостью соглашусь.:)

4b9b3361

Ответ 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)
        }
    }
}