จะจับคู่รูปแบบโดยใช้นิพจน์ทั่วไปใน Scala ได้อย่างไร


124

ฉันต้องการจับคู่ระหว่างอักษรตัวแรกของคำกับตัวอักษรตัวใดตัวหนึ่งในกลุ่มเช่น "ABC" ใน pseudocode สิ่งนี้อาจมีลักษณะดังนี้:

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

แต่ฉันจะคว้าตัวอักษรตัวแรกใน Scala แทน Java ได้อย่างไร ฉันจะแสดงออกอย่างถูกต้องได้อย่างไร มันเป็นไปได้ที่จะทำเช่นนี้ภายในชั้นกรณี ?


9
ขอเตือน: ในภาษา Scala (และ * ML) การจับคู่รูปแบบมีความหมายอื่นแตกต่างจากนิพจน์ทั่วไปมาก

1
คุณอาจต้องการ[a-cA-C]สำหรับนิพจน์ทั่วไปนั้น

2
ใน scala 2.8 สตริงจะถูกแปลงเป็นTraversable(like 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
ระวังว่าคุณไม่สามารถประกาศกลุ่มการจับภาพและไม่ได้ใช้ (เช่น case Pattern () จะไม่ตรงกับที่นี่)
Jeremy Leipzig

34
ระวังว่าคุณต้องใช้กลุ่มในนิพจน์ทั่วไปของคุณ: val Pattern = "[a-cA-C]".rจะไม่ทำงาน เนื่องจากการใช้กรณีunapplySeq(target: Any): Option[List[String]]ที่ตรงกันซึ่งจะส่งคืนกลุ่มที่ตรงกัน
rakensi

2
มันเป็นวิธีการในStringLikeซึ่งส่งกลับRegex
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แสดงอยู่ในบล็อกโพสต์บทนำสู่ 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: ไม่รู้ว่าจะมีปัญหาอะไร ทุกอย่างในคำตอบของฉันคือรหัส Scala ที่ถูกต้องตั้งแต่ 2.10
kiritsuku

@sschaef: case p.firstName.lastName.Map(...รูปแบบนั้น- ฉันจะอ่านมันบนโลกนี้ได้อย่างไร?
Erik Kaplun

1
@ErikAllik อ่านว่า "เมื่อ" firstName "ขึ้นต้นด้วย" Jo "และ" secondName "ตรงกับ regex ที่กำหนดกว่าการจับคู่จะสำเร็จ" นี่เป็นอีกตัวอย่างหนึ่งของพลัง Scalas ฉันจะไม่เขียนกรณีการใช้งานนี้เป็นตัวอย่างด้วยวิธีนี้ในรหัสการผลิต Btw การใช้แผนที่ควรถูกแทนที่ด้วยรายการเนื่องจากแผนที่ไม่มีการเรียงลำดับและสำหรับค่าที่มากขึ้นจะไม่รับประกันอีกต่อไปว่าตัวแปรที่ถูกต้องตรงกับตัวจับคู่ที่เหมาะสม
kiritsuku

1
วิธีนี้สะดวกมากสำหรับการสร้างต้นแบบอย่างรวดเร็ว แต่โปรดทราบว่าสิ่งนี้จะสร้างอินสแตนซ์ใหม่ของRegexทุกครั้งที่มีการตรวจสอบการจับคู่ และนั่นเป็นการดำเนินการที่ค่อนข้างแพงซึ่งเกี่ยวข้องกับการรวบรวมรูปแบบนิพจน์ทั่วไป
HRJ

51

ดังที่เดลแนนชี้ให้เห็นmatchคีย์เวิร์ดใน Scala ไม่มีส่วนเกี่ยวข้องกับ regexes หากต้องการทราบว่าสตริงตรงกับนิพจน์ทั่วไปหรือไม่คุณสามารถใช้String.matchesวิธีนี้ หากต้องการทราบว่าสตริงเริ่มต้นด้วย a, b หรือ c ในตัวพิมพ์เล็กหรือใหญ่ regex จะมีลักษณะดังนี้:

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

คุณสามารถอ่านนิพจน์นี้ว่า "หนึ่งในอักขระ a, b, c, A, B หรือ C ตามด้วยอะไรก็ได้" ( .หมายถึง "อักขระใด ๆ " และ*หมายถึง "ศูนย์หรือมากกว่าครั้ง" ดังนั้น ". *" คือสตริงใดก็ได้) .


25

เพื่อขยายความเล็กน้อยเกี่ยวกับคำตอบของ Andrew : ความจริงที่ว่านิพจน์ทั่วไปกำหนดตัวแยกสามารถใช้ในการสลายสตริงย่อยที่จับคู่โดย regex ได้เป็นอย่างดีโดยใช้การจับคู่รูปแบบของ 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 เป็นวิธีการจับคู่รูปแบบในความหมายของนิพจน์ทั่วไป

แต่เพื่อประโยชน์นอกเหนือจากนั้น word.firstLetter ในรหัส Scala จริงดูเหมือนว่า:

word(0)

Scala ถือว่า Strings เป็นลำดับของ Char ดังนั้นหากคุณต้องการรับอักขระตัวแรกของ String และจับคู่ด้วยเหตุผลบางประการคุณสามารถใช้สิ่งต่อไปนี้:

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

ฉันไม่ได้เสนอสิ่งนี้เป็นวิธีทั่วไปในการจับคู่รูปแบบ regex แต่เป็นไปตามแนวทางที่คุณเสนอในการค้นหาอักขระตัวแรกของ String ก่อนจากนั้นจึงจับคู่กับนิพจน์ทั่วไป

แก้ไข: เพื่อความชัดเจนวิธีที่ฉันจะทำก็คืออย่างที่คนอื่นพูด:

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

เพียงแค่ต้องการแสดงตัวอย่างให้ใกล้เคียงที่สุดกับรหัสเทียมเริ่มต้นของคุณ ไชโย!


3
"Cat"(0).toStringสามารถเขียนได้ชัดเจนยิ่งขึ้นว่า"Cat" take 1imho
David Winslow

นอกจากนี้ (แม้ว่านี่จะเป็นการสนทนาเก่า - ฉันอาจจะขุดหลุมฝังศพ): คุณสามารถลบ ". *" ออกจากท้ายเนื่องจากไม่ได้เพิ่มค่าใด ๆ ให้กับนิพจน์ทั่วไป Just "Cat" .matches ("^ [a-cA-C]")
akauppi

วันนี้ val r = "[A-Ca-c]".r ; "cat"(0) match { case r() => }2.11
som-snytt

หมวกสวัสดี (^) หมายถึงอะไร?
Michael Lafayette

เป็นจุดยึดที่หมายถึง "จุดเริ่มต้นของบรรทัด" ( cs.duke.edu/csl/docs/unix_course/intro-73.html ) ดังนั้นทุกสิ่งที่ตามหลังหมวก hi จะตรงกับรูปแบบหากเป็นสิ่งแรกในบรรทัด
59 เวลา 04:52 น

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

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