Swift에서 두 값 사이의 숫자를 "clamp"하는 표준 방법
주어진:
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에서 이 작업을 수행할 수 있는 다른 방법이 있는지 궁금합니다. 특히 Swift 표준 라이브러리에 이러한 목적을 위해 특별히 고안된 것이 있는지 여부를 알고 싶습니다(Swift에서 번호를 클램핑하는 것에 대한 질문이 없기 때문에 SO에 대해 문서화합니다.
없을 수도 있고, 만약 그렇다면 그것 또한 기꺼이 받아들이겠습니다.
스위프트 4/5
확장Comparable/Strideable
와 비슷한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
ClosedInterval 유형에 이미 다음이 있습니다.
func clamp(_ intervalToClamp: ClosedInterval<Bound>) -> ClosedInterval<Bound>
다른 구간을 인수로 사용하는 메서드입니다.스위프트 에볼루션 메일링 리스트에 제안이 있습니다.
주어진 구간에 단일 값을 클램프하는 다른 방법을 추가하려면:
/// 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
}
}
Apple과 동일한 구문을 사용하여 min and 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)
이 접근 방식의 멋진 점은 어떤 값이라도 연산을 수행하는 데 필요한 유형을 정의하므로 컴파일러가 자체적으로 이를 처리한다는 것입니다.
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
}
저는 스위프트의 레인지를 정말 좋아하지만, 클램프 기능에 대한 절대적인 표준 구문("모든 컴퓨터 언어에서 현재 50년 동안")은 더 간단하고 더 좋다고 생각합니다.
x = x.clamped(0.5, 5.0)
스위프트에 내장되기 전까지는 그게 최선이라고 생각합니다.
철학 코너:
IMO 클램프 함수의 두 값은 실제로 '범위'가 아닙니다. 단지 '두 값'일 뿐입니다.
(예를 들어, 두 동적 값이 때때로 "잘못된 순서"(즉, 원하는 결과가 외부의 것임) 또는 동일한(결과가 그 값임) 것이 게임 코드에서 완전히 일반적입니다.)
엔드 네이밍에 대한 의견...
우리는 우리가 하는 모든 일에 대해 포괄적인지 배타적인지 명시적으로 말할 것을 주장합니다.예를 들어, 전화가 오면
randomIntUpTo( 13 )
사실 우리는 그것을 이름지을 것입니다.
randomIntUpToExclusive( 13 )
아니면 정말로 '실수'인 것 같습니다.아니면 언어에 따라서도.
randomInt(fromInclusive: upToExclusive: )
사건이 어떻게 되든 간에이런 식으로 절대적으로 통일의 오류는 없으며, 논의할 필요도 없습니다.모든 코드 이름은 자체 문서화해야 합니다.그래서 실제로 우리에게 위의 함수는
func clamped(fromExclusive: Self, toExclusive: Self)
뭐라 표현하든 간에
하지만 그건 우리뿐입니다.하지만 이건 옳은 일입니다 :)
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
스위프트 3에는 새로운 것이 있습니다.CountableClosedRange
,CountableRange
,Range
,ClosedRange
의정서그들은 같은 것을 가지고 있습니다.upperBound
그리고.lowerBound
특성.그래서 모두 확장할 수 있습니다.Range
한에a한het으로 한 번에 프로토콜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
@Fattie의 답변과 제 의견에 이어서, 명확성을 위해 제 제안을 드립니다.
extension Comparable {
func clamped(_ a: Self, _ b: Self) -> Self {
min(max(self, a), b)
}
}
클램프를 가장 짧게(가장 효율적이지 않을 수도 있지만) 사용하는 방법은 다음과 같습니다.
let clamped = [0, a, 5].sorted()[1]
FixedWidthInteger
릭를여를합니다 A다aaog스ncded릭를를a> )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
하는 것은 에 어긋난다고 합니다. 즉, Δ Δ Δ Δ Δ Δ Δ Δ Δ Δ Δ Δ Δ Δ Δ Δ Δ Δ Δ Δ Δ Δ Δ Δ Δ Δ Δ Δ Δmin()
을로다다로s을adnoe,dmax()
값을 하한으로 클램핑합니다.따라서 다른 답에 추가하여 하한과 상한뿐만 아니라 범위까지 값을 클램핑할 수 있는 확장을 사용합니다.
extension Comparable {
func clamped(range: ClosedRange<Self>) -> Self {
return max(range.lowerBound, min(self, range.upperBound))
}
func clamped(lowerBound: Self) -> Self {
return max(lowerBound, self)
}
func clamped(upperBound: Self) -> Self {
return min(self, upperBound)
}
}
언급URL : https://stackoverflow.com/questions/36110620/standard-way-to-clamp-a-number-between-two-values-in-swift
'programing' 카테고리의 다른 글
ES6 제너레이터: 반복기의 스택 트레이스가 불량합니다.던지다 (err) (0) | 2023.09.12 |
---|---|
바닥글에 워드프레스 jquery (0) | 2023.09.12 |
Spring HATEOAS versus Spring Data Rest (0) | 2023.09.12 |
MySQL 삽입(While Loop 포함) (0) | 2023.09.12 |
도커-허브를 사용하지 않고 도커-이미지를 공유하는 방법? (0) | 2023.09.12 |