【问题标题】:Passing a method as an argument in self将方法作为参数传递给 self
【发布时间】:2020-08-21 15:33:09
【问题描述】:

我是 Ruby 新手,目前正在学习数据结构。我已经定义了自己的类,我想将类中的一个方法传递给类中的另一个方法。我尝试执行此操作的方法似乎不起作用。

这是我目前的代码:

class BinaryTree
  attr_accessor :left_child, :right_child, :node
  def initialize(node, left_child, right_child)
    @left_child = left_child
    @right_child = right_child
    @node = node
  end

  def to_s
    "node: #{@node}, left: #{@left_child}, right: #{@right_child}"
  end

  def self.print_node
    p @node
  end

  def traverse(some_func)
    if !left_child.nil?
        left_child.traverse(some_func)
    end
    if !right_child.nil?
        right_child.traverse(some_func)
    end
    self.some_func
  end
end

bst = BinaryTree.new(50, 
      BinaryTree.new(30, BinaryTree.new(5, nil, nil), BinaryTree.new(20, nil, nil)),
      BinaryTree.new(60, BinaryTree.new(45, nil, nil), BinaryTree.new(70, BinaryTree.new(65, nil, nil), BinaryTree.new(80, nil, nil))) )


puts bst.traverse(bst.print_node)

基本上我想遍历二叉树并在每个子节点上调用一些函数。在这种情况下,我只是想传递一个函数来打印节点,但是,此代码会导致以下错误。

 `<main>': undefined method `print_node' for #<BinaryTree:0x00007f82f88530b8> (NoMethodError)

我希望有人能解释为什么这会失败,以及我应该做些什么来完成这个。

【问题讨论】:

  • 当您编写self.some_method 时,您定义了一个类方法,而当您定义一个没有self 的方法时,您定义了一个实例方法。 bstBinaryTree 的实例对象,并且只能访问定义的实例方法。我不会评论代码,但删除 self 应该足以在您尝试访问它时调用 print_node。
  • def self.print_node中删除self
  • 谢谢。这确实有助于访问根节点,但是,它仍然没有在 traverse 方法中调用该方法。我相信,如果我错了,请纠正我,但在调用 bst.traverse(bst.print_node) 时,它正在调用该函数并打印根节点,但之后的所有内容都没有被调用。从遍历中调用该方法的正确方法是什么?调用 self.some_func 似乎是在引用数据的类而不是 BinaryTree 对象的类。

标签: ruby


【解决方案1】:

我想将类中的一个方法传递给类中的另一个方法

虽然您可以这样做,但在 Ruby 中这是通过 blocks 完成的。

bst.traverse { |node|
  p node
}

Ruby 中看起来像循环和块的东西实际上是称为Procs 的小型匿名函数。以上是去掉了语法糖的上述内容。

block = Proc.new { |node| p node }
bst.traverse(&block)

&amp; 表示该参数将被视为一个块。

你会写 traverse 来获取这样的块:

  def traverse(&block)
    if !left_child.nil?
      left_child.traverse(&block)
    end
    if !right_child.nil?
      right_child.traverse(&block)
    end
    
    block.call(@node)
  end

同样的事情,&amp; says to treat the argument as a block。我们递归地传递块。 block 是一个 Proc 对象,我们用 #call 调用它,并传入我们喜欢的任何参数。

您还会看到人们使用yield。这基本上做同样的事情,但不必明确定义&amp;block。现在,明确地这样做,直到你感到舒服为止。在这种情况下,我们必须明确地这样做,因为我们正在递归地传递块。


Procs 的作用类似于普通函数,但有两个关键区别:

  1. return 不是从 Proc 返回,而是从周围的方法返回。
  2. 它不检查所需的参数。

Lambda 的工作方式更像是检查参数并返回 return 的普通函数。你可以在Lambda and non-lambda semantics了解更多。


请参阅Blocks: What Are They Good For? 了解更多信息。

【讨论】:

    【解决方案2】:

    您不能像 Javascript 等其他语言一样“将一个方法传递给另一个方法”。 可能的,使用Object#method 或将符号传递给Object#send,但更常见的是使用匿名函数,如下所示:

      # Note I removed the `self.` here
      def print_node
        p @node
      end
    
      def traverse(&block)
        if !left_child.nil?
          left_child.traverse(&block)
        end
        if !right_child.nil?
          right_child.traverse(&block)
        end
        block.call(self)
      end
    

    你会这样称呼它:

     bst.traverse { |tree| tree.print_node }
    

    您也可以将其缩短为:

      bst.traverse(&:print_node)
    

    我建议阅读 Ruby 中的匿名函数。这里有点太多了,但是有很多关于它的好资源

    【讨论】:

    • 最好写成bst.traverse { |tree| tree.print_node },因为它是传入的树。或者不传递参数:bst.traverse { bst.print_node }。真正的节点应该是它自己的类。
    • 这很好,node 变量最好称为tree。但是您的第二个示例不起作用-块中引用的树不一定是顶层树。
    猜你喜欢
    • 1970-01-01
    • 2022-01-05
    • 2020-05-23
    • 2012-02-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-03-16
    • 1970-01-01
    相关资源
    最近更新 更多