Чтение нескольких входов с одной и той же строки по пути Scala - PullRequest
9 голосов
/ 23 сентября 2011

Я пытался использовать 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)

Дополнительный вопрос: Еслив String было несколько типов, как бы вы его извлекли?(Произнесите слово, int и процент в двойном размере)

Ответы [ 5 ]

6 голосов
/ 23 сентября 2011

Если вы имеете в виду, как бы вы конвертировали 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

Конечно, вы можете сделать так, чтобы экстракторы соответствовали всем, что вы хотите (мнемоника валюты, переименование с помощью 'J', URL) и возвращать любой тип, который вы хотите. Вы также не ограничены соответствующими строками, если вместо StringExtractor[A] вы сделаете это Extractor[A, B].

5 голосов
/ 23 сентября 2011

Вы можете прочитать строку целиком, разделить ее пробелами, а затем преобразовать каждый элемент (или тот, который вам нужен) в целые числа:

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

Для большинства сложных входных данных вы можете посмотретьна парсер комбинаторы .

3 голосов
/ 23 сентября 2011

Описываемый вами ввод - это не два Ints, а a String , который просто равен two Ints.Следовательно, вам нужно прочитать строку, разделив ее пробелом, и преобразовать отдельные строки в целые, как предложено @paradigmatic.

2 голосов
/ 23 сентября 2011

Я считаю, что экстракторы предоставляют некоторые механизмы, которые делают этот тип обработки приятнее. И я думаю, что это хорошо работает до определенного момента.

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

2 голосов
/ 23 сентября 2011

Одним из способов было бы разбиение и отображение:

// 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 документы о них.

В любом случае, если шаблоны регулярных выражений становятся слишком сложными, вам следует обратиться к 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)

Первый пример был более простым, но более сложным для адаптации к более сложным шаблонам и более уязвимым для неверного ввода.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...