从Scala方式读取同一行的多个输入

时间:2011-09-23 07:04:19

标签: string parsing scala input

我尝试使用readInt()从同一行读取两个整数,但这不是它的工作原理。

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

输入1 727我在运行时收到以下异常:

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)

我使用readf让程序工作,但对我来说这似乎很尴尬和难看:

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

有人建议我只使用Java的Scanner类(Scanner.nextInt()),但在Scala中有一个很好的惯用方法吗?

修改的: 我的解决方案遵循范式的例子:

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

后续问题:如果字符串中有多种类型,您将如何提取它? (说一个字,一个int和一个百分比作为Double)

5 个答案:

答案 0 :(得分:6)

如果您的意思是如何将val s = "Hello 69 13.5%"转换为(String, Int, Double),那么最明显的方式是

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

或者如上所述,您可以使用正则表达式进行匹配:

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

如果你实际上不知道String中会有什么数据,那么可能没有太多理由将它从String转换为它所代表的类型,因为你如何使用可能是String可能在Int?不过,你可以这样做:

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)

现在要做任何有用的事情,你需要按类型匹配你的值:

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!")
}

或者如果你想更进一步,你可以定义一些将为你做转换的提取器:

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 } 
}

并使用:

"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)
}

打印

Hello
69 is Int: true
0.135 is Double: true

当然,您可以让分隔符匹配您想要的任何内容(货币助记符,名称用'J',URL),并返回您想要的任何类型。您也不仅限于匹配字符串,而不是StringExtractor[A]而是使用Extractor[A, B]

答案 1 :(得分:5)

您可以将整行读取,使用空格拆分,然后将每个元素(或您想要的元素)转换为整数:

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

对于大多数复杂的输入,您可以查看parser combinators

答案 2 :(得分:3)

您描述的输入不是两个Ints,而是一个字符串 恰好是两个Ints。因此,您需要读取字符串,按空格分割并按照 @paradigmatic 的建议将各个字符串转换为Ints。

答案 3 :(得分:2)

一种方法是拆分和映射:

// Assuming whatever is being read is assigned to "input"
val input = "1 727"

val Array(x, y) = input split " " map (_.toInt)

或者,如果你的事情比这更复杂,那么正则表达式通常就足够了。

val twoInts = """^\s*(\d+)\s*(\d+)""".r
val Some((x, y)) = for (twoInts(a, b) <- twoInts findFirstIn input) yield (a, b)

还有其他方法可以使用正则表达式。请参阅Scala API docs了解相关信息。

无论如何,如果正则表达式模式变得过于复杂,那么你应该诉诸Scala Parser Combinators。既然你可以将两者结合起来,那么你就不会失去任何正则表达式的力量。

import scala.util.parsing.combinator._

object MyParser extends JavaTokenParsers {
    def twoInts = wholeNumber ~ wholeNumber ^^ { case a ~ b => (a.toInt, b.toInt) }
}

val MyParser.Success((x, y), _) = MyParser.parse(MyParser.twoInts, input)

第一个例子更简单,但更难适应更复杂的模式,更容易受到无效输入的影响。

答案 4 :(得分:2)

我发现提取器提供了一些使这种处理更好的机器。而且我认为它很好地适用于某一点。

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)

现在该如何使用:

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)

用于在控制台上阅读行:

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

提取器和模式匹配的好处是你可以根据需要添加case子句,你可以使用Tokens(a, b, _*)来忽略一些令牌。我认为它们很好地结合在一起(例如我和done一样的文字。)