Flattening nested lists of the same type

你离开我真会死。 提交于 2019-12-04 13:04:55
cainj

A true functional way. Without using a variable.

def flatten[A](list: List[A]): List[A] = list match {
   case Nil => Nil
   case (ls: List[A]) :: tail => flatten(ls) ::: flatten(tail)
   case h :: tail => h :: flatten(tail)
}

I prefere a recursive way. In general I would do something like this:

def flattenList[A](l: List[Any]): List[A] = {
  var acc = List[A]()
  l foreach ( entry => entry match {
    case a: List[Any] => acc = acc ::: flattenList(a)
    case b: A => acc = acc :+ b
  })
  acc
}

This will flatten you a List(Element(A), Element(B), List(Element(C), Element(D), List(Element(E), Element(F)))) to List(Element(A), Element(B), Element(C), Element(D), Element(E), Element(F))

In an ideal world, where the wretched type erasure would not exist, you would do something like this:

// WON'T WORK
def flatten[T <: SpecialList](parentList: T): List[Exp] = parentList.elements flatMap {
    case x:T => x.elements
    case somethingElse => List(somethingElse)
}

But the best solution under the circumstances is, in my opinion, this one:

def flatten[T <: SpecialList](parentList: T): List[Exp] = parentList.elements flatMap {
    case x:SpecialList if x.getClass == parentList.getClass => x.elements
    case somethingElse => List(somethingElse)
}

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:

abstract class SpecialList {
    val elements: List[Exp]

    def flatten: SpecialList = createAnother(elements flatMap {
        case x: SpecialList => {
            val flattenX = x.flatten
            if (flattenX.getClass == this.getClass) flattenX.elements else List(flattenX)
        }
        case somethingElse => List(somethingElse)
    })

    // Creates another special list of the same type
    def createAnother(elements: List[Exp]): SpecialList

}


case class ListA(elements: List[Exp]) extends SpecialList {
    override def toString: String = "ListA("+elements.mkString(",")+")"

    def createAnother(elements: List[Exp]) = ListA(elements)
}

case class ListB(elements: List[Exp]) extends SpecialList {
    override def toString: String = "ListB("+elements.mkString(",")+")"

    def createAnother(elements: List[Exp]) = ListB(elements)
}

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:

case class SpecialList(tag: Tag, elements: List[Exp]) extends Exp {
    def flatten: SpecialList = {
        val newElems = elements flatMap {
            case x: SpecialList => {
                val flattenX = x.flatten
                if (flattenX.tag == this.tag) flattenX.elements else List(flattenX)
            }
            case somethingElse => List(somethingElse)
        }
        SpecialList(tag, newElems)
    }

    override def toString = tag.toString ++ "(" + elements.mkString(",") + ")"

}


sealed abstract class Tag {

    // Syntactic sugar to maintain the notation used in the question
    def apply(elements: Exp*): SpecialList = SpecialList(this, elements.toList)

}

object ListA extends Tag { override val toString = "ListA" }
object ListB extends Tag { override val toString = "ListB" }

From a syntactic point of view, it's pretty much the same, since you have

val x = ListA(Element(A), Element(B), ListA(Element(C), Element(D)), ListB(Element(E),Element(F), ListA(Element(C), ListA(Element(D)))))
x.flatten => ListA(Element(A),Element(B),Element(C),Element(D),ListB(Element(E),Element(F),ListA(Element(C),Element(D))))

This may not fit your problem, however, so sorry if I went off the rails a bit there.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!