Let's say I want to flatten nested lists of the same type... For example
ListA(Element(A), Element(B), ListA(Element(C), Element(D)), ListB(Element(E),Element(F)))
ListA
contains nested list of the same type (ListA(Element(C), Element(D))
) so I want to substitute it with the values it contains, so the result of the upper example should look like this:
ListA(Element(A), Element(B), Element(C), Element(D), ListB(Element(E),Element(F)))
Current class hierarchy:
abstract class SpecialList() extends Exp {
val elements: List[Exp]
}
case class Element(name: String) extends Exp
case class ListA(elements: List[Exp]) extends SpecialList {
override def toString(): String = "ListA("+elements.mkString(",")+")"
}
case class ListB(elements: List[Exp]) extends SpecialList {
override def toString(): String = "ListB("+elements.mkString(",")+")"
}
object ListA{def apply(elements: Exp*):ListA = ListA(elements.toList)}
object ListB{def apply(elements: Exp*):ListB = ListB(elements.toList)}
I have made three solutions that works, but I think there have to be better way to achieve this:
First solution:
def flatten[T <: SpecialList](parentList: T): List[Exp] = {
val buf = new ListBuffer[Exp]
for (feature <- parentList.elements) feature match {
case listA:ListA if parentList.isInstanceOf[ListA] => buf ++= listA.elements
case listB:ListB if parentList.isInstanceOf[ListB] => buf ++= listB.elements
case _ => buf += feature
}
buf.toList
}
Second solution:
def flatten[T <: SpecialList](parentList: T): List[Exp] = {
val buf = new ListBuffer[Exp]
parentList match {
case listA:ListA => for (elem <- listA.elements) elem match {
case listOfTypeA:ListA => buf ++= listOfTypeA.elements
case _ => buf += elem
}
case listB:ListB => for (elem <- listB.elements) elem match {
case listOfTypeB:ListB => buf ++= listOfTypeB.elements
case _ => buf += elem
}
}
buf.toList
}
Third solution
def flatten[T <: SpecialList](parentList: T): List[Exp] = parentList.elements flatMap {
case listA:ListA if parentList.isInstanceOf[ListA] => listA.elements
case listB:ListB if parentList.isInstanceOf[ListB] => listB.elements
case other => List(other)
}
My question is whether there is any better, more generic way to achieve same functionality as in all of upper three solutions there is repetition of code?
In an ideal world, where the wretched type erasure would not exist, you would do something like this:
But the best solution under the circumstances is, in my opinion, this one:
It's a bit more generic than the one proposed in the question since you don't need to bother whether the argument is a ListA or ListB and it will also work if in the future you'll add a ListC.
However, this won't solve your more general problem for flattening at arbitrary depths, since flatten(ListA(...)) must also return a ListA(...) in the end - in the case above it returns a List which looses it's initial meaning. A solution to this problem could be:
The problem in this case is that the
createAnother
bit is pure boilerplate. On the other hand, this version maintains the generality of the above solution.A third suggestion, which may involve refactoring your code a bit more is to drop the ListA and ListB types altogether, since it seems to me that their purpose is to provide a tag to a list of Exp. So consider this solution:
From a syntactic point of view, it's pretty much the same, since you have
This may not fit your problem, however, so sorry if I went off the rails a bit there.
I prefere a recursive way. In general I would do something like this:
This will flatten you a
List(Element(A), Element(B), List(Element(C), Element(D), List(Element(E), Element(F))))
toList(Element(A), Element(B), Element(C), Element(D), Element(E), Element(F))
A true functional way. Without using a variable.