Какой самый элегантный способ найти пары слов в тексте с помощью Scala?
Приведен список пар слов
val terms = ("word1a", "word1b") :: ("word2a", "word2b") :: ... :: Nil
Какой самый элегантный способ проверить в Scala, есть ли хотя бы одна из пар в тексте? Тест должен завершиться как можно быстрее, когда он попадет в первый матч. Как бы вы решили это?
РЕДАКТИРОВАТЬ: Чтобы быть более точным, я хочу знать, если оба слова пары появляются где-то (не обязательно по порядку) в тексте. Если это относится к одной из пар в списке, метод должен вернуть true
, Нет необходимости возвращать подобранную пару, равно как и важно, если найдено более одной пары.
3 ответа
scala> val text = Set("blah1", "word2b", "blah2", "word2a")
text: scala.collection.immutable.Set[java.lang.String] = Set(blah1, word2b, blah2)
scala> terms.exists{case (a,b) => text(a) && text(b)}
res12: Boolean = true
РЕДАКТИРОВАТЬ: обратите внимание, что использование набора для представления токенов в тексте делает поиск из contains
гораздо эффективнее Вы не хотели бы использовать что-то последовательное, как список для этого.
РЕДАКТИРОВАТЬ 2: Обновлено для уточнения в требовании!
РЕДАКТИРОВАТЬ 3: изменено contains
в apply
согласно предложению в комментарии
РЕДАКТИРОВАТЬ - кажется, двусмысленная формулировка вашего вопроса означает, что я ответил на другой вопрос:
Потому что вы по сути просите любую из пары; с тем же успехом вы можете объединить все это в один большой набор.
val words = (Set.empty[String] /: terms) { case (s, (w1, w2)) => s + w1 + w2 }
Тогда вы просто спрашиваете, существует ли что-либо из этого в тексте:
text.split("\\s") exists words
Это быстро, потому что мы можем использовать структуру Set
быстро проверить, содержится ли слово в тексте; это заканчивается рано из-за "существует":
scala> val text = "blah1 blah2 word2b"
text: java.lang.String = blah1 blah2 word2b
В случае, если ваш текст очень длинный, вы можете Stream
это, так что следующее слово для тестирования лениво вычисляется, а не разбивать строку на подстроки заранее:
scala> val Word = """\s*(.*)""".r
Word: scala.util.matching.Regex = \s*(.*)
scala> def strmWds(text : String) : Stream[String] = text match {
| case Word(nxt) => val (word, rest) = nxt span (_ != ' '); word #:: strmWds(rest)
| case _ => Stream.empty
| }
strmWds: (text: String)Stream[String]
Теперь вы можете:
scala> strmWds(text) exists words
res4: Boolean = true
scala> text.split("\\s") exists words
res3: Boolean = true
Я предполагаю, что оба элемента пары должны появиться в тексте, но не имеет значения, где, и не имеет значения, какая пара появляется.
Я не уверен, что это самый элегантный, но он не плохой, и он довольно быстрый, если вы ожидаете, что текст, вероятно, содержит слова (и, следовательно, вам не нужно читать все это), и если вы можете сгенерировать итератор, который даст вам слова по одному:
case class WordPair(one: String, two: String) {
private[this] var found_one, found_two = false
def check(s: String): Boolean = {
if (s==one) found_one = true
if (s==two) found_two == true
found_one && found_two
}
def reset {
found_one = false
found_two = false
}
}
val wordpairlist = terms.map { case (w1,w2) => WordPair(w1,w2) }
// May need to wordpairlist.foreach(_.reset) first, if you do this on multiple texts
text.iterator.exists(w => wordpairlist.exists(_.check(w)))
Вы могли бы улучшить вещи, поместив все термины в набор, и даже не потрудившись проверить WordPirlist, если только слово из текста не было в этом наборе.
Если вы имеете в виду, что слова должны располагаться рядом друг с другом по порядку, вам следует изменить check
в
def check(s: String) = {
if (found_one && s==two) found_two = true
else if (s==one) { found_one = true; found_two = false }
else found_two = false
found_one && found_two
}