Scalaで正規表現を使用してパターンマッチングを行う方法


124

単語の最初の文字と、「ABC」などのグループ内の文字の1つとの一致を検索できるようにしたいと考えています。擬似コードでは、これは次のようになります。

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(のようにListして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
キャプチャグループを宣言してから使用できないことに注意してください(つまり、Pattern()はここでは一致しません)
Jeremy Leipzig

34
正規表現でグループを使用する必要があることに注意してくださいval Pattern = "[a-cA-C]".r。機能しません。これは、match-caseがを使用してunapplySeq(target: Any): Option[List[String]]、一致するグループを返すためです。
rakensi 2013

2
これは、Regexを返すStringLikeのメソッドです。
2014

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

3
グループを無視して@JeremyLeipzig: val r = "([A-Ca-c])".r ; "C" match { case r(_*) => }
som-snytt 2015年

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で機能していたのとまったく同じコードが実行中のアプリで機能しませんでした)。また、$記号を行末パターンとして使用することにも問題があります。コンパイラは文字列の終了がないことを報告します。
ラジッシュ2013

@ラジッシュ:何が問題なのかわからない。2.10以降、私の答えのすべてが有効なScalaコードです。
キリツク2013

@sschaef:そのcase p.firstName.lastName.Map(...パターン-一体どうやってそれを読むのですか?
エリックカプルン2014

1
@ErikAllikはそれを「 'firstName'が 'Jo'で始まり、 'secondName'が指定された正規表現と一致する場合、一致が成功した場合」のように読みます。これは、Scalasのパワーの例です。このユースケースを、この方法でプロダクションコードで作成することはありません。ところで、Mapは順序付けされていないため、Mapの使用法はListに置き換える必要があります。より多くの値については、適切な変数が適切なマッチャーに一致することが保証されなくなります。
キリツク

1
これは迅速なプロトタイピングには非常に便利ですがRegex、一致がチェックされるたびにの新しいインスタンスが作成されることに注意してください。そして、それは正規表現パターンのコンパイルを含む非常にコストのかかる操作です。
HRJ 2015

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

ハイハットに本当に戸惑います^。「^」は「行頭に合わせる」という意味でしたが。行の先頭と一致していません。
マイケルラファイエット

@MichaelLafayette:文字クラス([])内では、キャレットは否定を示すため、[^\s]「非空白文字」を意味します。
Fabian Steeg

9

String.matchesは、正規表現の意味でパターンマッチングを行う方法です。

しかし、おかしな話として、実際のScalaコードのword.firstLetterは次のようになります。

word(0)

Scalaは文字列をCharのシーケンスとして扱います。そのため、何らかの理由で文字列の最初の文字を明示的に取得して一致させたい場合は、次のようなものを使用できます。

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

これを正規表現のパターンマッチングを行う一般的な方法として提案することはしませんが、最初に文字列の最初の文字を見つけ、それを正規表現と照合するという提案されたアプローチと一致しています。

編集:明確にするために、他の人が言ったように、私がこれを行う方法は次のとおりです:

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

最初の疑似コードにできるだけ近い例を示したかっただけです。乾杯!


3
"Cat"(0).toString"Cat" take 1imho としてより明確に書くことができます。
David Winslow

また、これは古い議論ですが、私はおそらく重大な問題を抱えています):正規表現に値を追加しないため、末尾から「。*」を削除できます。Just "Cat" .matches( "^ [a-cA-C]")
akauppi

今日2.11にval r = "[A-Ca-c]".r ; "cat"(0) match { case r() => }
som-snytt 2015年

ハイハット(^)の意味?
マイケルラファイエット

これは、「ラインの始まり」を意味するアンカーです(cs.duke.edu/csl/docs/unix_course/intro-73.html)。したがって、ハイハットに続くすべてのものは、それが行の最初のものである場合、パターンに一致します。
Janx

9

@AndrewMyersの回答からのアプローチは文字列全体を正規表現と照合し、^and を使用して文字列の両端に正規表現を固定する効果があることに注意してください$。例:

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.