blue-sky
blue-sky

Reputation: 53826

How to find value closest to List of values?

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

Answers (4)

Jean-Philippe Pellet
Jean-Philippe Pellet

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

Jatin
Jatin

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

Travis Brown
Travis Brown

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

Don Roby
Don Roby

Reputation: 41137

Define an Ordering by distance from your selected number, and use the min mathod from List applying this ordering.

I don't have a scala install handy right now to provide a coded tested solution, but the two pieces of this are relatively easy.

Upvotes: 0

Related Questions