2
\$\begingroup\$

I have a function that receives a String like the following:

"-10.0 -10.0,10.0 10.0,0.0 0.0"

And needs to translate that into a Seq[Coordinate] that would look like this:

Seq(
    Coordinate(-10.0, -10.0),
    Coordinate( 10.0,  10.0),
    Coordinate(  0.0,   0.0)
)

Here is the function that I wrote to achieve this:

def toCoordinates(coordinates: String): Seq[Coordinate] = {
  val coordinatesList = coordinates.split(",")
  coordinatesList flatMap { coordinate =>
    coordinate.split("\\s+") match {
      case Array(lat:String, lon:String) => Some(Coordinate(lat.toDouble, lon.toDouble))
      case _                             => None
    }
  }
}

I see that if someone sends "foo bar" as a coordinate, my code would fail miserably, although I don't see a way to prevent this without some if conditions.

Any idea on how I can implement this check while still having functional code? Or any other tips that might be helpful for a Scala newbie.

\$\endgroup\$
1
\$\begingroup\$

Instead of writing lat.toDouble and lon.toDouble, I would use s.split("\\s+").map(_.toDouble) to convert all elements. The type annotation within the match seems excessive to me here.

To discard invalid coordinates such as "foo bar", you would have to catch a NumberFormatException.

Note that split("\\s+") is not tolerant of spaces after commas. For example, " 1.0 2.0".split("\\s+") would produce Array("", "1.0", "2.0"), which would be a three-element array and thus considered invalid.

def toCoordinates(ss: String): Seq[Coordinate] = {
  ss.split(",").flatMap { s =>
    try {
      s.split("(?<!^)\\s+").map(_.toDouble) match {
        case Array(lat, lon) => Some(Coordinate(lat, lon))
        case _               => None       
      }
    } catch {
      case e: NumberFormatException => None
    }
  }
}
\$\endgroup\$
3
\$\begingroup\$

Also you can write more human-readable and understandable algorithm with the help of extractors and method collect:

val Coord = """(.+?)\s+(.+)""".r

object Double { def unapply(s: String) = Try(s.toDouble).toOption }

def parseCoordinates(input: String): Seq[Coordinate] =
  input.split(',') collect {
    case Coord(Double(lat), Double(lan)) => Coordinate(lat, lan)
  }
\$\endgroup\$
  • \$\begingroup\$ what does that .r do? The one on the definition of Coord \$\endgroup\$ – Rodrigo Sasaki Jul 18 '16 at 18:50
  • \$\begingroup\$ Method .r creates regular expression (instance of Regex class). "abc".r is equivalent to new Regex("abc"). See description and usage here. \$\endgroup\$ – Nikolay Artamonov Jul 20 '16 at 11:48

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.