Làm thế nào để khớp mẫu bằng cách sử dụng biểu thức chính quy trong Scala?


124

Tôi muốn có thể tìm thấy sự trùng khớp giữa chữ cái đầu tiên của một từ và một trong các chữ cái trong một nhóm như "ABC". Trong mã giả, điều này có thể trông giống như:

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

Nhưng làm cách nào để lấy chữ cái đầu tiên trong Scala thay vì Java? Làm thế nào để tôi diễn đạt biểu thức chính quy? Có thể làm điều này trong một lớp trường hợp ?


9
Được cảnh báo: Trong Scala (và * ngôn ngữ ML), khớp mẫu có một kiểu khác, rất khác với biểu thức chính tả.

1
Bạn có thể muốn [a-cA-C]cho biểu hiện thường xuyên.

2
trong scala 2.8, các chuỗi được chuyển đổi thành Traversable(như ListArray), nếu bạn muốn 3 ký tự đầu tiên, hãy thử "my string".take(3), cho lần đầu tiên"foo".head
shellholic

Câu trả lời:


237

Bạn có thể làm điều này bởi vì các biểu thức chính quy định nghĩa các trình trích xuất nhưng trước tiên bạn cần xác định mẫu biểu thức chính quy. Tôi không có quyền truy cập vào Scala REPL để kiểm tra điều này nhưng một cái gì đó như thế này sẽ hoạt động.

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

5
hãy cẩn thận rằng bạn không thể khai báo một nhóm bắt giữ và sau đó không sử dụng nó (ví dụ trường hợp Mẫu () sẽ không khớp ở đây)
Jeremy Leipzig

34
Coi chừng bạn phải sử dụng các nhóm trong biểu thức thông thường của bạn: val Pattern = "[a-cA-C]".rsẽ không hoạt động. Điều này là do sử dụng trường hợp khớp unapplySeq(target: Any): Option[List[String]], trả về các nhóm khớp.
rakensi

2
Đây là một phương thức trên StringLike trả về Regex .
asm

11
@rakensi số val r = "[A-Ca-c]".r ; 'a' match { case r() => } . scala-lang.org/api/c Hiện / # scala.util.matching.Regex
som-snytt

3
@JeremyLeipzig bỏ qua các nhóm : val r = "([A-Ca-c])".r ; "C" match { case r(_*) => }.
som-snytt

120

Kể từ phiên bản 2.10, người ta có thể sử dụng tính năng nội suy chuỗi của 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

Thậm chí tốt hơn người ta có thể liên kết các nhóm biểu thức chính quy:

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

Cũng có thể thiết lập các cơ chế ràng buộc chi tiết hơn:

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

Một ví dụ ấn tượng về những gì có thể với Dynamicđược hiển thị trong bài đăng trên blog Giới thiệu về Kiểu động :

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

Rất thích câu trả lời, nhưng khi thử sử dụng nó bên ngoài REPL thì nó bị khóa (tức là chính xác cùng mã đã hoạt động trong REPL không hoạt động trong ứng dụng đang chạy). Ngoài ra, có một vấn đề với việc sử dụng $dấu làm mẫu kết thúc dòng: trình biên dịch phàn nàn về việc thiếu kết thúc chuỗi.
Rajish

@Rajish: Không biết những gì có thể là vấn đề. Tất cả mọi thứ trong câu trả lời của tôi là mã Scala hợp lệ kể từ 2.10.
kiritsuku

@sschaef: case p.firstName.lastName.Map(...mẫu đó tôi đã đọc nó như thế nào?
Erik Kaplun

1
@ErikAllik đọc nó như một cái gì đó như "khi 'FirstName' bắt đầu bằng 'Jo' và 'secondName' khớp với regex đã cho, hơn là trận đấu thành công". Đây là một ví dụ về sức mạnh của Scalas, tôi sẽ không viết trường hợp sử dụng này theo ví dụ theo cách này trong mã sản xuất. Btw, việc sử dụng Bản đồ nên được thay thế bằng Danh sách, vì Bản đồ không có thứ tự và đối với nhiều giá trị hơn, nó không còn được đảm bảo rằng biến đúng phù hợp với đúng đối sánh.
kiritsuku

1
Điều này rất thuận tiện cho việc tạo mẫu nhanh, nhưng lưu ý rằng điều này tạo ra một trường hợp mới Regexmỗi khi trận đấu được kiểm tra. Và đó là một hoạt động khá tốn kém liên quan đến việc biên dịch mẫu regex.
HRJ

51

Như del Nam đã chỉ ra, matchtừ khóa trong Scala không liên quan gì đến regexes. Để tìm hiểu xem một chuỗi có khớp với regex hay không, bạn có thể sử dụng String.matchesphương thức này. Để tìm hiểu xem một chuỗi bắt đầu bằng a, b hoặc c trong chữ thường hoặc chữ hoa, biểu thức chính quy sẽ như thế này:

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

Bạn có thể đọc regex này là "một trong các ký tự a, b, c, A, B hoặc C theo sau bởi bất cứ thứ gì" ( .có nghĩa là "bất kỳ ký tự nào" và *có nghĩa là "không hoặc nhiều lần", vì vậy ". *" Là bất kỳ chuỗi nào) .


25

Để mở rộng một chút về câu trả lời của Andrew : Thực tế là các biểu thức chính quy định nghĩa các trình trích xuất có thể được sử dụng để phân tách các chuỗi con khớp với biểu thức chính xác bằng cách sử dụng khớp mẫu của Scala, ví dụ:

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

Tôi thực sự bối rối bởi chiếc mũ cao ^. Tôi mặc dù "^" có nghĩa là "Khớp đầu dòng". Nó không phù hợp với đầu dòng.
Michael Lafayette

@MichaelLaf miếng: Bên trong lớp nhân vật ( []), dấu mũ biểu thị phủ định, do đó [^\s]có nghĩa là 'không phải khoảng trắng'.
Fabian Steeg

9

String.matches là cách thực hiện khớp mẫu theo nghĩa regex.

Nhưng như một tiện ích sang một bên, word.firstLetter trong mã Scala thực sự trông giống như:

word(0)

Scala coi Chuỗi là một chuỗi của Char, vì vậy nếu vì lý do nào đó bạn muốn nhận rõ ràng ký tự đầu tiên của Chuỗi và khớp với chuỗi đó, bạn có thể sử dụng một cái gì đó như thế này:

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

Tôi không đề xuất đây là cách chung để thực hiện khớp mẫu regex, nhưng nó phù hợp với cách tiếp cận được đề xuất của bạn để tìm ký tự đầu tiên của Chuỗi và sau đó khớp với ký tự regex.

EDIT: Để rõ ràng, cách tôi sẽ làm điều này là, như những người khác đã nói:

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

Chỉ muốn hiển thị một ví dụ càng gần càng tốt với mã giả ban đầu của bạn. Chúc mừng!


3
"Cat"(0).toStringcó thể được viết rõ ràng hơn như "Cat" take 1, imho.
David Winslow

Ngoài ra (mặc dù đây là một cuộc thảo luận cũ - tôi có thể đào sâu): bạn có thể xóa '. *' Từ cuối vì nó không thêm bất kỳ giá trị nào vào biểu thức chính quy. Chỉ cần "Cat" .matches ("^ [a-cA-C]")
akauppi

Hôm nay ngày 2.11 val r = "[A-Ca-c]".r ; "cat"(0) match { case r() => },.
som-snytt

Mũ hi (^) có nghĩa là gì?
Michael Lafayette

Đó là một mỏ neo có nghĩa là 'bắt đầu của dòng' ( cs.duke.edu/csl/docs/unix_cference/intro-73.html ). Vì vậy, tất cả mọi thứ sau chiếc mũ hi sẽ phù hợp với mô hình nếu đó là điều đầu tiên trên dòng.
Janx

9

Lưu ý rằng cách tiếp cận từ câu trả lời của @ AndrewMyer khớp với toàn bộ chuỗi với biểu thức chính quy, với hiệu ứng neo biểu thức chính quy ở cả hai đầu của chuỗi bằng ^$. Thí dụ:

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

Và không có .*ở cuối:

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
Thành ngữ , val MY_RE2 = "(foo|bar)".r.unanchored ; "foo123" match { case MY_RE2(_*) => }. Thành ngữ hơn, val rekhông có tất cả các mũ.
som-snytt

9

Đầu tiên chúng ta nên biết rằng biểu thức chính quy có thể được sử dụng riêng. Đây là một ví dụ:

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

Thứ hai, chúng ta nên lưu ý rằng việc kết hợp biểu thức chính quy với khớp mẫu sẽ rất mạnh mẽ. Đây là một ví dụ đơn giản.

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

Trong thực tế, bản thân biểu thức chính quy đã rất mạnh mẽ; điều duy nhất chúng ta cần làm là làm cho nó mạnh hơn bởi Scala. Dưới đây là nhiều ví dụ khác trong Tài liệu Scala: http://www.scala-lang.org/files/archive/api/civerse/index.html#scala.util.matching.Regex

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.