Идиоматический способ анализа файла в Scala

#scala #idioms

#scala #идиомы

Вопрос:

Я анализирую файл в Scala, у меня есть два вида файлов для чтения:

Набор обучающих предложений с такой формой:

 StringtStringtInt
StringtStringtInt
// ...
StringtStringtInt
  

И набор тестовых предложений с такой формой:

 StringtStringtStringtInt
StringtStringtStringtInt
// ...
StringtStringtStringtInt
  

До сих пор я использовал Either для различения типов форматов:

   def readDataSet(file: String): Option[Vector[LabeledSentence]] = {

  def getSentenceType(s: Array[String]) = s.length match {
    case 3 => Left((s(0), s(1), s(2).toInt))
    case 4 => Right((s(0), s(1), s(2), s(3).toInt))
    case _ => Right(("EOS", "EOS", "EOS", -1))
  }

    val filePath = getClass.getResource(file).getPath

    Manage(Source.fromFile(filePath)) { source =>

      val parsedTuples = source getLines() map (s => s.split("t"))

      // ..........

      // Got throught each token in the file and construct a sentence
      for (s <- parsedTuples) {
        getSentenceType(s) match {
          // When reaching the end of the sentence, save it
          case Right(("EOS", "EOS", "EOS", -1)) =>
            sentences  = new LabeledSentence(lex.result(), po.result(), dep.result())
            lex.clear()
            po.clear()
            dep.clear()
          //            if (isTrain) gold.clear()
          case Left(x) =>
            lex  = x._1
            po  = x._2
            dep  = x._3
          case Right(x) =>
            lex  = x._1
            po  = x._2
            gold  = x._3
            dep  = x._4
        }
      }
      Some(sentences.result())
    }
  } 
  

Есть ли лучший / идиоматический способ упростить этот код?

Я удалил некоторую часть кода, не важную для этой цели, если вы хотите увидеть полный код, проверьте мою страницу на github

ОБНОВЛЕНИЕ: следуя совету Димы, я упростил свой код, используя моноид, вот результат:

 val parsedTuples = source
    .getLines()
    .map(s => s.split("t"))
    .map {
      case Array(a, b, c, d) => Tokens(a, b, c, d.toInt)
      case Array(a, b, d) => Tokens(a, b, "", d.toInt)
      case _ => Tokens() // Read end of sentence
    }.foldLeft((Tokens(), Vector.empty[LabeledSentence])) {
    // When reading an end of sentence, create a new Labeled sentence with tokens
    case ((z, l), t) if t.isEmpty => (Tokens(), l :  LabeledSentence(z))
    // Accumulate tokens of the sentence
    case ((z, l), t) => (z append(z, t), l)
  }._2
  

Комментарии:

1. Какой-то анализатор комбинаторов, например github.com/tpolecat/atto или lihaoyi.com/fastparse

2. @Reactormonk, спасибо, я посмотрю

Ответ №1:

Вам не нужно Either . Просто всегда используйте 4-кортеж:

   source
    .getLines
    .map(_.split("\t"))
    .map {
      case Array(a, b, c, d) => Some(a, b, c, d.toInt)
      case Array(a, b, d) => Some(a, b, "", d.toInt)
      case _ => None
    }.foldLelft((List.empty[LabeledSentence], List[String].empty, List.empty[String], List.empty[String], List.empty[Int])) {
      case ((l, lex, po, gold, dep), None) =>
         (new LabeledSentence(lex.reverse, po.reverse, fold.reverse, dep.reverse)::l, List(), List(), List(), List())
      case ((l, lex, po, gold, dep), Some((a, b, c, d))) => 
         (l, a::lex, b::po, c::gold, d::dep)
   }._1._1.reverse
  

Вы могли бы сделать последний шаг намного более элегантным, если бы переосмыслили свой подход к lex, po, gold, dep материалу (сделайте его классом case и / или объедините с LabeledSentence , возможно?).

Кроме того, вам нужно сократить использование изменяемых контейнеров, это значительно усложняет понимание того, что происходит. Это не java …

Комментарии:

1. Как я мог сделать второй? Только вчера узнал, что такое монады, но сейчас это кажется мне слишком сложным.

2. Второй? Извините, я отредактировал свой ответ, пока вы набирали свой комментарий, я думаю, и теперь я не помню, к чему это могло относиться 🙂

3. This is not java ДА.. У меня есть много проблем в github, чтобы сделать весь возможный код неизменяемым, но прямо сейчас это не сложно для меня

4. Я упоминал об использовании Моноидов

5. О, Monoid — это просто класс, в котором есть a .zero и a .plus . Например, Int является моноидом. Вы можете определить пользовательские моноиды в scala, а затем вы могли бы просто сделать parsedTuples.sum(LabeledSentenceMonoid) … Пока не беспокойтесь об этом, возможно, еще слишком рано 🙂