【问题标题】:How can I make this method more Scalalicious我怎样才能使这种方法更具Scalalicious
【发布时间】:2012-04-23 23:20:56
【问题描述】:

给定一个简单的 node.id,node.parentId 关联,我有一个函数可以计算一些 treeNodes 集合的左右节点值。它非常简单并且效果很好......但是,我想知道是否有更惯用的方法。具体来说,有一种方法可以在不使用一些外部跟踪值的情况下跟踪左/右值,但仍然保持美味的递归。

/* 
 * A tree node
 */
case class TreeNode(val id:String, val parentId: String){
    var left: Int = 0
    var right: Int = 0
}

/* 
 * a method to compute the left/right node values 
 */
def walktree(node: TreeNode) = {
    /* 
     * increment state for the inner function 
     */
    var c = 0

    /*
     * A method to set the increment state
     */
    def increment = { c+=1; c } // poo

    /* 
     * the tasty inner method
     * treeNodes is a List[TreeNode]
     */
    def walk(node: TreeNode): Unit = {
      node.left = increment

      /* 
       * recurse on all direct descendants 
       */
      treeNodes filter( _.parentId == node.id) foreach (walk(_))

      node.right = increment
    }

    walk(node)
}

walktree(someRootNode)

编辑 - 节点列表取自数据库。将节点拉入适当的树会花费太多时间。我正在将一个平面列表拉入内存,我所拥有的只是通过节点 ID 与父母和孩子相关的关联。

添加左/右节点值允许我使用单个 SQL 查询获取所有子节点(和子节点的子节点)的快照。

如果父子关联发生变化(它们经常发生变化),计算需要非常快速地运行以保持数据完整性。

除了使用了不起的 Scala 集合之外,我还通过对树节点上的一些前置/后置过滤使用并行处理来提高速度。我想找到一种更惯用的方法来跟踪左/右节点值。在查看@dhg 的答案后,它变得更好了。使用 groupBy 而不是过滤器将算法(主要是?)变成线性的而不是二次的!

val treeNodeMap = treeNodes.groupBy(_.parentId).withDefaultValue(Nil)

def walktree(node: TreeNode) = {
    def walk(node: TreeNode, counter: Int): Int = {
        node.left = counter 
        node.right = 
          treeNodeMap(node.id) 
          .foldLeft(counter+1) {
            (result, curnode) => walk(curnode, result) + 1
        }
        node.right
    }
    walk(node,1)
}

【问题讨论】:

  • treeNodes 在哪里定义?您没有递归定义 TreeNode 是否有某些原因? walktree 有什么意义?要重新编号 leftright 值?为什么leftright 值不与TreeNodes 关联?
  • 这不是codereview,但你需要方式个更少的cmets,你至少需要一个有用的评论。恰好零个 cmets 说出了代码尚未告诉您的任何相关内容。把它们都扔掉,然后添加两行来描述目标。
  • @Rex,我也有同样的想法 :-)
  • treeNodes 只是一个 TreeNode 的列表。它不是递归定义的,因为列表是从数据库中提取的,这需要时间。

标签: scala recursion idioms


【解决方案1】:

您的代码似乎正在计算按顺序遍历编号。

我认为你想让你的代码更好的是fold,它将当前值向下传递并向上传递更新的值。请注意,在walktree 之前执行treeNodes.groupBy(_.parentId) 也可能是值得的,以防止您每次调用walk 时都调用treeNodes.filter(...)

val treeNodes = List(TreeNode("1","0"),TreeNode("2","1"),TreeNode("3","1"))

val treeNodeMap = treeNodes.groupBy(_.parentId).withDefaultValue(Nil)

def walktree2(node: TreeNode) = {
  def walk(node: TreeNode, c: Int): Int = {
    node.left = c
    val newC = 
      treeNodeMap(node.id)         // get the children without filtering
        .foldLeft(c+1)((c, child) => walk(child, c) + 1)
    node.right = newC
    newC
  }

  walk(node, 1)
}

它产生相同的结果:

scala> walktree2(TreeNode("0","-1"))
scala> treeNodes.map(n => "(%s,%s)".format(n.left,n.right))
res32: List[String] = List((2,7), (3,4), (5,6))

也就是说,我将完全重写您的代码,如下所示:

case class TreeNode(        // class is now immutable; `walktree` returns a new tree
  id: String,
  value: Int,               // value to be set during `walktree` 
  left: Option[TreeNode],   // recursively-defined structure
  right: Option[TreeNode])  //   makes traversal much simpler

def walktree(node: TreeNode) = {
  def walk(nodeOption: Option[TreeNode], c: Int): (Option[TreeNode], Int) = {
    nodeOption match {
      case None => (None, c)  // if this child doesn't exist, do nothing
      case Some(node) =>      // if this child exists, recursively walk
        val (newLeft, cLeft) = walk(node.left, c)        // walk the left side
        val newC = cLeft + 1                             // update the value
        val (newRight, cRight) = walk(node.right, newC)  // walk the right side
        (Some(TreeNode(node.id, newC, newLeft, newRight)), cRight)
    }
  }

  walk(Some(node), 0)._1
}

那么你可以这样使用它:

walktree(
  TreeNode("1", -1,
    Some(TreeNode("2", -1,
      Some(TreeNode("3", -1, None, None)),
      Some(TreeNode("4", -1, None, None)))),
    Some(TreeNode("5", -1, None, None))))

生产:

Some(TreeNode(1,4,
  Some(TreeNode(2,2,
    Some(TreeNode(3,1,None,None)),
    Some(TreeNode(4,3,None,None)))),
  Some(TreeNode(5,5,None,None))))

【讨论】:

  • +1 用于充分了解所需内容以找到解决方案
  • 其实比黄金好。这使节点行走减少了半秒!谢谢!
  • @Neil,这是因为在您的版本中,treeNodes.filter(...) 必须在每次调用 walk 来查找子节点时遍历每个节点。在我的版本中,一个节点确切地知道它的子节点在哪里,并直接调用它们。
  • 我的意思是使用 foldLeft 的过滤器版本。如果我立即将树组装成一个节点结构,我对树遍历的需求就会减少(我可能会在组装节点时保持跟踪)。过滤器是必要的,因为我只有一个平面内存列表,该列表取自并将持久保存到数据库中。
  • @Neil,我添加了一个groupBy,这样您就不必每次拨打walk 时都需要filter
【解决方案2】:

如果我得到你的算法正确:

def walktree(node: TreeNode, c: Int): Int = {
    node.left = c

    val c2 = treeNodes.filter(_.parentId == node.id).foldLeft(c + 1) { 
        (cur, n) => walktree(n, cur)
    }

    node.right = c2 + 1
    c2 + 2
}

walktree(new TreeNode("", ""), 0)

可能会发生非一错误。

很少有随意的想法(更适合http://codereview.stackexchange.com):

  • 尝试发布编译...我们必须猜测这是TreeNode的序列:

  • val 对于 case 类是隐含的:

    case class TreeNode(val id: String, val parentId: String) {
    
  • 避免对Unit 函数使用显式的=Unit

    def walktree(node: TreeNode) = {
    def walk(node: TreeNode): Unit = {
    
  • 有副作用的方法应该有():

    def increment = {c += 1; c}
    
  • 这非常慢,考虑在实际节点中存储子节点列表:

    treeNodes filter (_.parentId == node.id) foreach (walk(_))
    
  • 更简洁的语法是treeNodes foreach walk:

    treeNodes foreach (walk(_))
    

【讨论】:

    猜你喜欢
    • 2014-08-13
    • 2020-01-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-10-17
    相关资源
    最近更新 更多