#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)
… Пока не беспокойтесь об этом, возможно, еще слишком рано 🙂