2
\$\begingroup\$

For my first attempt at Scala I decided to solve a simple exercise on codingame.com.

Based on a string input containing the user location, the defibrillators count and a list of defibrillators entries (id, name, address, phone number, longitude, latitude), I have to print the closest defibrillator from the user. The distance formula is given in the rules.

An example of input:

3,879483
43,608177
3
1;Maison de la Prevention Sante;6 rue Maguelone 340000 Montpellier;;3,87952263361082;43,6071285339217
2;Hotel de Ville;1 place Georges Freche 34267 Montpellier;;3,89652239197876;43,5987299452849
3;Zoo de Lunaret;50 avenue Agropolis 34090 Mtp;;3,87388031141133;43,6395872778854
import math._
import scala.util._

object Solution extends App {
    val playerLongitude = readLine
    val playerLatitude = readLine
    val defibrilatorCount = readInt

    def sanitizeString(string : String) : String = string.replace(',', '.')
    def stringToDouble(string : String) : Double =  sanitizeString(string).toDouble

    val playerLongitudeValue = stringToDouble(playerLongitude)
    val playerLatitudeValue = stringToDouble(playerLatitude)

    def distanceToPlayer(long : Double, lat : Double) :  Double = {
        val x = (long - playerLongitudeValue) * cos((playerLatitudeValue + lat) / 2)
        val y  = lat - playerLatitudeValue
        sqrt(x * x + y * y) * 6371;
    }

    def distanceToPlayer(stringInput : String) : Double = {
        val tokens = sanitizeString(stringInput).split(";")
        distanceToPlayer(tokens(4).toDouble, tokens(5).toDouble)
    }

    var smallestDistance = Double.MaxValue
    var closestDefibName = ""

    for(i <- 0 until defibrilatorCount) {
        val defib = readLine
        val distance = distanceToPlayer(defib)
        if(distance < smallestDistance) {
            smallestDistance = distance
            closestDefibName = defib.split(";")(1)
        }
    }

    println(closestDefibName)
}
\$\endgroup\$
1
\$\begingroup\$

The main observation I would make is that this code — particularly the for loop — looks very procedural. To find the minimum of some collection as determined by some metric, use .minBy[B](f: (A) ⇒ B): A.

For readability, avoid interleaving function definitions with the execution flow. Put all of the definitions first, if possible.

You potentially split each defibrillator input line twice: once to extract the coordinates, then maybe again if you are interested in extracting the name. I suggest parsing each line once and constructing a case class object from it.

Your sanitizeString function is rather heavy-handed. To parse French-formatted decimals, use java.text.NumberFormat.

Note that defibrilatorCount is misspelled.

Suggested solution

import math.{cos, sqrt}
import io.StdIn.{readInt, readLine}
import java.text.NumberFormat
import java.util.Locale

object Solution extends App {
  val frenchFmt = NumberFormat.getInstance(Locale.FRANCE)
  def parseDouble(s: String): Double = frenchFmt.parse(s).doubleValue

  case class GeoObject(long: Double, lat: Double, name: String)

  def parseDefibrillator(line: String): GeoObject = {
    val fields = line.split(";")
    GeoObject(parseDouble(fields(4)), parseDouble(fields(5)), fields(1))
  }

  def distanceMetric(a: GeoObject, b: GeoObject): Double = {
    val x = (a.long - b.long) * cos((a.lat + a.lat) / 2)
    val y = a.lat - b.lat
    6371 * sqrt(x * x + y * y)
    // Note: 6371 * sqrt is optional, if just comparing distances
  }

  val player = GeoObject(parseDouble(readLine), parseDouble(readLine), null)
  val count = readInt
  val nearest = (0 until count).map(_ => parseDefibrillator(readLine))
                               .minBy(distanceMetric(player, _))
  println(nearest.name)
}
\$\endgroup\$
  • \$\begingroup\$ I was wondering how to remove the loops and you answered that question. Thank you for your answer, I find it very elegant. \$\endgroup\$ – Stud Jul 11 '18 at 11:36

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.