干这个Ruby代码

时间:2016-02-06 04:27:24

标签: ruby refactoring dry

如何删除此代码?

module TraverseTree
  def inorder_traverse root
    return nil unless root
    result = []
    result.concat inorder_traverse root.left if root.left
    result.push root.val
    result.concat inorder_traverse root.right if root.right
    result
  end

  def preorder_traverse root
    return nil unless root
    result = []
    result.push root.val
    result.concat preorder_traverse root.left if root.left
    result.concat preorder_traverse root.right if root.right
    result
  end

  def postorder_traverse root
    return nil unless root
    result = []
    result.concat postorder_traverse root.left if root.left
    result.concat postorder_traverse root.right if root.right
    result.push root.val
    result
  end
end

是否有一种基于函数名称以编程方式排序代码的好方法?

谢谢!

3 个答案:

答案 0 :(得分:2)

def traverse_recurse(root, options)
  return unless root
  options[:preorder].call(root.val) if options[:preorder]
  traverse_recurse(root.left, options)
  options[:inorder].call(root.val) if options[:inorder]
  traverse_recurse(root.right, options)
  options[:postorder].call(root.val) if options[:postorder]
end

def traverse_collect(root, type)
  result = []
  traverse_recurse(root, type => lambda { |val| result.push(val) })
  result
end

def preorder_traverse(root)
  traverse_collect(root, :preorder)
end

def inorder_traverse(root)
  traverse_collect(root, :inorder)
end

def postorder_traverse(root)
  traverse_collect(root, :postorder)
end

答案 1 :(得分:2)

正如克里斯的答案所指出的那样,肯定有消除重复的方法,但正如我在评论中提到的那样,我认为你的原始代码非常好,因为它的意图非常明确。即使没有任何评论,我也可以立即告诉每种方法的作用,我不想看到你失去了它。

但是,我确实看到了一种方法,你可以摆脱一些样板而不牺牲可读性。

这是你的第一个方法:

def inorder_traverse root
    return nil unless root
    result = []
    result.concat inorder_traverse root.left if root.left
    result.push root.val
    result.concat inorder_traverse root.right if root.right
    result
  end

向我跳出的第一件事是result = []; ... (return) result。这通常是Ruby中的代码味道,但是如何摆脱它并不是很明显,所以我会回到它。

跳出来的第二件事是这个方法检查root.left是否nil,然后以inorder_traverse作为参数调用root.left,这很好,但后来{ {1}}立即检查其参数是否为inorder_traverse。我们不需要两次这样做。

如果我们取消这些后置条件检查,我们最终得到这个:

nil

这不对,因为def inorder_traverse(root) return unless root result = [] result.concat(inorder_traverse(root.left)) result.push(root.val) result.concat(inorder_traverse(root.right)) result end 会在Array#concat返回inorder_traverse时引发TypeError。我们可以通过使用带有splat(nil)的Array#push来解决这个问题:当参数是一个数组时,它就像concat一样工作,当参数是*时,它就像concat一样工作一个空数组:

nil

你可能已经意识到,如果我们将一个参数展开到def inorder_traverse(root) return unless root result = [] result.push(*inorder_traverse(root.left)) result.push(root.val) result.push(*inorder_traverse(root.right)) result end ,我们可以在一个push中展开所有参数而不是调用push三个次:

push

......但是等一下。如果我们只是初始化一个空数组,将一堆元素推到它上,然后返回它,为什么我们在初始化它们时不直接将这些元素直接打到数组上?

所以:

def inorder_traverse(root)
  return unless root
  result = []
  result.push(
    *inorder_traverse(root.left),
    root.val,
    *inorder_traverse(root.right)
  )
  result
end

P.S。您可以做的另一件事是将module TraverseTree def inorder_traverse(root) return unless root [ *inorder_traverse(root.left), root.val, *inorder_traverse(root.right) ] end def preorder_traverse(root) return unless root [ root.val, *preorder_traverse(root.left), *preorder_traverse(root.right) ] end def postorder_traverse(root) return unless root [ *postorder_traverse(root.left), *postorder_traverse(root.right), root.val ] end end 替换为return unless root(或root && ...)。我发现这很诱人,但也有点臭,所以我留给你:

root and ...

加成

我不可避免地要考虑如何才能真正消除上述所有重复,并提出下面的代码,这些代码是匆匆写的,未经测试的,完全是不明智的。但写作很有趣!

def inorder_traverse(root)
  root && [
    *inorder_traverse(root.left),
    root.val,
    *inorder_traverse(root.right)
  ]
end

答案 2 :(得分:1)

如果你想在这件事上变得非常时髦,这是一种功能性的方法。

left_vals = -> traverse_order, root { traverse_order[root.left] if root.left }
right_vals = -> traverse_order, root { traverse_order[root.right] if root.right }
current_val = -> traverse_order, root { root.val }
traverse = -> parts, traverse_order, root { parts.inject([]) { |array, part| array.concat(Array(part[traverse_order, root])) } }
inorder_traverse = traverse.curry.([left_vals, current_val, right_vals], -> root { inorder_traverse[root] })
preorder_traverse = traverse.curry.([current_val, left_vals, right_vals], -> root { preorder_traverse[root] })
postorder_traverse = traverse.curry.([left_vals, right_vals, current_val], -> root { postorder_traverse[root] })

然后你可以打电话;

postorder_traverse[root]
inorder_traverse.(root)
preorder_traverse.call(root)

它们都是等价的。