Reputation: 53826
I'm trying to write an algorithm that returns the closest value within a list of values. So in a List(4.0, 6.0, 2.0) the closest value to 7.0 is 6.0
Below is the code I'm using but it's not correct as the closest value being returned is 4 :
How can fix below code or is there a Scala utility method I can use to solve this ?
val num = 7.0 //> num : Double = 7.0
val listNums = List[Double](4,6,2) //> listNums : List[Double] = List(4.0, 6.0, 2.0)
def getClosest(num : Double , listNums : List[Double]) = {
var min = java.lang.Double.MAX_VALUE
var closest = num
for(x <- listNums ){
val diff = x - num
if(num < min){
min = diff
closest = x
}
}
closest
} //> getClosest: (num: Double, listNums: List[Double])Double
val closest = getClosest(num , listNums) //> closest : Double = 4.0
Upvotes: 5
Views: 4141
Reputation: 59994
If you want to be more generic, you can try this:
def getClosest[A: Numeric](value: A, elems: Traversable[A]): A = {
val ops = implicitly[Numeric[A]]
elems.minBy(e => ops.abs(ops.minus(e, value)))
}
Then:
getClosest(20, List(1, 19, 22, 24))
//> res1: Int = 19
getClosest(BigDecimal("5000000000000000"), List(BigDecimal(1), BigDecimal(19)))
//> res2: scala.math.BigDecimal = 19
Upvotes: 0
Reputation: 31724
A simple implementation would be:
def getClosest(num : Double , list : List[Double]) :Double = list match {
case x :: xs => list.foldLeft(x){(ans,next) =>
if(math.abs(next - num) < math.abs(ans - num)) next else ans }
case Nil => throw new RuntimeException("Empty list")
}
scala> getClosest(20, List(1,19,22,24))
res0: Double = 19.0
A more general implementation would be:
def getClosest[A: Numeric](num: A, list: List[A]): A = {
val im = implicitly[Numeric[A]]
list match {
case x :: xs => list.minBy (y => im.abs(im.minus(y, num)))
case Nil => throw new RuntimeException("Empty list")
}
}
Thanks to @Travis for suggesting minBy
. It is much more prettier than foldLeft
Upvotes: 3
Reputation: 139038
This is almost a one-liner with minBy
:
def getClosest(num: Double, listNums: List[Double]) =
listNums.minBy(v => math.abs(v - num))
minBy
is unfortunately a partial function—it'll crash with an exception when called on an empty list. To match the behavior of your implementation, you can write the following:
def getClosest(num: Double, listNums: List[Double]) = listNums match {
case Nil => Double.MaxValue
case list => list.minBy(v => math.abs(v - num))
}
The problem with your code is that you're not taking the absolute value, as the other answer implicitly points out. Don't use Math.abs
, though—that's shorthand for java.lang.Math.abs
. math.abs
is more idiomatic.
Upvotes: 22