3

I currently receive a Int that could be anywhere between 0 and 180,000. I need to change it to fit between 0 and 3000. I know in other languages you can use something like

Map(min1,max1,min2,max2,input)

I can't seem to find something like that inside of swift. This is what I currently have, but it always returns 0.

var newY = [(Int)(input)]
newY = newY.map {_ in 0 * 3000}
print(newY[0])

I think I am using the wrong function. I have never done any mapping in Swift before.

Travis Griggs
  • 21,522
  • 19
  • 91
  • 167
Jacob Bashista
  • 87
  • 2
  • 10
  • http://stackoverflow.com/questions/24132399/how-does-one-make-random-number-between-range-for-arc4random-uniform – Sahil Mar 15 '17 at 17:50
  • What does "keep a number between two numbers" mean? Do you want to generate a random number? – Andrew Brooke Mar 15 '17 at 17:51
  • Your question is unclear, but documentation on `map` can be found [here](https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/Closures.html). – Caleb Mar 15 '17 at 17:55
  • Lets say you get a value `3003`: should this be mapped to `3`, or to an integer of the size `~3000*3003/180000` (`~50`)? – dfrib Mar 15 '17 at 17:57
  • Which other language does your example come from? It might be easier to answer your question if I could see the definition of how that one behaves. – Phillip Mills Mar 15 '17 at 18:02
  • @sahil not a duplicate, that question deals with random numbers. This question has to do with bounding a number. – David Berry Mar 15 '17 at 18:03
  • @PhillipMills Here is where I am getting my example from https://processing.org/reference/map_.html – Jacob Bashista Mar 15 '17 at 18:34
  • @dfri It should be mapped like `~3000*3003/180000 (~50` – Jacob Bashista Mar 15 '17 at 18:35

6 Answers6

10

The map function on collections is going to do something very different. It applies a mapping function to each element of a collection and returns a new collection based on the results.

What you're looking for would be:

func map(minRange:Int, maxRange:Int, minDomain:Int, maxDomain:Int, value:Int) -> Int {
    return minDomain + (maxDomain - minDomain) * (value - minRange) / (maxRange - minRange)
}

print(map(minRange: 0, maxRange: 1800000, minDomain: 0, maxDomain: 3000, value: 200000))

With only a little more work you can make it generic over all integer types:

func map<T:IntegerArithmetic>(minRange:T, maxRange:T, minDomain:T, maxDomain:T, value:T) -> T {
    return minDomain + (maxDomain - minDomain) * (value - minRange) / (maxRange - minRange)
}

Another option would be to take advantage of the Swift Range type to make calling more succinct:

func map<T:IntegerArithmetic>(range:Range<T>, domain:Range<T>, value:T) -> T {
    return domain.lowerBound + (domain.upperBound - domain.lowerBound) * (value - range.lowerBound) / (range.upperBound - range.lowerBound)
}

map(range:0..<3000, domain:0..<180000, value: 1500)
David Berry
  • 40,941
  • 12
  • 84
  • 95
2

Here's a version that works for both Double and Float:

extension FloatingPoint {
  /// Allows mapping between reverse ranges, which are illegal to construct (e.g. `10..<0`).
  func interpolated(
    fromLowerBound: Self,
    fromUpperBound: Self,
    toLowerBound: Self,
    toUpperBound: Self) -> Self
  {
    let positionInRange = (self - fromLowerBound) / (fromUpperBound - fromLowerBound)
    return (positionInRange * (toUpperBound - toLowerBound)) + toLowerBound
  }

  func interpolated(from: ClosedRange<Self>, to: ClosedRange<Self>) -> Self {
    interpolated(
      fromLowerBound: from.lowerBound,
      fromUpperBound: from.upperBound,
      toLowerBound: to.lowerBound,
      toUpperBound: to.upperBound)
  }
}

Usage:

50.interpolated(from: 0...100, to: 0...10) == 5
3.interpolated(from: 1...5, to: 0...10) == 5
4.interpolated(from: 1...5, to: 1...10) == 7.75
11.interpolated(
  fromLowerBound: 10,
  fromUpperBound: 20,
  toLowerBound: 100,
  toUpperBound: 120) == 102
11.interpolated(
  fromLowerBound: 10,
  fromUpperBound: 20,
  toLowerBound: 120,
  toUpperBound: 100) == 118
11.interpolated(
  fromLowerBound: 20,
  fromUpperBound: 10,
  toLowerBound: 100,
  toUpperBound: 120) == 118
11.interpolated(
  fromLowerBound: 20,
  fromUpperBound: 10,
  toLowerBound: 120,
  toUpperBound: 100) == 102
Senseful
  • 86,719
  • 67
  • 308
  • 465
1
//Map function
func mapy(n:Double, start1:Double, stop1:Double, start2:Double, stop2:Double) -> Double {
    return ((n-start1)/(stop1-start1))*(stop2-start2)+start2;
};
omarojo
  • 1,197
  • 1
  • 13
  • 26
0

Given that you are looking for mapping the relative position of a value in a given range to another range, you could do something along the lines:

// dummy function name
func transform(_ number: Int, fromRange: (Int, Int), toRange: (Int, Int)) -> Int? {
    guard number >= fromRange.0 && number <= fromRange.1,
        toRange.0 <= toRange.1 else { return nil }
    return toRange.0 + (number-fromRange.0)*(toRange.1-toRange.0)/(fromRange.1-fromRange.0)
}

// ex1
let numberA = 3001
let transformedNumberA = transform(numberA, fromRange: (0, 180_000), toRange: (0,3000))
print(transformedNumberA ?? -1) // 50

// ex2
let numberB = 134_000
let transformedNumberB = transform(numberB, fromRange: (0, 180_000), toRange: (0,3000))
print(transformedNumberB ?? -1) // 2233

// ex3
let numberC = 200_000
let transformedNumberC = transform(numberC, fromRange: (0, 180_000), toRange: (0,3000))
print(transformedNumberC ?? -1) // -1 (nil return)

Just take care to notice that (number-fromRange.0)*(toRange.1-toRange.0) (left associativity of / and * operators) may overflow.

dfrib
  • 70,367
  • 12
  • 127
  • 192
  • You need to subtract the minimum input range value before math, otherwise it only works if the input range starts at zero. – David Berry Mar 15 '17 at 18:15
0

It's not clear whether the OP simply wants to clamp (title seems to say that) or wants to interpolate from one bounding range to another. The title makes me think one, but the dual max/min values make me think they're after an interpolation. I answered the former. David Berry has a good answer for the latter.

What you may want is min/max functions. Swift has these. To "clamp" a value between a low and high value, you usually combine the two:

var bottom = 13
var top = 42
var tooLow = 7
var clamped = min(top, max(bottom, tooLow)) -> 13
var justRight = 23
clamped = min(top, max(bottom, justRight)) --> 23
var tooHigh = 99
clamped = min(top, max(bottom, tooHigh))  --> 42

This is usually the route most people go, and is probably good enough for most. I personally hate writing that again and again, and I get tired of having to think about which side to feed into the max and the min. And I don't like that it uses what looks like a free function, I'm an object oriented message sending sort of guy, so I do the following:

precedencegroup MinMaxPrecedence {
    associativity: left
    higherThan: NilCoalescingPrecedence, AdditionPrecedence, MultiplicationPrecedence
}

infix operator <> : MinMaxPrecedence

func <><T:Comparable>(a:T, b:T) -> T {
    return a < b ? a : b
}

infix operator >< : MinMaxPrecedence

func ><<T:Comparable>(a:T, b:T) -> T {
    return a < b ? b : a
}

Basically, this defines two new operators (<> and ><) that can be used between any type that adopts Comparable. They're easy for me to remember, the one that tucks in smaller wants the smaller value, and the one that opens up bigger returns the bigger value. What's nice is that you can then put them in simpler expressions:

var bottom = 13
var top = 42
var tooLow = 7
var justRight = 23
var tooHigh = 99
bottom >< tooLow <> top --> 13
bottom >< justRight <> top --> 23
bottom >< tooHigh <> top --> 42
Travis Griggs
  • 21,522
  • 19
  • 91
  • 167
  • Although I agree it's not absolutely clear, his `map` function takes two ranges which implies he's actually wanting to interpolate. Combine that with having seen a `map` function somewhere somewhen that does exactly that, it seems likely that's what they're actually wanting. – David Berry Mar 15 '17 at 18:26
  • Yup, realized that was likely after writing all of the above out. :( – Travis Griggs Mar 15 '17 at 18:27
0

Or you can use the simd_mix function...

import simd

func remap(sourceMin: Float, sourceMax: Float, destMin: Float, destMax: Float, t: Float) -> Float {
    let f = (t - sourceMin) / (sourceMax - sourceMin)
    return simd_mix(destMin, destMax, f)
}
Niklas
  • 1,322
  • 14
  • 11