Scala'da normal ifade kullanarak eşleştirme kalıbı nasıl yapılır?


124

Bir kelimenin ilk harfi ile "ABC" gibi bir gruptaki harflerden biri arasında bir eşleşme bulabilmek istiyorum. Sözde kodda, bu şöyle görünebilir:

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

Ama Java yerine Scala'da ilk harfi nasıl alabilirim? Normal ifadeyi nasıl doğru bir şekilde ifade ederim? Bunu bir vaka sınıfı içinde yapmak mümkün mü ?


9
Dikkat edin: Scala'da (ve * ML dillerinde), kalıp eşleştirmenin başka, normal ifadelerden çok farklı bir anlamı vardır.

1
Muhtemelen [a-cA-C]bu düzenli ifadeyi istiyorsun .

2
scala 2.8'de, dizeler Traversable( Listve gibi Array) dönüştürülür , ilk 3 karakteri istiyorsanız, deneyin "my string".take(3), ilk için"foo".head
shellholic

Yanıtlar:


237

Bunu yapabilirsiniz çünkü normal ifadeler ayıklayıcıları tanımlar, ancak önce normal ifade desenini tanımlamanız gerekir. Bunu test etmek için Scala REPL'e erişimim yok ama bunun gibi bir şey çalışmalı.

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

5
Bir ele geçirme grubu ilan edip sonra kullanamayacağınıza dikkat edin (yani, Desen () burada eşleşmeyecektir)
Jeremy Leipzig

34
Normal ifadenizde grupları kullanmanız gerektiğine dikkat edin : val Pattern = "[a-cA-C]".rçalışmayacak. Bunun nedeni unapplySeq(target: Any): Option[List[String]], eşleşen grupları döndüren eşleştirme durumu kullanmasıdır .
rakensi

2
StringLike üzerinde bir Regex döndüren bir yöntemdir .
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
Görmezden grupları @JeremyLeipzig: val r = "([A-Ca-c])".r ; "C" match { case r(_*) => }.
som-snytt

120

2.10 sürümünden bu yana, Scala'nın dizgi enterpolasyon özelliği kullanılabilir:

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

Daha da iyisi, düzenli ifade gruplarını bağlayabilir:

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

Daha ayrıntılı bağlanma mekanizmaları ayarlamak da mümkündür:

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

Nelerin mümkün olduğuna dair etkileyici bir örnek, Type Dynamic'e GirişDynamic blog gönderisinde gösterilmektedir :

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

Cevabı çok beğendim, ancak REPL dışında kullanmaya çalıştığında kilitlendi (yani REPL'de çalışan kodun aynısı uygulamada çalışmıyordu). Ayrıca, $işaretin bir satır sonu örüntüsü olarak kullanılmasıyla ilgili bir sorun vardır : derleyici, dizge sonlandırma eksikliğinden şikayet eder.
Rajish

@Rajish: Sorunun ne olabileceğini bilmiyorum. Cevabımdaki her şey 2.10'dan beri geçerli Scala kodudur.
kiritsuku

@sschaef: bu case p.firstName.lastName.Map(...model — bunu nasıl okuyabilirim?
Erik Kaplun

1
@ErikAllik bunu, "'firstName' 'Jo' ile başladığında ve 'secondName' verilen normal ifade ile eşleştiğinde, maç başarılı olduğunda" gibi bir şey olarak okur. Bu daha çok Scalas gücünün bir örneğidir, bu kullanım durumunu örnek olarak bu şekilde üretim kodunda yazmazdım. Btw, bir Haritanın kullanımı bir Liste ile değiştirilmelidir, çünkü bir Harita sırasızdır ve daha fazla değer için doğru değişkenin doğru eşleştirici ile eşleşeceği artık garanti edilmez.
kiritsuku

1
Bu, hızlı prototipleme için çok uygundur, ancak Regexbunun maçın her kontrol edildiğinde yeni bir örnek oluşturduğunu unutmayın . Ve bu, normal ifade deseninin derlenmesini içeren oldukça maliyetli bir işlemdir.
HRJ

51

Delnan'ın belirttiği gibi match, Scala'daki anahtar kelimenin normal ifadelerle ilgisi yoktur. Bir dizenin bir normal ifadeyle eşleşip eşleşmediğini öğrenmek için String.matchesyöntemi kullanabilirsiniz . Bir dizenin küçük veya büyük harfle a, b veya c ile başlayıp başlamadığını bulmak için normal ifade şu şekilde görünür:

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

Bu normal ifadeyi "a, b, c, A, B veya C karakterlerinden biri ve ardından herhangi bir şey" olarak okuyabilirsiniz ( ."herhangi bir karakter" *anlamına gelir ve "sıfır veya daha fazla kez" anlamına gelir, bu nedenle ". *" Herhangi bir dizedir) .


25

Andrew'un cevabını biraz genişletmek gerekirse : Normal ifadelerin çıkarıcıları tanımladığı gerçeği, Scala'nın desen eşleştirmesini kullanarak normal ifadeyle eşleşen alt dizeleri çok güzel bir şekilde ayrıştırmak için kullanılabilir, örneğin:

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

Yüksek şapka ^ ile kafam gerçekten karıştı. "^" "Satırın başıyla eşleş" anlamına geliyor sanmıştım. Satırın başıyla eşleşmiyor.
Michael Lafayette

@MichaelLafayette: Bir karakter sınıfının ( []) içindeki düzeltme işareti olumsuzlamayı gösterir, bu [^\s]nedenle 'boşluk olmayan' anlamına gelir.
Fabian Steeg

9

String.matches, normal ifade anlamında desen eşleştirme yapmanın yoludur.

Ancak kullanışlı bir yana, gerçek Scala kodundaki word.firstLetter şuna benzer:

word(0)

Scala, Dizeleri bir Karakter dizisi olarak ele alır, bu nedenle herhangi bir nedenle String'in ilk karakterini açıkça almak ve eşleştirmek istiyorsanız, bunun gibi bir şey kullanabilirsiniz:

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

Bunu normal ifade kalıbı eşleştirmesi yapmanın genel yolu olarak önermiyorum, ancak ilk önce bir String'in ilk karakterini bulmak ve sonra onu bir normal ifadeyle eşleştirmek önerdiğiniz yaklaşımla uyumludur.

DÜZENLEME: Açık olmak gerekirse, bunu yapmamın yolu, diğerlerinin de söylediği gibi:

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

İlk sözde kodunuza olabildiğince yakın bir örnek göstermek istedim. Şerefe!


3
"Cat"(0).toString"Cat" take 1imho olarak daha açık bir şekilde yazılabilir .
David Winslow

Ayrıca (bu eski bir tartışma olsa da - muhtemelen mezar kazıyorum): normal ifadeye herhangi bir değer eklemediği için '. *' Karakterini sondan kaldırabilirsiniz. Sadece "Kedi". Eşleşmeleri ("^ [a-cA-C]")
akauppi

Bugün 2.11'de val r = "[A-Ca-c]".r ; "cat"(0) match { case r() => }.
som-snytt

Merhaba şapka (^) ne anlama geliyor?
Michael Lafayette

Bu, 'satırın başlangıcı' anlamına gelen bir bağlantıdır ( cs.duke.edu/csl/docs/unix_course/intro-73.html ). Yani hi hattan sonra gelen her şey, satırdaki ilk şeyse kalıba uyacaktır.
2016

9

@ AndrewMyers cevabı dan yaklaşımı eşleştiğini Not tamamı kullanılarak dize her iki uçta düzenli ifade ankraj etkisiyle, düzenli ifade dize ^ve $. Misal:

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

Ve .*sonunda hayır ile:

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
Deyimsel olarak val MY_RE2 = "(foo|bar)".r.unanchored ; "foo123" match { case MY_RE2(_*) => },. Daha deyimsel olarak, val retümü büyük harf olmadan.
som-snytt

9

Öncelikle normal ifadenin ayrı olarak kullanılabileceğini bilmeliyiz. İşte bir örnek:

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

İkinci olarak, normal ifadeyi kalıp eşleştirmeyle birleştirmenin çok güçlü olacağını fark etmeliyiz. İşte basit bir örnek.

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

Aslında, düzenli ifadenin kendisi zaten çok güçlüdür; Yapmamız gereken tek şey onu Scala ile daha güçlü hale getirmek. Scala Belgesinde daha fazla örnek: http://www.scala-lang.org/files/archive/api/current/index.html#scala.util.matching.Regex

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.