Bagaimana cara mencocokkan pola menggunakan ekspresi reguler di Scala?


124

Saya ingin dapat menemukan kecocokan antara huruf pertama dari sebuah kata, dan salah satu huruf dalam grup seperti "ABC". Dalam pseudocode, ini mungkin terlihat seperti:

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

Tapi bagaimana cara mengambil huruf pertama di Scala, bukan di Jawa? Bagaimana cara mengekspresikan ekspresi reguler dengan benar? Apakah mungkin untuk melakukan ini dalam kelas kasus ?


9
Diperingatkan: Dalam bahasa Scala (dan * ML), pencocokan pola memiliki makna lain, sangat berbeda dari regex.

1
Anda mungkin ingin [a-cA-C]untuk ekspresi reguler itu.

2
dalam scala 2.8, string dikonversi menjadi Traversable(seperti Listdan Array), jika Anda menginginkan 3 karakter pertama, coba "my string".take(3), untuk yang pertama"foo".head
shellholic

Jawaban:


237

Anda dapat melakukan ini karena ekspresi reguler menentukan extractors tetapi Anda harus menentukan pola regex terlebih dahulu. Saya tidak memiliki akses ke Scala REPL untuk menguji ini, tetapi sesuatu seperti ini harus bekerja.

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

5
Waspadalah bahwa Anda tidak dapat mendeklarasikan grup penangkap dan kemudian tidak menggunakannya (yaitu Pola kasus () tidak akan cocok di sini)
Jeremy Leipzig

34
Berhati-hatilah bahwa Anda harus menggunakan grup dalam ekspresi reguler Anda: val Pattern = "[a-cA-C]".rtidak akan berfungsi. Ini karena penggunaan case-match unapplySeq(target: Any): Option[List[String]], yang mengembalikan grup yang cocok.
rakensi

2
Ini adalah metode di StringLike yang mengembalikan Regex .
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 kelompok mengabaikan: val r = "([A-Ca-c])".r ; "C" match { case r(_*) => }.
som-snytt

120

Sejak versi 2.10, seseorang dapat menggunakan fitur interpolasi string 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

Bahkan yang lebih baik dapat mengikat grup ekspresi reguler:

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

Dimungkinkan juga untuk mengatur mekanisme pengikatan yang lebih rinci:

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

Contoh yang mengesankan tentang apa yang mungkin dengan Dynamicditampilkan di posting blog Pengantar Jenis Dinamis :

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

Sangat menyukai jawabannya, tetapi ketika mencoba menggunakannya di luar REPL terkunci (yaitu kode yang sama persis yang bekerja di REPL tidak bekerja dalam menjalankan aplikasi). Juga ada masalah dengan menggunakan $tanda sebagai pola garis akhir: kompiler mengeluh tentang kurangnya terminasi string.
Rajish

@ Rajish: Tidak tahu apa yang bisa menjadi masalah. Semua yang ada dalam jawaban saya adalah kode Scala yang valid sejak 2.10.
kiritsuku

@ sschaef: case p.firstName.lastName.Map(...pola itu — bagaimana saya bisa membacanya?
Erik Kaplun

1
@ErikAllik membacanya sebagai sesuatu seperti "ketika 'firstName' dimulai dengan 'Jo' dan 'secondName' cocok dengan regex yang diberikan, daripada pertandingan berhasil". Ini lebih merupakan contoh kekuatan Scalas, saya tidak akan menulis use case ini sebagai contoh dalam kode produksi ini. Btw, penggunaan Peta harus diganti oleh Daftar, karena Peta tidak berurutan dan untuk nilai lebih tidak dijamin lagi bahwa variabel yang tepat cocok dengan pencocokan yang tepat.
kiritsuku

1
Ini sangat mudah untuk Regexpembuatan prototipe cepat, tetapi perhatikan bahwa ini menciptakan contoh baru setiap kali pertandingan diperiksa. Dan itu adalah operasi yang cukup mahal yang melibatkan kompilasi dari pola regex.
HRJ

51

Seperti yang ditunjukkan delnan, matchkata kunci dalam Scala tidak ada hubungannya dengan regex. Untuk mengetahui apakah string cocok dengan regex, Anda dapat menggunakan String.matchesmetode ini. Untuk mengetahui apakah string dimulai dengan a, b atau c dalam huruf kecil atau besar, regex akan terlihat seperti ini:

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

Anda dapat membaca regex ini sebagai "salah satu karakter a, b, c, A, B atau C diikuti oleh apa pun" ( .berarti "karakter apa saja" dan *berarti "nol atau lebih banyak kali", jadi ". *" Adalah string apa pun) .


25

Untuk sedikit memperluas jawaban Andrew : Fakta bahwa ekspresi reguler menentukan ekstraktor dapat digunakan untuk menguraikan substring yang dicocokkan oleh regex dengan sangat baik menggunakan pencocokan pola Scala, misalnya:

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

Saya benar-benar bingung dengan topi tinggi ^. Saya pikir "^" berarti "Cocokkan dengan awal kalimat". Itu tidak cocok dengan awal baris.
Michael Lafayette

@MichaelLafayette: Di dalam kelas karakter ( []), tanda sisipan menunjukkan negasi, jadi [^\s]berarti 'non-spasi putih'.
Fabian Steeg

9

String.matches adalah cara untuk melakukan pencocokan pola dalam arti regex.

Tetapi sebagai tambahan samping, word.firstLetter dalam kode Scala nyata terlihat seperti:

word(0)

Scala memperlakukan Strings sebagai urutan Char's, jadi jika karena alasan tertentu Anda ingin secara eksplisit mendapatkan karakter pertama dari String dan mencocokkannya, Anda dapat menggunakan sesuatu seperti ini:

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

Saya tidak mengusulkan ini sebagai cara umum untuk melakukan pencocokan pola regex, tapi itu sejalan dengan pendekatan yang Anda usulkan untuk pertama kali menemukan karakter pertama dari String dan kemudian mencocokkannya dengan regex.

EDIT: Agar lebih jelas, cara saya akan melakukan ini adalah, seperti yang orang lain katakan:

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

Hanya ingin menunjukkan contoh sedekat mungkin dengan pseudocode awal Anda. Bersulang!


3
"Cat"(0).toStringbisa ditulis lebih jelas sebagai "Cat" take 1, imho.
David Winslow

Juga (meskipun ini adalah diskusi lama - saya mungkin menggali kubur): Anda dapat menghapus '. *' Dari akhir karena tidak menambah nilai pada regex. Just "Cat" .matches ("^ [a-cA-C]")
akauppi

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

Apa arti dari h topi (^)?
Michael Lafayette

Ini adalah jangkar yang berarti 'awal dari garis' ( cs.duke.edu/csl/docs/unix_course/intro-73.html ). Jadi semua yang mengikuti topi akan cocok dengan pola jika itu adalah hal pertama di telepon.
Janx

9

Perhatikan bahwa pendekatan dari jawaban @ AndrewMyers cocok dengan seluruh string ke ekspresi reguler, dengan efek jangkar ekspresi reguler di kedua ujung string menggunakan ^dan $. Contoh:

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

Dan tanpa .*akhir:

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
Secara idiomatis val MY_RE2 = "(foo|bar)".r.unanchored ; "foo123" match { case MY_RE2(_*) => },. Lebih idiomatis, val retanpa topi.
som-snytt

9

Pertama-tama kita harus tahu bahwa ekspresi reguler dapat digunakan secara terpisah. Berikut ini sebuah contoh:

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

Kedua kita harus memperhatikan bahwa menggabungkan ekspresi reguler dengan pencocokan pola akan sangat kuat. Ini adalah contoh sederhana.

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

Bahkan, ekspresi reguler itu sendiri sudah sangat kuat; satu-satunya hal yang perlu kita lakukan adalah membuatnya lebih kuat oleh Scala. Berikut adalah lebih banyak contoh dalam Dokumen Scala: http://www.scala-lang.org/files/archive/api/current/index.html#scala.util.matching.Regex

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.