Как сопоставить шаблон с использованием регулярного выражения в Scala?

124

Я хотел бы найти совпадение между первой буквой слова и одной из букв в группе, такой как «ABC». В псевдокоде это может выглядеть примерно так:

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

Но как мне взять первую букву в Scala вместо Java? Как правильно выразить регулярное выражение? Можно ли это сделать в классе case ?

Брюс Фергюсон
источник
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
будьте осторожны, вы не можете объявить группу захвата, а затем не использовать ее (т.е. case Pattern () здесь не будет совпадать)
Джереми Лейпциг,
34
Помните, что вы должны использовать группы в своем регулярном выражении: val Pattern = "[a-cA-C]".rне будет работать. Это связано с тем unapplySeq(target: Any): Option[List[String]], что используется case-case , который возвращает совпадающие группы.
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 09
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, показан в сообщении блога 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")
  }
}
kiritsuku
источник
Ответ очень понравился, но при попытке использовать его вне REPL он заблокировался (т.е. точно такой же код, который работал в REPL, не работал в запущенном приложении). Также существует проблема с использованием $знака в качестве образца конца строки: компилятор жалуется на отсутствие завершения строки.
Rajish
@Rajish: Не знаю, в чем может быть проблема. Все в моем ответе является действительным кодом Scala с 2.10.
kiritsuku
@sschaef: этот case p.firstName.lastName.Map(...шаблон - как я могу это прочитать?
Эрик Каплун
1
@ErikAllik прочитал это как что-то вроде «когда 'firstName' начинается с 'Jo' и 'secondName' соответствует заданному регулярному выражению, то совпадение является успешным». Это скорее пример мощи Scalas, я бы не стал писать этот пример использования таким образом в производственном коде. Кстати, использование Map следует заменить на List, потому что Map неупорядочен и для большего количества значений больше не гарантируется, что правильная переменная соответствует правильному сопоставителю.
kiritsuku
1
Это очень удобно для быстрого прототипирования, но обратите внимание, что это создает новый экземпляр Regexкаждый раз, когда проверяется соответствие. И это довольно дорогостоящая операция, связанная с компиляцией шаблона регулярного выражения.
HRJ
51

Как указал Делнан, matchключевое слово в Scala не имеет ничего общего с регулярными выражениями. Чтобы узнать, соответствует ли строка регулярному выражению, вы можете использовать String.matchesметод. Чтобы узнать, начинается ли строка с a, b или c в нижнем или верхнем регистре, регулярное выражение будет выглядеть так:

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

Вы можете прочитать это регулярное выражение как «один из символов a, b, c, A, B или C, за которым следует что-нибудь» ( .означает «любой символ» и *означает «ноль или более раз», поэтому «. *» - это любая строка) ,

sepp2k
источник
25

Чтобы немного расширить ответ Эндрю : тот факт, что регулярные выражения определяют экстракторы, можно использовать для разложения подстрок, сопоставленных регулярным выражением, очень красиво, используя сопоставление с образцом 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 - это способ сопоставления с образцом в смысле регулярного выражения.

Но как удобный случай word.firstLetter в реальном коде Scala выглядит так:

word(0)

Scala обрабатывает строки как последовательность символов Char, поэтому, если по какой-то причине вы хотите явно получить первый символ строки и сопоставить его, вы можете использовать что-то вроде этого:

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

Я не предлагаю это как общий способ сопоставления с образцом регулярного выражения, но он соответствует предложенному вами подходу: сначала найти первый символ строки, а затем сопоставить его с регулярным выражением.

РЕДАКТИРОВАТЬ: Чтобы быть ясным, я бы сделал это, как говорили другие:

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

Просто хотел показать пример, максимально приближенный к вашему исходному псевдокоду. Ура!

Janx
источник
3
"Cat"(0).toStringМожно было бы более ясно написать как "Cat" take 1, имхо.
Дэвид Уинслоу,
Также (хотя это старое обсуждение - я, вероятно, копаю могилу): вы можете удалить '. *' С конца, поскольку он не добавляет никакого значения в регулярное выражение. Просто "Cat" .matches ("^ [a-cA-C]")
akauppi
Сегодня в 2.11 val r = "[A-Ca-c]".r ; "cat"(0) match { case r() => }.
som-snytt 09
Что означает хай-хет (^)?
Майкл Лафайет
Это якорь, означающий «начало строки» ( cs.duke.edu/csl/docs/unix_course/intro-73.html ). Таким образом, все, что следует за хай-хэтом, будет соответствовать шаблону, если оно будет первым на линии.
Janx
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 09
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

Haimei
источник