2011-09-23 24 views
9

Intenté usar readInt() para leer dos enteros de la misma línea pero así no es como funciona.Lectura de múltiples entradas desde la misma línea Scala Way

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

Con una entrada de 1 727 me sale el siguiente excepción en tiempo de ejecución:

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) 

me dieron el programa para trabajar mediante el uso de readf pero parece bastante incómoda y desagradable para mí:

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

Alguien sugirió que solo use la clase de escáner de Java, (Scanner.nextInt()), pero ¿hay una buena manera idiomática de hacerlo en Scala?

Edición: Mi solución siguiente ejemplo paradigmático de:

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

pregunta de seguimiento: Si hubiera una mezcla de tipos en la cadena ¿cómo extraerlo? (Diga una palabra, un int y un porcentaje como un doble)

+1

Esto puede ser útil: http://ikaisays.com/2009/04/04/using-pattern-matching-with-regular-expressions-in-scala/ – Swiss

Respuesta

6

Si quieres decir ¿cómo convertir en un val s = "Hello 69 13.5%"(String, Int, Double) entonces la manera más obvia es

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

O como se ha mencionado que podría coincidir con el uso de una expresión regular:

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

Si no lo hace En realidad, sé qué datos van a estar en la Cadena, entonces probablemente no haya muchas razones para convertirla de una Cadena al tipo que representa, ya que cómo puedes usar algo que podría ser String y podría estar en Int? Aún así, se podría hacer algo como esto:

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) 

ahora a hacer nada útil que tendrá que coincidir en sus valores por tipo:

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

o si quisiera tomar las cosas un poco más allá, se podría definir unos extractores que hacer la conversión para usted:

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

y uso:

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

impresiones

Hello 
69 is Int: true 
0.135 is Double: true 

Por supuesto, usted puede hacer el partido extrators en cualquier cosa que desee (mnemotécnica moneda, nombre pidiendo por 'J', URL) y devolver cualquier tipo que desee. No está limitado a hacer coincidir cadenas, si en lugar de StringExtractor[A] lo hace Extractor[A, B].

+1

¡Se le ocurrió la idea del extractor antes de que terminara de escribir la mía! Sin embargo, impulsé la idea un poco más: espero que ilustren mejor cómo los extractores realmente ayudan. – huynhjl

+0

@huynhjl ¡buen trabajo! Tu versión tiene algunas bonitas mejoras. –

5

Puede leer la línea en su conjunto, que se dividió el uso de espacios y luego convertir cada elemento (o el que desee) a enteros:

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

Para las entradas más complejas, puede echar un vistazo a parser combinators.

+2

Y para las entradas moderadamente complejas puede usar las correspondencias regex. – ziggystar

3

La entrada que está describiendo no es sino dos Entrs una cadenaque sólo pasa a ser dos Ints. Por lo tanto, debe leer la Cadena, dividir por el espacio y convertir las Cadenas individuales en Ints según lo sugerido por @paradigmatic.

+0

En Java puede hacer 'Scanner s = new Scanner();' y luego buscar el siguiente entero de la entrada haciendo 'int a = s.nextInt(); int b = s.nextInt(); '. Supongo que no hay nada similar en la scala stdlib y que en Scala se supone que debes pensar en 'readInt' como' readNextLineAsInt'? – Trevor

+1

Sí, la mayoría de los métodos en el objeto 'Console' leen una línea completa. Pero si no te gusta este enfoque (y luego el que publiqué), puedes apegarte a los escáneres de Java: 'val s = new Scanner; val a = s.nextInt; val b = s.nextInt' también debería funcionar. – paradigmatic

+0

Claro que podría usar el modo Java, pero ¿con qué fuerza debería tratar de evitar el uso de Java al escribir Scala? Mientras estoy aprendiendo?En el código de producción? – Trevor

2

Una forma sería la división y la cartografía:

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

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

O, si usted tiene las cosas un poco más complicado que eso, una expresión regular suele ser lo suficientemente bueno.

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

Hay otras formas de usar expresiones regulares. Vea el Scala API docs sobre ellos.

De todos modos, si los patrones de expresiones regulares se están volviendo demasiado complicados, entonces debe apelar al Scala Parser Combinators. Como puedes combinar ambos, no pierdes la potencia de regex.

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) 

El primer ejemplo fue más simple, pero más difícil de adaptar a patrones más complejos y más vulnerable a entradas no válidas.

2

Me parece que los extractores proporcionan algunas máquinas que hacen que este tipo de procesamiento sea más agradable. Y creo que funciona hasta cierto punto muy bien.

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) 

Ahora Modo de empleo:

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) 

Para utilizar para la lectura de las líneas en la consola:

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 

Lo bueno de los extractores y la coincidencia de patrones es que se puede añadir case cláusulas según sea necesario, puede usar Tokens(a, b, _*) para ignorar algunos tokens. Creo que combinan muy bien (por ejemplo, con literales como lo hice con done).

Cuestiones relacionadas