擦干这个Ruby代码

擦干这个Ruby代码,ruby,refactoring,dry,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

我怎样才能擦干这段代码

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
是否有一种基于函数名以编程方式对代码排序的好方法


谢谢

正如Chris的回答所指出的,这里当然有消除重复的方法,但正如我在对他们的回答的评论中提到的,我认为您的原始代码非常好,因为它的意图非常明确。即使没有任何评论,我也能马上说出每种方法的作用,我不想看到你失去它

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
然而,我确实看到了一种方法,可以在不牺牲可读性的情况下去掉一些样板文件

以下是您的第一种方法:

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=[]。。。(返回)结果
。在Ruby中,这通常是一种代码味道,但如何消除它还不是很明显,所以我将回到这里

跳出的第二件事是,该方法在调用
inorder\u transverse
之前,先检查
root.left
是否为
nil
,然后将
inorder\u transverse
作为参数调用
root.left
,这很好,但是
inorder\u transverse
会立即检查其参数是否为
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
这是不对的,但是,因为
数组#concat
将在
按顺序遍历
返回
nil
时引发类型错误。我们可以通过使用splat(
*
)的
Array#push
来解决这个问题:当参数是数组时,它的工作方式与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
但是,您可能已经意识到,如果我们将一个参数splating到
push
,我们可以在一个
push
中splat所有参数,而不是调用
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
另外,您可以做的另一件事是将
return除非root
替换为
root&&…
(或
root和…
)。我觉得这很诱人,但也有点臭,所以我把它留给你:

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

正如Chris的回答所指出的,这里当然有消除重复的方法,但正如我在对他们的回答的评论中提到的,我认为您的原始代码非常好,因为它的意图非常明确。即使没有任何评论,我也能马上说出每种方法的作用,我不想看到你失去它

然而,我确实看到了一种方法,可以在不牺牲可读性的情况下去掉一些样板文件

以下是您的第一种方法:

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=[]。。。(返回)结果
。在Ruby中,这通常是一种代码味道,但如何消除它还不是很明显,所以我将回到这里

跳出的第二件事是,该方法在调用
inorder\u transverse
之前,先检查
root.left
是否为
nil
,然后将
inorder\u transverse
作为参数调用
root.left
,这很好,但是
inorder\u transverse
会立即检查其参数是否为
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
这是不对的,但是,因为
数组#concat
将在
按顺序遍历
返回
nil
时引发类型错误。我们可以通过使用splat(
*
)的
Array#push
来解决这个问题:当参数是数组时,它的工作方式与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
但是,您可能已经意识到,如果我们将一个参数splating到
push
,我们可以在一个
push
中splat所有参数,而不是调用
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
另外,您可以做的另一件事是将
return除非root
替换为
root&&…
(或
root和…
)。我觉得这很诱人,但也有点臭,所以我把它留给你:

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)

它们都是等价的。

如果你想在这件事上变得非常时髦,这里有一个实用的方法

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)

它们都是等价的。

这太棒了!谢谢!只是一个简单的后续问题,你认为这值得做吗?我的意思是,未干燥和干燥的代码的长度几乎相同,干燥的代码看起来更复杂。在现实世界的制作中,你会建议这样做吗?@marwei是的,毫无疑问。重复的代码是维护的噩梦。我认为@marwei的担心是完全正确的。他们的原始代码有一些重复,但其意图非常明确。Chris的代码本身并不坏,但不太明显@marwei的代码是相当自我记录的,但如果没有几行注释,我不会将Chris的
traverse\u recurse
方法提交给我的回购协议。可读性对于可维护性和干燥性同样重要。这太棒了!比