2011-09-23 81 views
9

我試圖用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和一個百分比作爲雙人間)

+1

這可能有幫助:http://ikaisays.com/2009/04/04/using-pattern-matching-with-regular-expressions-in-scala/ – Swiss

回答

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的東西,並且可能在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 

當然,你可以讓你想要任何東西的extrators比賽(貨幣助記符,名稱以「J」,網址乞討),並返回任何你想要的類型。你並不侷限於匹配字符串,如果不是StringExtractor[A],你可以將其設爲Extractor[A, B]

+1

在完成我的寫作之前,您提出了提取器的想法!我將這個想法推進了一點 - 希望能更好地說明提取器真正的幫助。 – huynhjl

+0

@huynhjl幹得好!你的版本有一些很好的改進。 –

5

可以讀取線作爲一個整體,分開使用空格,然後再轉換各單元(或者你想要的),以整數它:

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

對於大多數複雜的輸入,你可以看看parser combinators

+2

而對於中度複雜的輸入,你可以使用正則表達式匹配器。 – ziggystar

3

你所描述的輸入是不是兩個整數,但一個String這恰好是兩個整數。因此,您需要讀取字符串,按空格拆分並將各個字符串轉換爲Ints,如@paradigmatic所示。

+0

在Java中,您可以執行'Scanner s = new Scanner();',然後通過執行int a = s.nextInt()來從輸入中掃描下一個整數。 int b = s.nextInt();'。我假設在scala stdlib中沒有類似的東西,而在Scala中,你應該把'readInt'看作'readNextLineAsInt'? – Trevor

+1

是的'Console'對象中的大多數方法都會讀取整行。但是如果你不喜歡這種方法(然後是我發佈的方法),那麼你可以堅持使用Java掃描器:'val s = new Scanner; val a = s.nextInt; val b = s.nextInt'也應該有效。 – paradigmatic

+0

當然我可以使用Java方式,但是在編寫Scala時應該如何避免使用Java的東西?在我學習的時候?在生產代碼? – Trevor

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) 

第一個例子更簡單,但難以適應更復雜的模式,更容易受到無效輸入的影響。

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)。