Alex Rivera | Logout

Reading Multiple Inputs from the Same Line the Scala Way

Asked 2011-09-23T07:04:18.683
9

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)

Edit
Report

1 Answer

2

I find that extractors provide some machinery that makes this type of processing nicer. And I think it works up to a certain point nicely.

object Tokens {
  def unapplySeq(line: String): Option[Seq[String]] = 
    Some(line.split("\\s+").toSeq)
}

class RegexToken[T](pattern: String, convert: (String) => T) {
  val pat = pattern.r
  def unapply(token: String): Option[T] = token match {
    case pat(s) => Some(convert(s))
    case _ => None
  }
}

object IntInput extends RegexToken[Int]("^([0-9]+)$", _.toInt)

object Word extends RegexToken[String]("^([A-Za-z]+)$", identity)

object Percent extends RegexToken[Double](
  """^([0-9]+\.?[0-9]*)%$""", _.toDouble / 100)

Now how to use:

List("1 727", "uptime 365 99.999%") collect {
  case Tokens(IntInput(x), IntInput(y)) => "sum " + (x + y)
  case Tokens(Word(w), IntInput(i), Percent(p)) => w + " " + (i * p)
}
// List[java.lang.String] = List(sum 728, uptime 364.99634999999995)

To use for reading lines at the console:

Iterator.continually(readLine("prompt> ")).collect{
  case Tokens(IntInput(x), IntInput(y)) => "sum " + (x + y)
  case Tokens(Word(w), IntInput(i), Percent(p)) => w + " " + (i * p)
  case Tokens(Word("done"))  => "done"
}.takeWhile(_ != "done").foreach(println)
// type any input and enter, type "done" and enter to finish

The nice thing about extractors and pattern matching is that you can add case clauses as necessary, you can use Tokens(a, b, _*) to ignore some tokens. I think they combine together nicely (for instance with literals as I did with done).

answered 2011-09-23T14:19:20.543

Your Answer