Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Reading Multiple Inputs from the Same Line the Scala Way

I tried to use readInt() to read two integers from the same line but that is not how it works.

val x = readInt()
val y = readInt()

With an input of 1 727 I get the following exception at runtime:

Exception in thread "main" java.lang.NumberFormatException: For input string: "1 727"
    at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
    at java.lang.Integer.parseInt(Integer.java:492)
    at java.lang.Integer.parseInt(Integer.java:527)
    at scala.collection.immutable.StringLike$class.toInt(StringLike.scala:231)
    at scala.collection.immutable.StringOps.toInt(StringOps.scala:31)
    at scala.Console$.readInt(Console.scala:356)
    at scala.Predef$.readInt(Predef.scala:201)
    at Main$$anonfun$main$1.apply$mcVI$sp(Main.scala:11)
    at scala.collection.immutable.Range.foreach$mVc$sp(Range.scala:75)
    at Main$.main(Main.scala:10)
    at Main.main(Main.scala)

I got the program to work by using readf but it seems pretty awkward and ugly to me:

  val (x,y) = readf2("{0,number} {1,number}")
  val a = x.asInstanceOf[Int]
  val b = y.asInstanceOf[Int]
  println(function(a,b))

Someone suggested that I just use Java's Scanner class, (Scanner.nextInt()) but is there a nice idiomatic way to do it in Scala?

Edit: My solution following paradigmatic's example:

val Array(a,b) = readLine().split(" ").map(_.toInt)

Followup Question: If there were a mix of types in the String how would you extract it? (Say a word, an int and a percentage as a Double)

like image 438
Trevor Avatar asked Sep 23 '11 07:09

Trevor


2 Answers

If you mean how would you convert val s = "Hello 69 13.5%" into a (String, Int, Double) then the most obvious way is

val tokens = s.split(" ")
(tokens(0).toString, 
 tokens(1).toInt, 
 tokens(2).init.toDouble / 100)
 // (java.lang.String, Int, Double) = (Hello,69,0.135)

Or as mentioned you could match using a regex:

val R = """(.*) (\d+) (\d*\.?\d*)%""".r
s match {
  case R(str, int, dbl) => (str, int.toInt, dbl.toDouble / 100)
}

If you don't actually know what data is going to be in the String, then there probably isn't much reason to convert it from a String to the type it represents, since how can you use something that might be a String and might be in Int? Still, you could do something like this:

val int = """(\d+)""".r
val pct = """(\d*\.?\d*)%""".r

val res = s.split(" ").map {
  case int(x) => x.toInt
  case pct(x) => x.toDouble / 100
  case str => str
} // Array[Any] = Array(Hello, 69, 0.135)

now to do anything useful you'll need to match on your values by type:

res.map {
  case x: Int => println("It's an Int!")
  case x: Double => println("It's a Double!")
  case x: String => println("It's a String!")
  case _ => println("It's a Fail!")
}

Or if you wanted to take things a bit further, you could define some extractors which will do the conversion for you:

abstract class StringExtractor[A] {
  def conversion(s: String): A
  def unapply(s: String): Option[A] = try { Some(conversion(s)) } 
                                      catch { case _ => None }
}

val intEx = new StringExtractor[Int] { 
  def conversion(s: String) = s.toInt 
}
val pctEx = new StringExtractor[Double] { 
   val pct = """(\d*\.?\d*)%""".r
   def conversion(s: String) = s match { case pct(x) => x.toDouble / 100 } 
}

and use:

"Hello 69 13.5%".split(" ").map {
  case intEx(x) => println(x + " is Int: "    + x.isInstanceOf[Int])
  case pctEx(x) => println(x + " is Double: " + x.isInstanceOf[Double])
  case str      => println(str)
}

prints

Hello
69 is Int: true
0.135 is Double: true

Of course, you can make the extrators match on anything you want (currency mnemonic, name begging with 'J', URL) and return whatever type you want. You're not limited to matching Strings either, if instead of StringExtractor[A] you make it Extractor[A, B].

like image 92
Luigi Plinge Avatar answered Nov 15 '22 20:11

Luigi Plinge


You can read the line as a whole, split it using spaces and then convert each element (or the one you want) to ints:

scala> "1 727".split(" ").map( _.toInt )
res1: Array[Int] = Array(1, 727)

For most complex inputs, you can have a look at parser combinators.

like image 32
paradigmatic Avatar answered Nov 15 '22 19:11

paradigmatic