【问题标题】:How do I add 'each' method to Ruby object (or should I extend Array)?如何将“每个”方法添加到 Ruby 对象(或者我应该扩展 Array)?
【发布时间】:2010-01-17 04:58:04
【问题描述】:

我有一个对象 Results,其中包含 result 对象数组以及有关数组中对象的一些缓存统计信息。我希望 Results 对象能够表现得像一个数组。我的第一个切入点是添加这样的方法

 def <<(val)
    @result_array << val
 end

这感觉很像 c,我知道 Ruby 有更好的方法。

我也希望能够做到这一点

 Results.each do |result|   
    result.do_stuff   
 end

但我不确定each 方法实际上在做什么。

目前我只是通过一个方法返回底层数组并在其上调用每个数组,这似乎不是最优雅的解决方案。

任何帮助将不胜感激。

【问题讨论】:

    标签: ruby


    【解决方案1】:

    对于实现类数组方法的一般情况,是的,您必须自己实现它们。 Vava 的回答就是一个例子。但是,在您提供的情况下,您真正​​想做的是将处理each(可能还有其他一些方法)的任务委托给包含的数组,并且可以自动化。

    require 'forwardable'
    
    class Results
      include Enumerable
      extend Forwardable
      def_delegators :@result_array, :each, :<<
    end
    

    这个类将获得 Array 的所有 Enumerable 行为以及 Array &lt;&lt; 运算符,并且都将通过内部数组。


    请注意,当您将代码从 Array 继承切换到此技巧时,您的 &lt;&lt; 方法将开始返回而不是对象本身,就像真正的 Array 的 &lt;&lt; 所做的那样——这可能会导致您每次都声明另一个变量使用&lt;&lt;

    【讨论】:

    • 我知道 ruby​​ 库中应该有类似的东西。现在,如果我能在这个答案上加注星标以便以后找到它……
    • 你需要extend Forwardable,而不是include
    • 关于可转发的大量信息,但这样做不允许他操纵值。他正在调用一个方法,所以我假设他想在返回数据之前对数据做一些事情。不过我可能是错的。
    【解决方案2】:

    each 只需遍历数组并使用每个元素调用给定块,这很简单。由于在类内部也使用数组,因此您只需将 each 方法重定向到数组中的一个,这样既快速又易于阅读/维护。

    class Result
        include Enumerable
    
        def initialize
            @results_array = []
        end
    
        def <<(val)
            @results_array << val
        end
    
        def each(&block)
            @results_array.each(&block)
        end
    end
    
    r = Result.new
    
    r << 1
    r << 2
    
    r.each { |v|
       p v
    }
    
    #print:
    # 1
    # 2
    

    请注意,我混入了Enumerable。这将免费为您提供一堆数组方法,如all?map 等。

    顺便说一句,使用 Ruby,您可以忘记继承。你不需要接口继承,因为鸭子类型并不真正关心实际类型,你也不需要代码继承,因为 mixin 更适合这类事情。

    【讨论】:

    • 实际上并没有解释“每个”方法的实现,这就是问题所在。
    • @Ed:当然可以。您可以让 each 方法执行任何必要的操作来遍历对象的元素,这取决于类的实际实现。在这种情况下,显而易见的选择是让 each 遍历数组的元素。
    • 对我来说似乎是一个简单的包装方法。
    • 是的,它是一个简单的包装器,即聚合模式,尽可能优先于继承。
    • 我不认为存在“聚合模式”,或者至少不是众所周知的。我认为您想要继承与组合:stackoverflow.com/questions/760473/…
    【解决方案3】:

    你的

    要让一个类表现得像一个数组,而不是直接从 Array 继承,你可以混入 Enumerable 模块并添加一些方法。

    这是一个例子(包括 Chuck 对使用 Forwardable 的极好建议):

    # You have to require forwardable to use it
    require "forwardable"
    
    class MyArray
      include Enumerable
      extend Forwardable
    
      def initialize
        @values = []
      end
    
      # Map some of the common array methods to our internal array
      def_delegators :@values, :<<, :[], :[]=, :last
    
      # I want a custom method "add" available for adding values to our internal array
      def_delegator :@values, :<<, :add
    
      # You don't need to specify the block variable, yield knows to use a block if passed one
      def each
        # "each" is the base method called by all the iterators so you only have to define it
        @values.each  do |value| 
          # change or manipulate the values in your value array inside this block
          yield value
        end
      end
    
    end
    
    m = MyArray.new
    
    m << "fudge"
    m << "icecream"
    m.add("cake")
    
    # Notice I didn't create an each_with_index method but since 
    # I included Enumerable it knows how and uses the proper data.
    m.each_with_index{|value, index| puts "m[#{index}] = #{value}"}
    
    puts "What about some nice cabbage?"
    m[0] = "cabbage"
    puts "m[0] = #{m[0]}"
    
    puts "No! I meant in addition to fudge"
    m[0] = "fudge"
    m << "cabbage"
    puts "m.first = #{m.first}"
    puts "m.last = #{m.last}"
    

    哪些输出:

    m[0] = fudge
    m[1] = icecream
    m[2] = cake
    What about some nice cabbage?
    m[0] = cabbage
    No! I meant in addition to fudge
    m.first = fudge
    m.last = cabbage
    

    【讨论】:

      【解决方案4】:

      这感觉很像 c,我知道 Ruby 有更好的办法。

      如果你想让一个对象“感觉”像一个数组,那么重写

      但不确定每个方法是什么 确实在幕后工作。

      Array 的 each 方法只是循环遍历所有元素(使用 for 循环,我认为)。如果您想添加自己的 each 方法(这也是非常 'Ruby'-ish),您可以执行以下操作:

      def each
        0.upto(@result_array.length - 1) do |x|
          yield @result_array[x]
        end
      end
      

      【讨论】:

      • 既然 @result_array 已经是 Array 的方法,为什么不使用 @result_array 进行迭代?
      【解决方案5】:

      如果您创建一个继承自 Array 的类 Results,您将继承所有功能。

      然后您可以通过重新定义来补充需要更改的方法,并且您可以为旧功能调用 super。

      例如:

      class Results < Array
        # Additional functionality
        def best
          find {|result| result.is_really_good? }
        end
      
        # Array functionality that needs change
        def compact
          delete(ininteresting_result)
          super
        end
      end
      

      或者,您可以使用内置库forwardable。如果您不能从 Array 继承,这将特别有用,因为您需要从另一个类继承:

      require 'forwardable'
      class Results
        extend Forwardable
        def_delegator :@result_array, :<<, :each, :concat # etc...
      
        def best
          @result_array.find {|result| result.is_really_good? }
        end
      
        # Array functionality that needs change
        def compact
          @result_array.delete(ininteresting_result)
          @result_array.compact
          self
        end
      end
      

      这两种形式,你都可以随意使用:

      r = Results.new
      r << some_result
      r.each do |result|
        # ...
      end
      r.compact
      puts "Best result: #{r.best}"
      

      【讨论】:

      • 扩展类 Array 的解决方案部分起作用,因为 object.compact.best 不起作用。方法不可链接,compact 返回 Array 对象,而不是 Results 对象。 :(
      【解决方案6】:

      不确定我是否添加了任何新内容,但决定显示一个非常简短的代码,我希望我能在答案中找到它以快速显示可用选项。这里没有@shelvacu 所说的枚举器。

      class Test
         def initialize
           @data = [1,2,3,4,5,6,7,8,9,0,11,12,12,13,14,15,16,172,28,38]
         end
      
         # approach 1
         def each_y
           @data.each{ |x| yield(x) }
         end
      
         #approach 2
         def each_b(&block)
           @data.each(&block)
         end  
      end
      

      让我们检查一下性能:

      require 'benchmark'
      test = Test.new
      n=1000*1000*100
      Benchmark.bm do |b|
        b.report { 1000000.times{ test.each_y{|x| @foo=x} } }
        b.report { 1000000.times{ test.each_b{|x| @foo=x} } }
      end
      

      结果如下:

             user     system      total        real
         1.660000   0.000000   1.660000 (  1.669462)
         1.830000   0.000000   1.830000 (  1.831754)
      

      这意味着 yield 比 &block 我们已经知道的要快一点。

      更新:这是 IMO 创建 each 方法的最佳方式,该方法还负责返回枚举器

      class Test
        def each
          if block_given?
            @data.each{|x| yield(x)}  
          else    
            return @data.each
          end  
        end  
      end
      

      【讨论】:

        【解决方案7】:

        如果你真的想创建自己的#each 方法,并且假设你不想转发,如果没有给出块,你应该返回一个 Enumerator

        class MyArrayLikeClass
          include Enumerable
        
          def each(&block)
            return enum_for(__method__) if block.nil?
            @arr.each do |ob|
              block.call(ob)
            end
          end
        end
        

        如果没有给出块,这将返回一个 Enumerable 对象,允许 Enumerable 方法链接

        【讨论】:

        • 对我来说,Ruby 允许用户完全模仿本地语言特性这一事实使 Ruby 如此美丽。
        猜你喜欢
        • 1970-01-01
        • 2017-07-04
        • 1970-01-01
        • 1970-01-01
        • 2012-07-05
        • 1970-01-01
        • 1970-01-01
        • 2023-04-04
        • 1970-01-01
        相关资源
        最近更新 更多