Recursive Scala Case class Reduction I have a case class that is recursive and looks like this:

case class Config(name: String, isEnabled: Boolean, elems: Map[String, MyCase])

case class MyCase(
  id: String,
  isActive: Boolean,
  elems: Option[Map[String, MyCase]])


Where the Config contains the id of the MyCase entries contained as a Map. I have to iterate over this structure and come up with a Map that contains the parent child relations. Say for example., if I have the Config class represented as below (for simplicity, I have just given the id's):

Config(Map("5" -&gt; myCase1, "6" -&gt; myCase2))

5 - Config
 1
  1
  2 
 2
6
 1


where id's 5 and 6 are top level entries which in turn has a recursive structure. I have to now come up with a Map that contains the parent child relationship for the id's. So for the case above, I will expect a Map that looks like:

Map(
 "5" -&gt; Seq("5.1"),
 "5.1" -&gt; Seq("5.1.1", "5.1.2"),
 "5.1.1" -&gt; Seq.empty[String],
 "5.1.2" -&gt; Seq.empty[String],
 "6" -&gt; Seq("6.1"),
 "6.1" -&gt; Seq.empty[String]
)


Notice how I append the parent id to the child. 

Given a Seq of such Configs, 

val configs = Seq(Config1, Config2)


this is what I have come up with:

def relationsFor(config: Config): Map[String, Seq[String]] = {

    def prepareChildren(parentId: String, acc: Map[String, Seq[String]], subElems: Map[String, MyCase]): Map[String, Seq[String]] = {
      subElems.collect {
        case (id, elem) if elem.isActive =&gt;
          val newAcc = acc ++ Map(parentId -&gt; subElems.keys.map(subElemId =&gt; s"$parentId.$subElemId").toSeq)
          if (elem.elems.isDefined) {
            val newParentId = s"$parentId.$id"
            val newAcc1 = newAcc ++ Map(s"$parentId.$id" -&gt; elem.elems.get.keys.map(subElemId =&gt; s"$newParentId.$subElemId").toSeq)
            prepareChildren(s"$parentId.$id", newAcc1, elem.elems.get)
          } else {
            acc ++ Map(s"$parentId.$id" -&gt; Seq.empty[String])
          }
      }
    }.flatten.toMap

    configs.collect {
      case config if config.isEnabled =&gt;
        config.elems.collect {
          case (parentId, elemCase) if elemCase.elems.isDefined =&gt;
            prepareChildren(parentId, Map.empty[String, Seq[String]], elemCase.elems.get)
          case (parentId, _) =&gt;
            Map(parentId -&gt; Seq.empty[String])
        }
    }.flatten.flatten.toMap
  }


Could this be simplified ? I have already tested this and it works as expected, but I find it a little bit hard to understand. Can this be made much more elegant? I mean can the relationsFor method be made more elegant ?
I tried do it a different way, when you asked this question on StackOverflow.. You find my version below, first I'll try to give you some feedback on your version :


I see you regularly use isDefined together with pattern matching or if / else. I would replace this by matching for Some / None or even better with a combination of map and getOrElse or with fold :

o.map(f).getOrElse(b)
o.fold(b)(f)


These are more ideomatic ways to handle an Option and unwrap the Option for you where you want to use it (calling get on an Option is something you should try to minimize).
It seems like you could have reused newParentId in the places where you wrote s"$parentId.$id", this would mean that newParentId should be placed outside the if else.
I am not sure if you can change the signature of MyCase, but I am not sure what extra the Option brings for elems, but this may be moot if you want to differentiate between None and Map.empty[String, MyCase]], if you don't then no collection or an empty collections is pretty much the same. 




Here's what I came up with :

def getMapWithChildIds(config: Config): Map[String, Seq[String]] = {
  def mkId(prefix: String, key: String): String = 
    if (prefix.isEmpty) key else prefix + "." + key

  def go(map: Map[String, MyCase], prefix: String): Map[String, Seq[String]] =
    map.flatMap { case (key, elem) =&gt; 
      val fullKey = mkId(prefix, key)
      // this will handle an unactive MyCase the same way as a MyCase with no elems
      Option(elem).filter(_.isActive).flatMap(_.elems).fold(
        Map(fullKey -&gt; Seq.empty[String]))(
        submap =&gt; {
          val directSubKeys = submap.keys.map(mkId(fullKey, _)).toList
          // map entry for this key + entries for sub keys
          Map(fullKey -&gt; directSubKeys) ++ go(submap, fullKey)
        })
    }

  go(config.elems, "")
}


Which gives for :

val noMap: Option[Map[String, MyCase]] = None

val config =
  Config("a", true,
    Map(
      "5" -&gt; MyCase("id?5", true, 
        Some(Map(
          "1" -&gt; MyCase("id?5.1", true,
            Some(Map(
              "1" -&gt; MyCase("id?5.1.1", true, noMap),
              "2" -&gt; MyCase("id?5.1.2", true, noMap)
            ))
          ),
          "2" -&gt; MyCase("id?5.2", true, noMap)
        ))
      ),
      "6" -&gt; MyCase("id?6", true, 
        Some(Map(
          "1" -&gt; MyCase("id?6.1", true, noMap)
        ))
      )
    )
  )


The following result :

val map = getMapWithChildIds(config)
// Map[String,Seq[String]] = Map(5.2 -&gt; List(), 5.1.2 -&gt; List(), 5 -&gt; List(5.1, 5.2), 5.1.1 -&gt; List(), 6 -&gt; List(6.1), 6.1 -&gt; List(), 5.1 -&gt; List(5.1.1, 5.1.2))

map.toList.sortBy(_._1).foreach(println)
// (5,List(5.1, 5.2))
// (5.1,List(5.1.1, 5.1.2))
// (5.1.1,List())
// (5.1.2,List())
// (5.2,List())
// (6,List(6.1))
// (6.1,List())