递归地构建列表列表

时间:2013-07-12 13:10:47

标签: scala

只要递归调用的返回类型为Any,下面的代码就会编译,但显然我做错了,因为它不应该是Any。

  case class Group(
    id: Long = -1,
    parentId: Long = -1,
    name: String = "")

  def makeTree(groupId: Long, groups: List[Group]) = {
    def getAllChildren(gid: Long): Any = {
      def children = for {
        g <- groups; if g.parentId == gid
      } yield g

      if (children.isEmpty) List()
      else {
        children map { x =>
          getAllChildren(x.id)
        }
      }
    }
    getAllChildren(groupId)
  }                                               
  val groups = List(Group(1, 0, "A"), 
                    Group(2, 1, "B"), 
                    Group(3, 1, "C"), 
                    Group(4, 2, "D"))

  makeTree(1, groups)
  //Results in: Any = List(List(List()), List())
  }

如果我将getAllChildren的签名更改为:

def getAllChildren(gid: Long): List[Group]

然后我收到一个错误:

type mismatch;  found   : List[List[Group]]  required: List[Group]

我在这里做错了什么。

3 个答案:

答案 0 :(得分:3)

不是真的说scala,但看起来,对于某些id,你收集具有该id的组,然后用它的子列表替换每个组,依此类推。

具体来说,这里:

  if (children.isEmpty) List()
  else {
    children map { x =>
      getAllChildren(x.id)
     }
  }

实际上,这是您的错误的根源:您的算法允许无限递归,每次递归在您的返回类型周围添加另一个List[...]。但是你不能拥有动态深度的类型。

例如,如果您尝试通过提供类型List[List[Group]]来解决此问题,则会抱怨它找到List[List[List[Group]]],依此类推,直到您放弃。

这是类型标记告诉我们我们即将编写潜在无限递归的方式。您可能假定您的层次结构不能涉及循环的不变量,但这不会反映在类型中。事实上,构建两个群体彼此为父母的例子并不难。在这种情况下,您的程序将生成一个更深的嵌套列表,直到它由于内存不足而终止。

请注意,您无法通过使用flatMap而不是map来修复代码:原因是getAllChildren永远不会生成包含Group元素的列表。它要么返回一个空列表,要么返回一个空列表的扁平列表,即一个空列表。因此,如果它应该返回,它将在flatmap版本中返回一个空列表。

答案 1 :(得分:3)

您需要将children map ...的号码更改为children flatMap ...,否则您不会返回List[Group],但可能会像@Ingo建议的那样List[List[.....]]flatMap会将每个元素映射到List,然后展平所有列表,只创建一个包含所有子元素的List[Group]

编辑:正如@Ingo指出的那样,即使flatMap可以解决输入问题,您的功能仍然无效,因为您始终返回一个空列表。你应该去

children flatMap { x => x :: getAllChildren(x.id, acc) }

将孩子置于孩子的孩子身边。

答案 2 :(得分:1)

如果你将children map上返回的列表展平,那么你的代码就可以了:

def makeTree(groupId: Long, groups: List[Group]) = {
  def getAllChildren(gid: Long): List[Group] = {
    def children = for {
      g <- groups; if g.parentId == gid
    } yield g

    if (children.isEmpty) List()
    else {
      val listList = children map { x =>
        x :: getAllChildren(x.id)
      }

      listList.flatten
    }
  }
  getAllChildren(groupId)
}

发生这种情况的原因是您正在获取List [Group]并将其映射到也返回List[Group]的函数,从而产生List[List[Group]]。简单地展平List将产生所需的结果。