Scala에서 정규 표현식을 사용하여 패턴 일치를 수행하는 방법은 무엇입니까?


124

단어의 첫 글자와 "ABC"와 같은 그룹의 글자 중 하나가 일치하는 항목을 찾고 싶습니다. 의사 코드에서 이것은 다음과 같이 보일 수 있습니다.

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

하지만 Java 대신 Scala에서 첫 글자를 잡는 방법은 무엇입니까? 정규식을 올바르게 표현하려면 어떻게해야합니까? 케이스 클래스 내에서이 작업을 수행 할 수 있습니까?


9
경고 : Scala (및 * ML 언어)에서 패턴 일치에는 정규식과 매우 다른 의미가 있습니다.

1
[a-cA-C]해당 정규식을 원할 것입니다 .

2
scala 2.8에서 문자열은 Traversable( Listand Array) 로 변환됩니다 . 처음 3 개의 문자를 원하면을 시도 "my string".take(3)하십시오"foo".head
shellholic

답변:


237

정규식이 추출기를 정의하기 때문에이를 수행 할 수 있지만 먼저 정규식 패턴을 정의해야합니다. 나는 이것을 테스트하기 위해 Scala REPL에 액세스 할 수 없지만 이와 같은 것이 작동합니다.

val Pattern = "([a-cA-C])".r
word.firstLetter match {
   case Pattern(c) => c bound to capture group here
   case _ =>
}

5
당신이 캡처 그룹을 선언 할 수 없습니다 것을 조심 후 사용하지 (즉, 경우 패턴 () 여기에 일치하지 않습니다)
제레미 라이프 치히

34
정규식에서 그룹 을 사용해야합니다val Pattern = "[a-cA-C]".r . 작동하지 않습니다. 이는 match-case가 unapplySeq(target: Any): Option[List[String]]일치하는 그룹을 반환 하는를 사용하기 때문 입니다.
rakensi

2
Regex 를 반환하는 StringLike 의 메서드입니다 .
asm

11
@rakensi 번호 val r = "[A-Ca-c]".r ; 'a' match { case r() => } . scala-lang.org/api/current/#scala.util.matching.Regex
SOM-snytt

3
@JeremyLeipzig 무시 그룹 : val r = "([A-Ca-c])".r ; "C" match { case r(_*) => }.
som-snytt

120

버전 2.10부터 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

더 좋은 방법은 정규식 그룹을 바인딩 할 수 있습니다.

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

더 자세한 바인딩 메커니즘을 설정할 수도 있습니다.

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

무엇을 사용할 수 있는지에 대한 인상적인 예 Dynamic는 블로그 게시물 Introduction to 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")
  }
}

대답을 매우 좋아했지만 REPL 외부에서 사용하려고하면 잠겼습니다 (즉, REPL에서 작동 한 것과 정확히 동일한 코드가 실행중인 앱에서 작동하지 않음). 또한 $부호를 줄 끝 패턴으로 사용하는 데 문제가 있습니다 . 컴파일러가 문자열 종료가 없다고 불평합니다.
Rajish

@Rajish : 무엇이 문제 일 수 있는지 모릅니다. 내 대답의 모든 것은 2.10 이후 유효한 Scala 코드입니다.
kiritsuku

@sschaef : 그 case p.firstName.lastName.Map(...패턴 — 대체 어떻게 그것을 읽을 수 있습니까?
Erik Kaplun 2014

1
@ErikAllik은 " 'firstName'이 'Jo'로 시작하고 'secondName'이 주어진 정규식과 일치 할 때 일치가 성공한 것보다"와 같이 읽습니다. 이것은 Scalas 파워의 예에 가깝습니다. 저는이 사용 사례를 프로덕션 코드에서 이런 식으로 작성하지 않을 것입니다. Btw, Map의 사용은 List로 대체되어야합니다. Map은 순서가 지정되지 않았고 더 많은 값에 대해 올바른 변수가 올바른 일치 자와 일치하는지 더 이상 보장되지 않기 때문입니다.
kiritsuku

1
이것은 빠른 프로토 타이핑에 매우 편리하지만 Regex일치를 확인할 때마다 새 인스턴스를 생성합니다 . 그리고 이것은 정규식 패턴의 컴파일을 포함하는 비용이 많이 드는 작업입니다.
HRJ

51

delnan이 지적했듯이 matchScala 의 키워드는 정규식 과 관련이 없습니다. 문자열이 정규식과 일치하는지 확인하려면 String.matches메서드를 사용할 수 있습니다 . 문자열이 소문자 또는 대문자로 a, b 또는 c로 시작하는지 확인하려면 정규식은 다음과 같습니다.

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

이 정규식은 "문자 a, b, c, A, B 또는 C 중 하나 다음에 아무 것이나 오는 것"으로 읽을 수 있습니다 ( ."모든 문자"를 *의미하고 "0 회 이상 "을 의미하므로 ". *"는 모든 문자열). .


25

Andrew의 대답을 조금 확장하려면 : 정규식이 추출기를 정의한다는 사실은 Scala의 패턴 일치를 사용하여 정규식과 일치하는 하위 문자열을 매우 잘 분해하는 데 사용할 수 있습니다.

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

나는 높은 모자 ^에 정말로 혼란 스럽습니다. "^"는 "줄의 시작 부분 일치"를 의미했지만. 줄의 시작과 일치하지 않습니다.
Michael Lafayette

@MichaelLafayette : 문자 클래스 ( []) 내에서 캐럿은 부정을 나타내므로 [^\s]'비 공백'을 의미합니다.
Fabian Steeg

9

String.matches는 정규식 의미에서 패턴 일치를 수행하는 방법입니다.

하지만, 실제 Scala 코드의 word.firstLetter는 다음과 같습니다.

word(0)

Scala는 Strings를 Char의 시퀀스로 취급하므로 어떤 이유로 든 String의 첫 번째 문자를 명시 적으로 가져와 일치 시키려면 다음과 같이 사용할 수 있습니다.

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

정규식 패턴 일치를 수행하는 일반적인 방법으로 이것을 제안하지는 않지만 먼저 String의 첫 번째 문자를 찾은 다음 정규식과 일치시키는 제안 된 접근 방식과 일치합니다.

편집 : 명확하게 말하면 다른 사람들이 말했듯이 이것을하는 방법은 다음과 같습니다.

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

가능한 한 초기 의사 코드에 가까운 예제를 보여주고 싶었습니다. 건배!


3
"Cat"(0).toString"Cat" take 1, imho 로 더 명확하게 쓸 수 있습니다 .
David Winslow

또한 (이것은 오래된 토론이지만-아마도 엄청나게 파고들 것입니다) : 정규식에 값을 추가하지 않기 때문에 끝에서 '. *'를 제거 할 수 있습니다. 그냥 "고양이".matches ( "^ [A-CA-C]")
akauppi

2.11 오늘, val r = "[A-Ca-c]".r ; "cat"(0) match { case r() => }.
som-snytt

안녕 모자 (^)는 무엇을 의미합니까?
Michael Lafayette

'줄의 시작'을 의미하는 앵커입니다 ( cs.duke.edu/csl/docs/unix_course/intro-73.html ). 따라서 하이햇 뒤의 모든 것이 라인에서 첫 번째 인 경우 패턴과 일치합니다.
Janx

9

@AndrewMyers의 답변의 접근 방식은 전체 문자열을 정규식 과 일치시키고 ^및을 사용하여 문자열의 양쪽 끝에 정규식을 고정하는 효과가 $있습니다. 예:

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

그리고 .*끝 이 없습니다 .

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

1
관용적으로 val MY_RE2 = "(foo|bar)".r.unanchored ; "foo123" match { case MY_RE2(_*) => }. 더 관용적으로, val re모두 대문자가 없습니다.
som-snytt 2015 년

9

먼저 정규 표현식을 별도로 사용할 수 있음을 알아야합니다. 다음은 그 예입니다.

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

둘째, 정규 표현식과 패턴 매칭을 결합하는 것이 매우 강력하다는 것을 알아야합니다. 다음은 간단한 예입니다.

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

사실 정규 표현식 자체는 이미 매우 강력합니다. 우리가해야 할 유일한 일은 Scala로 더 강력하게 만드는 것입니다. 다음은 Scala 문서의 더 많은 예제입니다. http://www.scala-lang.org/files/archive/api/current/index.html#scala.util.matching.Regex

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.