Below is the tail-recursive function that will group your input lines in specified way.
The idea is simple: process input line by line. When key->value
pair is encountered, add it to the buffer (or accumulator). When line doesn't look like k->v
pair, add this line to the value string of the last pair that is already present in the buffer.
val s =
"""k -> v \n
| test
| \n here
|k2 -> v2
""".stripMargin.split("\n").toList
def rec(input:List[String]):Map[String, String] = {
val ARROW = "\\s*(.+?)\\s*->\\s*(.+?)\\s*".r
def r0(in:List[String], accum:List[(String, List[String])]):List[(String, List[String])] = in match {
// end of input, reverse line accumulators
case Nil => accum.map{case (k, lines) => k -> lines.reverse}.reverse
// key -> value line encountered, adding new k->v pair to outer accumulator
case ARROW(k, v) :: tail => r0(tail, (k, List(v)) :: accum)
// line without key encountered, adding this line to previous k->v pair in the accumulator
case line :: tail => r0(tail, accum match {
case (k, lines) :: accTail => (k, line :: lines) :: accTail
case _ => accum // if accum is empty and input doesn't have a key, ignore line
})
}
r0(input, Nil).toMap.mapValues(_.mkString("\n"))
}
rec(s).foreach(println(_))
Result:
(k,v \n
test
\n here)
(k2,v2
)
Each line is processed exactly once, also each addition and modification of the buffer is O(1), so the whole process is O(N).
Also, please note, that you're reading file in a way that leaves resource opened. Please refer to this for details.