Jak dopasować wzorzec za pomocą wyrażenia regularnego w Scali?

124

Chciałbym znaleźć dopasowanie między pierwszą literą słowa a jedną z liter w grupie, takiej jak „ABC”. W pseudokodzie może to wyglądać mniej więcej tak:

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

Ale jak mogę pobrać pierwszą literę w Scali zamiast w Javie? Jak poprawnie wyrazić wyrażenie regularne? Czy można to zrobić w ramach klasy sprawy ?

Bruce Ferguson
źródło
9
Uwaga: w językach Scala (i * ML) dopasowywanie wzorców ma inne, bardzo różne od wyrażeń regularnych, znaczenie.
1
Prawdopodobnie chcesz [a-cA-C]tego wyrażenia regularnego.
2
w scali 2.8, łańcuchy są konwertowane na Traversable(jak Listi Array), jeśli chcesz pierwsze 3 znaki, spróbuj "my string".take(3), na pierwszy"foo".head
szelolic

Odpowiedzi:

237

Możesz to zrobić, ponieważ wyrażenia regularne definiują ekstraktory, ale najpierw musisz zdefiniować wzorzec wyrażenia regularnego. Nie mam dostępu do REPL Scala, aby to przetestować, ale coś takiego powinno działać.

val Pattern = "([a-cA-C])".r
word.firstLetter match {
   case Pattern(c) => c bound to capture group here
   case _ =>
}
jako M
źródło
5
pamiętaj, że nie można zadeklarować grupę przechwytywania i nie używać go (czyli przypadek wzoru () nie będzie pasował tutaj)
Jeremy Lipsk
34
Pamiętaj, że musisz używać grup w wyrażeniu regularnym: val Pattern = "[a-cA-C]".rnie zadziała. Dzieje się tak, ponieważ używa przypadków dopasowania unapplySeq(target: Any): Option[List[String]], które zwraca pasujące grupy.
rakensi
2
Jest to metoda na StringLike, która zwraca Regex .
asm
11
@rakensi nr val r = "[A-Ca-c]".r ; 'a' match { case r() => } . scala-lang.org/api/current/#scala.util.matching.Regex
som-snytt
3
@JeremyLeipzig grupy ignorując: val r = "([A-Ca-c])".r ; "C" match { case r(_*) => }.
som-snytt
120

Od wersji 2.10 można korzystać z funkcji interpolacji ciągów Scali:

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

Jeszcze lepiej można wiązać grupy wyrażeń regularnych:

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

Możliwe jest również ustawienie bardziej szczegółowych mechanizmów wiązania:

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

Imponujący przykład tego, co jest możliwe dzięki, Dynamicpokazano w poście na blogu Wprowadzenie do typu dynamicznego :

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
źródło
Bardzo podobała mi się odpowiedź, ale gdy próbowano jej użyć poza REPL, zablokowała się (tj. Dokładnie ten sam kod, który działał w REPL, nie działał w uruchomionej aplikacji). Występuje również problem z użyciem $znaku jako wzorca końca linii: kompilator narzeka na brak zakończenia łańcucha.
Rajish,
@Rajish: Nie wiem, co może być problemem. Wszystko w mojej odpowiedzi to prawidłowy kod Scala od 2.10.
kiritsuku,
@sschaef: ten case p.firstName.lastName.Map(...wzór - jak mam to przeczytać?
Erik Kaplun
1
@ErikAllik odczytuje to jako coś w stylu „kiedy 'firstName' zaczyna się od 'Jo', a 'secondName' pasuje do podanego wyrażenia regularnego, to dopasowanie się powiedzie”. To jest bardziej przykład mocy Scalas, nie napisałbym tego przypadku użycia w przykładzie w ten sposób w kodzie produkcyjnym. Przy okazji, użycie mapy powinno zostać zastąpione listą, ponieważ mapa jest nieuporządkowana i dla większej liczby wartości nie jest już gwarantowane, że właściwa zmienna pasuje do odpowiedniego elementu dopasowującego.
kiritsuku
1
Jest to bardzo wygodne do szybkiego tworzenia prototypów, ale pamiętaj, że tworzy to nową instancję Regexza każdym razem, gdy sprawdzane jest dopasowanie. Jest to dość kosztowna operacja, która wymaga kompilacji wzorca regex.
HRJ
51

Jak zauważył delnan, matchsłowo kluczowe w Scali nie ma nic wspólnego z wyrażeniami regularnymi . Aby dowiedzieć się, czy ciąg pasuje do wyrażenia regularnego, możesz użyć String.matchesmetody. Aby dowiedzieć się, czy ciąg znaków zaczyna się od a, b lub c małymi czy dużymi literami, wyrażenie regularne będzie wyglądać następująco:

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

Możesz odczytać to wyrażenie regularne jako „jeden ze znaków a, b, c, A, B lub C, po którym następuje cokolwiek” ( .oznacza „dowolny znak” i *oznacza „zero lub więcej razy”, więc „. *” To dowolny ciąg) .

sepp2k
źródło
25

Aby rozwinąć nieco odpowiedź Andrew : Fakt, że wyrażenia regularne definiują ekstraktory, można wykorzystać do bardzo ładnego dekompozycji podciągów dopasowanych przez wyrażenie regularne, używając dopasowania wzorców Scali, np .:

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
źródło
Jestem naprawdę zdezorientowany wysokim kapeluszem ^. Pomyślałem, że „^” oznaczało „Dopasuj początek wiersza”. Nie pasuje do początku wiersza.
Michael Lafayette
@MichaelLafayette: Wewnątrz klasy znaków ( []), daszek wskazuje na negację, więc [^\s]oznacza „bez białych znaków”.
Fabian Steeg
9

String.matches to sposób na dopasowanie wzorców w sensie wyrażenia regularnego.

Na marginesie, word.firstLetter w prawdziwym kodzie Scala wygląda tak:

word(0)

Scala traktuje ciągi jako sekwencję znaków Char, więc jeśli z jakiegoś powodu chciałeś jawnie pobrać pierwszy znak ciągu i dopasować go, możesz użyć czegoś takiego:

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

Nie proponuję tego jako ogólnego sposobu dopasowywania wzorców wyrażeń regularnych, ale jest to zgodne z proponowanym przez Ciebie podejściem, aby najpierw znaleźć pierwszy znak ciągu, a następnie dopasować go do wyrażenia regularnego.

EDYCJA: Żeby było jasne, sposób bym to zrobił, jak powiedzieli inni:

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

Chciałem tylko pokazać przykład jak najbliższy początkowemu pseudokodowi. Twoje zdrowie!

Janx
źródło
3
"Cat"(0).toStringmożna by jaśniej zapisać jako "Cat" take 1, imho.
David Winslow,
Ponadto (choć jest to stara dyskusja - prawdopodobnie szukam grobu): możesz usunąć znak „. *” Z końca, ponieważ nie dodaje on żadnej wartości do wyrażenia regularnego. Tylko „Cat” .matches („^ [a-cA-C]”)
akauppi
Dzisiaj w dniu 2.11 val r = "[A-Ca-c]".r ; "cat"(0) match { case r() => }.
som-snytt
Co oznacza hi hat (^)?
Michael Lafayette,
Jest to kotwica oznaczająca „początek wiersza” ( cs.duke.edu/csl/docs/unix_course/intro-73.html ). Więc wszystko, co następuje po hi hat, będzie pasowało do wzoru, jeśli jest to pierwsza rzecz na linii.
Janx
9

Zauważ, że podejście z odpowiedzi @ AndrewMyers dopasowuje cały ciąg do wyrażenia regularnego, czego efektem jest zakotwiczenie wyrażenia regularnego na obu końcach ciągu przy użyciu ^i $. Przykład:

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

I bez .*końca:

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
Michaił w YugaByte
źródło
1
Idiomatically, val MY_RE2 = "(foo|bar)".r.unanchored ; "foo123" match { case MY_RE2(_*) => }. Bardziej idiomatycznie, val rebez wszystkich czapek.
som-snytt
9

Najpierw powinniśmy wiedzieć, że wyrażenie regularne może być używane oddzielnie. Oto przykład:

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

Po drugie, powinniśmy zauważyć, że połączenie wyrażenia regularnego z dopasowywaniem wzorców byłoby bardzo potężne. Oto prosty przykład.

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

W rzeczywistości samo wyrażenie regularne jest już bardzo potężne; Jedyne, co musimy zrobić, to uczynić go potężniejszym za pomocą Scali. Oto więcej przykładów w dokumencie Scala: http://www.scala-lang.org/files/archive/api/current/index.html#scala.util.matching.Regex

Haimei
źródło