Як вирівняти відповідність, використовуючи регулярний вираз у Scala?


124

Мені хотілося б знайти відповідність між першою літерою слова та однією з літер у такій групі, як "ABC". У псевдокоді це може виглядати приблизно так:

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(як Listі Array), якщо ви хочете перші 3 символи, спробуйте "my string".take(3), для першої"foo".head
оболонка

Відповіді:


237

Це можна зробити, оскільки регулярні вирази визначають екстрактори, але спочатку потрібно визначити шаблон регулярного виразів. У мене немає доступу до Scala REPL, щоб перевірити це, але щось подібне повинно працювати.

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

5
майте на увазі, що ви не можете оголосити групу захоплення, а потім не використовувати її (тобто випадок шаблону () тут не збігається)
Джеремі Лейпциг

34
Остерігайтеся, що ви повинні використовувати групи у своєму регулярному виразі: val Pattern = "[a-cA-C]".rне вийде. Це тому, що використовується match-case unapplySeq(target: Any): Option[List[String]], який повертає групи, що відповідають.
rakensi

2
Це метод на StringLike, який повертає Regex .
асм

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(_*) => }.
сом-снітт

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, показаний у публікації в блозі Введення в тип динаміки :

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: Не знаю, в чому може бути проблема. Все в моїй відповіді є дійсним кодом Scala з 2.10.
kiritsuku

@sschaef: цей case p.firstName.lastName.Map(...шаблон - як же я це читаю?
Ерік Каплун

1
@ErikAllik читає це як щось на зразок "коли" firstName "починається з" Jo ", а" secondName "відповідає заданому регексу, ніж збіг є успішним". Це більше приклад потужності Scalas, я б не писав цей випадок використання, наприклад, таким чином у виробничому коді. До речі, використання Карти слід замінити Переліком, оскільки Карта не упорядкована і для більшої кількості значень більше не гарантується, що права змінна відповідає правильній відповідності.
kiritsuku

1
Це дуже зручно для швидкого прототипування, але зауважте, що це створює новий примірник Regexкожного разу, коли перевіряється збіг. І це досить дорога операція, яка включає компіляцію схеми регулярних виразів.
HRJ

51

Як зазначив Делнан, matchключове слово в Scala не має нічого спільного з регулярними виразами. Щоб дізнатись, чи відповідає рядок з регулярним виразом, ви можете скористатися String.matchesметодом. Щоб дізнатись, чи починається рядок з a, b або c в нижньому або верхньому регістрі, регулярний вираз буде виглядати так:

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

Ви можете прочитати цей регулярний вираз як "один із символів a, b, c, A, B або C з будь-яким" ( .означає "будь-який символ" і *означає "нуль або більше разів", тому ". *" - будь-який рядок) .


25

Щоб трохи розширити відповідь Ендрю : Той факт, що регулярні вирази визначають екстрактори, може дуже добре використовувати декомпозиції, узгоджені регексом, використовуючи відповідність шаблону Скали, наприклад:

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

Мене справді бентежить високий капелюх ^. I хоч "^" означав "Зрівняти початок рядка". Це не відповідає початку рядка.
Майкл Лафайєт

@MichaelLafayette: Всередині класу символів ( []) карета вказує на заперечення, тому [^\s]означає " непробільний простір ".
Фабіан Стіг

9

String.matches - це спосіб зіставлення шаблонів у сенсі виразів.

Але як корисний бік, word.firstLetter у реальному коді Scala виглядає так:

word(0)

Скала трактує струни як послідовність Чар, тому, якщо ви з якихось причин хотіли явно отримати перший символ рядка і зіставити його, ви можете використовувати щось подібне:

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

Я не пропоную це як загальний спосіб узгодити шаблон регулярного виразів, але це відповідає вашому запропонованому підходу спочатку знайти перший символ рядка, а потім зіставити його з регулярним виразом.

EDIT: Щоб було зрозуміло, я б це зробив, як казали інші:

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

Просто хотів показати приклад якомога ближче до початкового псевдокоду. Ура!


3
"Cat"(0).toStringможна було б чіткіше записати як "Cat" take 1, imho.
Девід Уінслоу

Також (хоча це давнє обговорення - я, мабуть, викопую могилу): ви можете видалити ". *" З кінця, оскільки це не додає значення регулярному вираженню. Просто "Cat" .matches ("^ [a-cA-C]")
akauppi

Сьогодні 2.11 val r = "[A-Ca-c]".r ; "cat"(0) match { case r() => }.
som-snytt

Що означає привіт капелюх (^)?
Майкл Лафайєт

Це якор, що означає "початок рядка" ( cs.duke.edu/csl/docs/unix_course/intro-73.html ). Тож все, що слідує за шапкою привіт, буде відповідати шаблону, якщо це перше, що в лінійці.
Янкс

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. Ось більше прикладів у документі Scala: http://www.scala-lang.org/files/archive/api/current/index.html#scala.util.matching.Regex

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.