Como padronizar a correspondência usando expressão regular no Scala?

124

Gostaria de encontrar uma correspondência entre a primeira letra de uma palavra e uma das letras de um grupo como "ABC". No pseudocódigo, isso pode ser algo como:

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

Mas como pego a primeira letra do Scala em vez do Java? Como expresso a expressão regular corretamente? É possível fazer isso dentro de uma classe de caso ?

Bruce Ferguson
fonte
9
Esteja avisado: no Scala (e nos idiomas * ML), a correspondência de padrões tem outro significado, muito diferente das regexes.
1
Você provavelmente deseja [a-cA-C]essa expressão regular.
2
em Scala 2.8, cordas são convertidos para Traversable(como Liste Array), se você quiser os primeiros 3 caracteres, tente "my string".take(3), pela primeira"foo".head
shellholic

Respostas:

237

Você pode fazer isso porque expressões regulares definem extratores, mas precisa primeiro definir o padrão regex. Não tenho acesso a um Scala REPL para testar isso, mas algo assim deve funcionar.

val Pattern = "([a-cA-C])".r
word.firstLetter match {
   case Pattern(c) => c bound to capture group here
   case _ =>
}
asm
fonte
5
tomar cuidado para que você não pode declarar um grupo de captura e, em seguida, não usá-lo (Padrão ie caso () não irá corresponder aqui)
Jeremy Leipzig
34
Lembre-se de que você deve usar grupos em sua expressão regular: val Pattern = "[a-cA-C]".rnão funcionará. Isso ocorre porque os casos de correspondência são usados unapplySeq(target: Any): Option[List[String]], que retornam os grupos correspondentes.
Rakensi
2
É um método em StringLike que retorna um Regex .
As5
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 a versão 2.10, pode-se usar o recurso de interpolação de strings do 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

Melhor ainda, é possível vincular grupos de expressão regular:

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

Também é possível definir mecanismos de ligação mais detalhados:

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

Um exemplo impressionante sobre o que é possível Dynamicé mostrado na postagem do blog Introdução ao Tipo Dinâmico :

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
fonte
Gostei muito da resposta, mas quando tentou usá-lo fora do REPL, ele bloqueou (ou seja, exatamente o mesmo código que funcionava no REPL não funcionava no aplicativo em execução). Também há um problema ao usar o $sinal como um padrão de final de linha: o compilador reclama da falta de terminação de string.
Rajish 11/07/2013
@ Rajish: Não sei o que pode ser o problema. Tudo na minha resposta é um código Scala válido desde a versão 2.10.
Kiritsuku
@sschaef: esse case p.firstName.lastName.Map(...padrão - como diabos eu leio isso?
Erik Kaplun
1
O @ErikAllik o lê como algo como "quando 'firstName' começa com 'Jo' e 'secondName' corresponde ao regex especificado, que a correspondência é bem-sucedida". Este é mais um exemplo do poder do Scalas, eu não escreveria esse caso de uso desta forma no código de produção. Por outro lado, o uso de um mapa deve ser substituído por uma lista, porque um mapa é desordenado e, para mais valores, não é mais garantido que a variável certa corresponde ao correspondente certo.
kiritsuku
1
Isso é muito conveniente para a criação rápida de protótipos, mas observe que isso cria uma nova instância Regexsempre que a correspondência é verificada. E essa é uma operação bastante cara que envolve a compilação do padrão regex.
HRJ 21/04
51

Como delnan apontou, a matchpalavra - chave em Scala não tem nada a ver com expressões regulares. Para descobrir se uma string corresponde a um regex, você pode usar o String.matchesmétodo Para descobrir se uma string começa com a, b ou c em letras minúsculas ou maiúsculas, a regex ficaria assim:

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

Você pode ler essa expressão regular como "um dos caracteres a, b, c, A, B ou C seguido de qualquer coisa" ( .significa "qualquer caractere" e *"zero ou mais vezes", portanto ". *" É qualquer sequência de caracteres) .

sepp2k
fonte
25

Para expandir um pouco a resposta de Andrew : O fato de expressões regulares definirem extratores pode ser usado para decompor muito bem as substrings correspondidas pelo regex usando a correspondência de padrões de Scala, por exemplo:

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
fonte
Estou realmente confuso com o chapéu alto ^. Eu pensei que "^" significava "Corresponder ao início da linha". Não corresponde ao começo da linha.
Michael Lafayette
@ MichaelLafayette: Dentro de uma classe de caracteres ( []), o sinal de intercalação indica negação, então [^\s]significa 'espaço não branco'.
Fabian Steeg
9

String.matches é a maneira de fazer a correspondência de padrões no sentido de expressão regular.

Mas, como um exemplo prático, o word.firstLetter no código Scala real se parece com:

word(0)

Scala trata Strings como uma sequência de Char, portanto, por algum motivo, você deseja obter explicitamente o primeiro caractere do String e combiná-lo, você pode usar algo como isto:

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

Não estou propondo isso como a maneira geral de fazer a correspondência de padrões de regex, mas está alinhado com a abordagem proposta para primeiro encontrar o primeiro caractere de uma String e depois corresponder a um regex.

EDIT: Para ser claro, a maneira que eu faria isso é, como outros já disseram:

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

Só queria mostrar um exemplo o mais próximo possível do seu pseudocódigo inicial. Felicidades!

Janx
fonte
3
"Cat"(0).toStringpoderia ser mais claramente escrito como "Cat" take 1, imho.
David Winslow
Além disso (embora essa seja uma discussão antiga - provavelmente estou cavando covas): você pode remover o '. *' Do final, pois não agrega nenhum valor ao regex. Apenas "Cat" .matches ("^ [a-cA-C]")
akauppi 29/03
Hoje em 2.11 val r = "[A-Ca-c]".r ; "cat"(0) match { case r() => },.
som-snytt
O que significa o hi hat (^)?
Michael Lafayette
É uma âncora que significa 'início da linha' ( cs.duke.edu/csl/docs/unix_course/intro-73.html ). Portanto, tudo o que segue o hi hat corresponderá ao padrão, se for a primeira coisa na linha.
Janx
9

Observe que a abordagem da resposta de @ AndrewMyers corresponde a toda a string à expressão regular, com o efeito de ancorar a expressão regular nas duas extremidades da string usando ^e $. Exemplo:

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

E sem .*no 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 na YugaByte
fonte
1
Linguisticamente val MY_RE2 = "(foo|bar)".r.unanchored ; "foo123" match { case MY_RE2(_*) => }. Mais linguisticamente, val resem todas as letras maiúsculas.
som-snytt
9

Primeiro, devemos saber que a expressão regular pode ser usada separadamente. Aqui está um exemplo:

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

Segundo, devemos observar que combinar expressão regular com correspondência de padrões seria muito poderoso. Aqui está um exemplo simples.

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

De fato, a própria expressão regular já é muito poderosa; a única coisa que precisamos fazer é torná-lo mais poderoso pelo Scala. Aqui estão mais exemplos no Scala Document: http://www.scala-lang.org/files/archive/api/current/index.html#scala.util.matching.Regex

Haimei
fonte