干这个 Ruby 代码
Dry This Ruby Code
我怎样才能干掉这段代码?
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
有没有一种好的方法可以根据函数名称以编程方式对代码进行排序?
谢谢!!
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
正如 Chris 的回答所指出的,这里肯定有消除重复的方法,但正如我在对他们的回答的评论中提到的,我认为您的原始代码非常好,因为它的 intent 就很清楚了。即使没有任何评论,我也可以立即说出每个方法的作用,我不想看到你失去它。
但是,我确实看到了一种方法,您可以在不牺牲可读性的情况下摆脱一些样板。
这是您的第一种方法:
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
作为参数调用inorder_traverse
之前检查root.left
是否是nil
,这很好,但是然后 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
returns nil
时引发 TypeError。我们可以通过使用 Array#push
和 splat (*
) 来解决这个问题:当参数是一个数组时,它的工作方式就像 concat,而当参数是 nil
时,它的工作方式就像 concat使用空数组:
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
...但请稍等。如果我们只是初始化一个空数组,将一堆元素压入其中,然后返回它,为什么我们不在初始化时直接将这些元素拼写到数组上呢?
等等:
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
P.S。您可以做的另一件事是将 return unless root
替换为 root && ...
(或 root and ...
)。我觉得这很诱人,但也有点臭,所以我把它留给你:
def inorder_traverse(root)
root && [
*inorder_traverse(root.left),
root.val,
*inorder_traverse(root.right)
]
end
奖金
我不可避免地开始思考如何才能真正消除上面的所有重复,并想出了下面的代码,这是仓促编写的、未经测试的,而且完全是不明智的。但是写起来很有趣!
module TraverseTree
ORDERS = %i[preorder inorder postorder].each do |order|
define_method(:"#{order}_traverse",
&method(:traverse_by).curry(order))
end
private
def traverse_by(order, root)
root && [
traverse_by(order, root.left),
traverse_by(order, root.right)
]
.insert(ORDERS.index(order), root.val)
.compact.flatten
end
end
如果你想在这件事上变得非常时髦,这里有一个实用的方法。
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)
它们都是等价的。
我怎样才能干掉这段代码?
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
有没有一种好的方法可以根据函数名称以编程方式对代码进行排序?
谢谢!!
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
正如 Chris 的回答所指出的,这里肯定有消除重复的方法,但正如我在对他们的回答的评论中提到的,我认为您的原始代码非常好,因为它的 intent 就很清楚了。即使没有任何评论,我也可以立即说出每个方法的作用,我不想看到你失去它。
但是,我确实看到了一种方法,您可以在不牺牲可读性的情况下摆脱一些样板。
这是您的第一种方法:
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
作为参数调用inorder_traverse
之前检查root.left
是否是nil
,这很好,但是然后 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
returns nil
时引发 TypeError。我们可以通过使用 Array#push
和 splat (*
) 来解决这个问题:当参数是一个数组时,它的工作方式就像 concat,而当参数是 nil
时,它的工作方式就像 concat使用空数组:
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
...但请稍等。如果我们只是初始化一个空数组,将一堆元素压入其中,然后返回它,为什么我们不在初始化时直接将这些元素拼写到数组上呢?
等等:
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
P.S。您可以做的另一件事是将 return unless root
替换为 root && ...
(或 root and ...
)。我觉得这很诱人,但也有点臭,所以我把它留给你:
def inorder_traverse(root)
root && [
*inorder_traverse(root.left),
root.val,
*inorder_traverse(root.right)
]
end
奖金
我不可避免地开始思考如何才能真正消除上面的所有重复,并想出了下面的代码,这是仓促编写的、未经测试的,而且完全是不明智的。但是写起来很有趣!
module TraverseTree
ORDERS = %i[preorder inorder postorder].each do |order|
define_method(:"#{order}_traverse",
&method(:traverse_by).curry(order))
end
private
def traverse_by(order, root)
root && [
traverse_by(order, root.left),
traverse_by(order, root.right)
]
.insert(ORDERS.index(order), root.val)
.compact.flatten
end
end
如果你想在这件事上变得非常时髦,这里有一个实用的方法。
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)
它们都是等价的。