2011-09-23 46 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)。