2016-03-20 10 views
32

Dane:standardowy sposób „zaciśnięcia” liczbę pomiędzy dwoma wartościami w Swift

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

Chcę wiedzieć najprostszych, Swiftiest drogę do dociskania tych wartości na danym zakresie, powiedzmy 0...5, tak że:

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

wiem, że mogę wykonać następujące czynności:

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

albo coś podobnego:

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

Ale zastanawiałem się, czy są jakieś inne sposoby, aby to zrobić w Swift w szczególności chcę wiedzieć (oraz dokument na SO, ponieważ nie wydaje się być pytanie o zaciskania numery Swift), czy jest coś w standardowej bibliotece Swift, przeznaczonej specjalnie do tego celu.

Może nie być, a jeśli tak, to także odpowiedź, którą z radością przyjmuję. :)

Odpowiedz

31

ClosedInterval typ ma już

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

metodę, która zajmuje kolejne interwału jako argument. Jest propozycja na liście mailingowej Swift ewolucji

dodać inną metodę, która dociska pojedynczą wartość w danym przedziale:

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

i który jest dokładnie to, czego potrzebujesz.

Stosując realizację istniejącego clamp() sposobie w

jako przykład, dodatkowy sposób clamp() może być wykonany jako

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

Przykład:

(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 jest sprawdzonym generic type

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

dlatego to działa nie tylko dla Double ale dla wszystkich typów, które są Comparable (jak Int, CGFloat, String, ...):

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

Aktualizacja Swift 3 (Xcode 8):ClosedInterval została zmieniona do ClosedRange, a jego właściwości są lower/upperBound teraz:

extension ClosedRange { 
    func clamp(_ value : Bound) -> Bound { 
     return self.lowerBound > value ? self.lowerBound 
      : self.upperBound < value ? self.upperBound 
      : value 
    } 
} 
+0

Fantastyczny! To jest dokładnie ten rodzaj informacji, których szukałem. Chciałbym zobaczyć to zaimplementowane w przyszłych wersjach Swift. –

+0

Jako rodzimy Szwed (alfabet: '..., x, y, z, å, ä, ö'),' "ä" <"z" // true' jest nieco mylący;) w innej notatce, Wymienione przykłady 'clamp' będą musiały być użyte z zewnętrzną nazwą parametru' value' dla przykładu Swift 3, być może warto wspomnieć o tym wyraźnie (lub Xcode naprawi to zapytanie!). – dfri

+1

@dfri: Porównanie łańcuchów i znaków jest * nie * zależne od ustawień narodowych, porównaj http://stackoverflow.com/questions/25713975/what-does-it-mean-that-string-and-character-comparisons-in-swift- are-not-locale. Dodano zewnętrzną nazwę parametru "_", aby przykłady znów działały. Dzięki za opinie! –

4

w Swift 3 istnieją nowe protokoły CountableClosedRange, CountableRange, Range, ClosedRange. Mają takie same właściwości: upperBound i lowerBound. Więc można przedłużyć wszystkie Range protokołów jednocześnie z metodą clamp deklarując protokół niestandardowe:

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

Zastosowanie:

(0...10).clamp(12) // 10 
(0..<100).clamp(-2) // 0 
("a"..."c").clamp("z") // c 
+6

'(0 .. <100) .clamp (123)' zwraca '100', co * może * być nieoczekiwane, ponieważ liczba ta nie jest zawarta w przedziale. –

+0

Powyższy problem występuje we wszystkich przykładowych implementacjach! – StackUnderflow

+0

To dobry powód, aby zaimplementować mocowanie tylko dla zamkniętych zakresów. –

35

Swift 4, Swift 3

Przedłużenie Comparable podobnym do ClosedRange.clamped(to:_) -> ClosedRange ze standardowej biblioteki Swift 3.

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

extension Strideable where Self.Stride: SignedInteger { 

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

Zastosowanie:

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" 
4

Stosując taką samą składnię jak jabłko zrobić MIN i MAX operatora:

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

można użyć jako że:

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

Fajną rzeczą w tym podejściu jest to, że każdy wartość określa typ niezbędny do wykonania operacji, więc kompilator sam to obsługuje.

Powiązane problemy