¿Cómo emparejar patrones usando expresiones regulares en Scala?

124

Me gustaría poder encontrar una coincidencia entre la primera letra de una palabra y una de las letras de un grupo como "ABC". En pseudocódigo, esto podría parecerse a algo como:

case Process(word) =>
   word.firstLetter match {
      case([a-c][A-C]) =>
      case _ =>
   }
}

Pero, ¿cómo tomo la primera letra en Scala en lugar de Java? ¿Cómo expreso la expresión regular correctamente? ¿Es posible hacer esto dentro de una clase de caso ?

Bruce Ferguson
fuente
9
Tenga cuidado: en Scala (y * idiomas ML), la coincidencia de patrones tiene otro significado muy diferente de las expresiones regulares.
1
Probablemente quieras [a-cA-C]para esa expresión regular.
2
en scala 2.8, las cadenas se convierten a Traversable(me gusta Listy Array), si desea los primeros 3 caracteres, intente "my string".take(3), para el primero"foo".head
shellholic

Respuestas:

237

Puede hacerlo porque las expresiones regulares definen extractores, pero primero debe definir el patrón de expresiones regulares. No tengo acceso a un REPL de Scala para probar esto, pero algo como esto debería funcionar.

val Pattern = "([a-cA-C])".r
word.firstLetter match {
   case Pattern(c) => c bound to capture group here
   case _ =>
}
asm
fuente
55
tenga en cuenta que no puede declarar un grupo de captura y luego no usarlo (es decir, el patrón de caso () no coincidirá aquí)
Jeremy Leipzig
34
Tenga en cuenta que debe usar grupos en su expresión regular: val Pattern = "[a-cA-C]".rno funcionará. Esto se debe a que utiliza casos de coincidencia unapplySeq(target: Any): Option[List[String]], que devuelve los grupos coincidentes.
rakensi
2
Es un método en StringLike que devuelve una expresión regular .
asm
11
@rakensi No. val r = "[A-Ca-c]".r ; 'a' match { case r() => } . scala-lang.org/api/current/#scala.util.matching.Regex
som-snytt
3
@JeremyLeipzig grupos ignorando: val r = "([A-Ca-c])".r ; "C" match { case r(_*) => }.
som-snytt
120

Desde la versión 2.10, se puede usar la función de interpolación de cadenas de Scala:

implicit class RegexOps(sc: StringContext) {
  def r = new util.matching.Regex(sc.parts.mkString, sc.parts.tail.map(_ => "x"): _*)
}

scala> "123" match { case r"\d+" => true case _ => false }
res34: Boolean = true

Aún mejor, uno puede unir grupos de expresiones regulares:

scala> "123" match { case r"(\d+)$d" => d.toInt case _ => 0 }
res36: Int = 123

scala> "10+15" match { case r"(\d\d)${first}\+(\d\d)${second}" => first.toInt+second.toInt case _ => 0 }
res38: Int = 25

También es posible establecer mecanismos de enlace más detallados:

scala> object Doubler { def unapply(s: String) = Some(s.toInt*2) }
defined module Doubler

scala> "10" match { case r"(\d\d)${Doubler(d)}" => d case _ => 0 }
res40: Int = 20

scala> object isPositive { def unapply(s: String) = s.toInt >= 0 }
defined module isPositive

scala> "10" match { case r"(\d\d)${d @ isPositive()}" => d.toInt case _ => 0 }
res56: Int = 10

Un ejemplo impresionante de lo que es posible Dynamicse muestra en la publicación del blog Introducción a Type Dynamic :

object T {

  class RegexpExtractor(params: List[String]) {
    def unapplySeq(str: String) =
      params.headOption flatMap (_.r unapplySeq str)
  }

  class StartsWithExtractor(params: List[String]) {
    def unapply(str: String) =
      params.headOption filter (str startsWith _) map (_ => str)
  }

  class MapExtractor(keys: List[String]) {
    def unapplySeq[T](map: Map[String, T]) =
      Some(keys.map(map get _))
  }

  import scala.language.dynamics

  class ExtractorParams(params: List[String]) extends Dynamic {
    val Map = new MapExtractor(params)
    val StartsWith = new StartsWithExtractor(params)
    val Regexp = new RegexpExtractor(params)

    def selectDynamic(name: String) =
      new ExtractorParams(params :+ name)
  }

  object p extends ExtractorParams(Nil)

  Map("firstName" -> "John", "lastName" -> "Doe") match {
    case p.firstName.lastName.Map(
          Some(p.Jo.StartsWith(fn)),
          Some(p.`.*(\\w)$`.Regexp(lastChar))) =>
      println(s"Match! $fn ...$lastChar")
    case _ => println("nope")
  }
}
kiritsuku
fuente
Me gustó mucho la respuesta, pero cuando traté de usarla fuera de REPL, se bloqueó (es decir, exactamente el mismo código que funcionó en REPL no funcionó al ejecutar la aplicación). También hay un problema con el uso del $signo como patrón de final de línea: el compilador se queja de la falta de terminación de cadena.
Rajish
@Rajish: No sé cuál puede ser el problema. Todo en mi respuesta es un código Scala válido desde 2.10.
kiritsuku
@sschaef: ese case p.firstName.lastName.Map(...patrón, ¿cómo puedo leer eso?
Erik Kaplun
1
@ErikAllik lo lee como algo así como "cuando 'firstName' comienza con 'Jo' y 'secondName' coincide con la expresión regular dada, entonces la coincidencia es exitosa". Este es más un ejemplo del poder Scalas, no escribiría este caso de uso, por ejemplo, de esta manera en el código de producción. Por cierto, el uso de un Mapa debe ser reemplazado por una Lista, porque un Mapa no está ordenado y para más valores ya no se garantiza que la variable correcta coincida con el emparejador correcto.
Kiritsuku
1
Esto es muy conveniente para la creación rápida de prototipos, pero tenga en cuenta que esto crea una nueva instancia Regexcada vez que se verifica la coincidencia. Y esa es una operación bastante costosa que implica la compilación del patrón regex.
HRJ
51

Como señaló Delnan, la matchpalabra clave en Scala no tiene nada que ver con expresiones regulares. Para averiguar si una cadena coincide con una expresión regular, puede usar el String.matchesmétodo. Para saber si una cadena comienza con a, b o c en minúsculas o mayúsculas, la expresión regular se vería así:

word.matches("[a-cA-C].*")

Puede leer esta expresión regular como "uno de los caracteres a, b, c, A, B o C seguido de cualquier cosa" ( .significa "cualquier carácter" y *significa "cero o más veces", por lo que ". *" Es cualquier cadena) .

sepp2k
fuente
25

Para ampliar un poco la respuesta de Andrew : El hecho de que las expresiones regulares definan extractores se puede usar para descomponer muy bien las subcadenas emparejadas por la expresión regular utilizando la coincidencia de patrones de Scala, por ejemplo:

val Process = """([a-cA-C])([^\s]+)""".r // define first, rest is non-space
for (p <- Process findAllIn "aha bah Cah dah") p match {
  case Process("b", _) => println("first: 'a', some rest")
  case Process(_, rest) => println("some first, rest: " + rest)
  // etc.
}
Fabian Steeg
fuente
Estoy realmente confundido por el sombrero de copa ^. Pensé que "^" significaba "Coincidir con el comienzo de la línea". No coincide con el comienzo de la línea.
Michael Lafayette
@MichaelLafayette: dentro de una clase de caracteres ( []), el símbolo de intercalación indica negación, por lo que [^\s]significa "sin espacios en blanco".
Fabian Steeg
9

String.matches es la forma de hacer coincidir patrones en el sentido de expresiones regulares.

Pero como un práctico a un lado, word.firstLetter en código Scala real se ve así:

word(0)

Scala trata las cadenas como una secuencia de Char, por lo que si por alguna razón desea obtener explícitamente el primer carácter de la cadena y combinarlo, puede usar algo como esto:

"Cat"(0).toString.matches("[a-cA-C]")
res10: Boolean = true

No estoy proponiendo esto como la forma general de hacer una coincidencia de patrón de expresiones regulares, pero está en línea con su enfoque propuesto para encontrar primero el primer carácter de una Cadena y luego compararlo con una expresión regular.

EDITAR: Para ser claros, la forma en que haría esto es, como otros han dicho:

"Cat".matches("^[a-cA-C].*")
res14: Boolean = true

Solo quería mostrar un ejemplo lo más cerca posible de su pseudocódigo inicial. ¡Salud!

Janx
fuente
3
"Cat"(0).toStringpodría escribirse más claramente como "Cat" take 1, en mi humilde opinión.
David Winslow
Además (aunque esta es una discusión antigua, probablemente estoy cavando tumbas): puede eliminar el '. *' Del final ya que no agrega ningún valor a la expresión regular. Solo "Cat" .matches ("^ [a-cA-C]")
akauppi
Hoy en 2.11, val r = "[A-Ca-c]".r ; "cat"(0) match { case r() => }.
som-snytt
¿Qué significa el hi hat (^)?
Michael Lafayette
Es un ancla que significa "inicio de la línea" ( cs.duke.edu/csl/docs/unix_course/intro-73.html ). Entonces, todo lo que sigue al hi hat coincidirá con el patrón si es lo primero en la línea.
Janx
9

Tenga en cuenta que el enfoque de la respuesta de @ AndrewMyers hace coincidir la cadena completa con la expresión regular, con el efecto de anclar la expresión regular en ambos extremos de la cadena usando ^y $. Ejemplo:

scala> val MY_RE = "(foo|bar).*".r
MY_RE: scala.util.matching.Regex = (foo|bar).*

scala> val result = "foo123" match { case MY_RE(m) => m; case _ => "No match" }
result: String = foo

scala> val result = "baz123" match { case MY_RE(m) => m; case _ => "No match" }
result: String = No match

scala> val result = "abcfoo123" match { case MY_RE(m) => m; case _ => "No match" }
result: String = No match

Y sin un .*al final:

scala> val MY_RE2 = "(foo|bar)".r
MY_RE2: scala.util.matching.Regex = (foo|bar)

scala> val result = "foo123" match { case MY_RE2(m) => m; case _ => "No match" }
result: String = No match
Mikhail en YugaByte
fuente
1
Idiomáticamente, val MY_RE2 = "(foo|bar)".r.unanchored ; "foo123" match { case MY_RE2(_*) => }. Más idiomáticamente, val resin mayúsculas.
som-snytt
9

Primero debemos saber que la expresión regular se puede usar por separado. Aquí hay un ejemplo:

import scala.util.matching.Regex
val pattern = "Scala".r // <=> val pattern = new Regex("Scala")
val str = "Scala is very cool"
val result = pattern findFirstIn str
result match {
  case Some(v) => println(v)
  case _ =>
} // output: Scala

En segundo lugar, debemos notar que combinar la expresión regular con la coincidencia de patrones sería muy poderoso. Aquí hay un ejemplo simple.

val date = """(\d\d\d\d)-(\d\d)-(\d\d)""".r
"2014-11-20" match {
  case date(year, month, day) => "hello"
} // output: hello

De hecho, la expresión regular en sí misma ya es muy poderosa; lo único que debemos hacer es hacerlo más poderoso con Scala. Aquí hay más ejemplos en el documento Scala: http://www.scala-lang.org/files/archive/api/current/index.html#scala.util.matching.Regex

Haimei
fuente