Идиоматический способ найти подходящую линию в Scala - PullRequest
3 голосов
/ 29 марта 2011

У меня есть Iterable[String], представляющий строки в файле, и я хотел бы найти первую строку в этой последовательности, которая соответствует регулярному выражению, и вернуть числовое значение, извлеченное регулярным выражением. Файл достаточно большой, чтобы не было смысла загружать все это в память, а затем вызывать toString() или что-то в этом роде, поэтому мне нужно будет пройти по нему строку за раз.

Вот что у меня (работает):

val RateRegex : Regex = ".....".r

def getRate(source : Source) : Option[Double] = {
  import java.lang.Double._

  for(line <- source.getLines() ) {
    line match {
      case RateRegex(rawRate) => return Some(parseDouble(rawRate))
      case None => ()
    }
  }

  return None
}

Это кажется мне безобразным. Это очень важно, и case None => () может быть заменено комментарием, который говорит: «Вы делаете это неправильно».

Я думаю, что хочу что-то вроде def findFirstWhereNonNone(p : Function[A,Option[B]]) => Option[B], где элементы коллекции имеют тип A.

Существуют ли встроенные методы, которые позволили бы мне сделать это более функциональным способом? Должен ли я просто написать этот метод?

P.S. Пока я в этом, есть ли альтернатива использованию java.lang.Double.parseDouble? Класс Scala Double не раскрывает его.

P.P.S Я видел много постов на SO, в которых предлагалось, чтобы Source API не использовался в производстве, но они все с 2008 и 2009 годов. Это все еще так? Если так, что я должен использовать для IO?

Обновление

Теперь у меня есть:

import util.matching.Regex.Groups

for{line <- source.getLines()
    Groups(rawRate) <- RateRegex.findFirstMatchIn(line)} {
  return Some(parseDouble(rawRate))
}

return None

что для меня намного лучше.

Ответы [ 2 ]

5 голосов
/ 29 марта 2011

РЕДАКТИРОВАТЬ: Эта третья альтернатива довольно аккуратно:

source
.getLines()
.collectFirst{ case RateRegex(x) => x.toDouble}

Не уверен, что это более функционально, но вы можете использовать поведение foreach / for-comp понимания в Options

def getRate(source : Source) : Option[Double] = {

     for {line    <- source.getLines() 
          rawRate <- RateRegex.findFirstIn(line)}
       return  Some(rawRate toDouble)

  return None
}

Это тоже работает (очень похоже на ответ EasyAngel):

source
.getLines()
.map{RateRegex.findFirstMatchIn(_)}
.filter{_.isDefined}
.map{_.get.group(0).toDouble}
.head
.toList
.headOption

Последние три немного уродливы. Взятие (1) должно гарантировать, что мы оцениваем только до первого матча. ToList должен заставить оценку, а headOption извлечь первое значение как Some () или None, если его нет. Есть ли более идиоматический способ сделать это?

1 голос
/ 29 марта 2011

Вот одно из возможных решений:

def getRates(source : Source) = source.getLines.map {
    case RateRegex(rate) => Some(rate toDouble)
    case _ => None
} filter (_ isDefined) toList

Обратите внимание, что теперь эта функция возвращает List[Option[Double]] всех найденных тарифов. Также важно, что Iterator остается ленивым, пока я не позвоню toList


Обновление

Как было сказано в комментариях, вот решение, которое возвращает только первое вхождение:

def getRate(source : Source): Option[Double] = source.getLines.map {
  case RateRegex(rate) => Some(rate toDouble)
  case _ => None
} find (_ isDefined) getOrElse None
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...