从同一行读取多个输入斯卡拉方式
我试图用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和一个百分比作为双人间)
如果你的意思是你会如何转换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]
。
在完成我的写作之前,您提出了提取器的想法!我将这个想法推进了一点 - 希望能更好地说明提取器真正的帮助。 – huynhjl
@huynhjl干得好!你的版本有一些很好的改进。 –
可以读取线作为一个整体,分开使用空格,然后再转换各单元(或者你想要的),以整数它:
scala> "1 727".split(" ").map(_.toInt)
res1: Array[Int] = Array(1, 727)
对于大多数复杂的输入,你可以看看parser combinators。
而对于中度复杂的输入,你可以使用正则表达式匹配器。 – ziggystar
你所描述的输入是不是两个整数,但一个String这恰好是两个整数。因此,您需要读取字符串,按空格拆分并将各个字符串转换为Ints,如@paradigmatic所示。
在Java中,您可以执行'Scanner s = new Scanner();',然后通过执行int a = s.nextInt()来从输入中扫描下一个整数。 int b = s.nextInt();'。我假设在scala stdlib中没有类似的东西,而在Scala中,你应该把'readInt'看作'readNextLineAsInt'? – Trevor
是的'Console'对象中的大多数方法都会读取整行。但是如果你不喜欢这种方法(然后是我发布的方法),那么你可以坚持使用Java扫描器:'val s = new Scanner; val a = s.nextInt; val b = s.nextInt'也应该有效。 – paradigmatic
当然我可以使用Java方式,但是在编写Scala时应该如何避免使用Java的东西?在我学习的时候?在生产代码? – Trevor
一种方法是分裂和映射:
// 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)
第一个例子更简单,但难以适应更复杂的模式,更容易受到无效输入的影响。
我发现提取器提供了一些使这种处理更好的机器。我认为它可以很好地工作到某一点。
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
)。
这可能有帮助:http://ikaisays.com/2009/04/04/using-pattern-matching-with-regular-expressions-in-scala/ – Swiss