如何向Ruby对象添加“each”方法(或者应该扩展数组)?

时间:2023-02-03 07:06:20

I have an object Results that contains an array of result objects along with some cached statistics about the objects in the array. I'd like the Results object to be able to behave like an array. My first cut at this was to add methods like this

我有一个对象结果,它包含一个结果对象数组,以及一些关于数组中对象的缓存统计信息。我希望结果对象能够表现得像一个数组。我首先要做的就是添加这样的方法

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

This feels very c-like and I know Ruby has better way.

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

I'd also like to be able to do this

我也想这样做

 Results.each do |result|   
    result.do_stuff   
 end

but am not sure what the each method is really doing under the hood.

但我不确定每个方法在幕后真正在做什么。

Currently I simply return the underlying array via a method and call each on it which doesn't seem like the most-elegant solution.

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

Any help would be appreciated.

如有任何帮助,我们将不胜感激。

7 个解决方案

#1


61  

For the general case of implementing array-like methods, yes, you have to implement them yourself. Vava's answer shows one example of this. In the case you gave, though, what you really want to do is delegate the task of handling each (and maybe some other methods) to the contained array, and that can be automated.

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

require 'forwardable'

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

This class will get all of Array's Enumerable behavior as well as the Array << operator and it will all go through the inner array.

这个类将获得数组的所有可枚举行为以及数组<< <运算符,它将通过内部数组。< p>


Note, that when you switch your code from Array inheritance to this trick, your << methods would start to return not the object intself, like real Array's << did -- this can cost you declaring another variable everytime you use <<.

注意,当您将代码从数组继承切换到这个技巧时,您的< <方法将开始返回而不是对象intself,就像实数组的<< did一样——这将使您每次使用<<时都要声明另一个变量。< p>

#2


35  

each just goes through array and call given block with each element, that is simple. Since inside the class you are using array as well, you can just redirect your each method to one from array, that is fast and easy to read/maintain.

每个元素都经过数组并调用给定的块和每个元素,这很简单。由于在类中也使用数组,所以可以将每个方法从数组重定向到一个方法,这是快速且容易读取/维护的。

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

Note that I have mixed in Enumerable. That will give you a bunch of array methods like all?, map, etc. for free.

注意,我已经混合了枚举。这会给你一系列的数组方法?、免费地图等。

BTW with Ruby you can forget about inheritance. You don't need interface inheritance because duck-typing doesn't really care about actual type, and you don't need code inheritance because mixins are just better for that sort of things.

顺便说一句,使用Ruby您可以忘记继承。您不需要接口继承,因为duck-typing并不真正关心实际类型,而且您不需要代码继承,因为mixins只适合于此类事情。

#3


8  

This feels very c-like and I know Ruby has better way.

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

If you want an object to 'feel' like an array, than overriding << is a good idea and very 'Ruby'-ish.

如果你想让一个对象“感觉”像一个数组,那么重写<< <是一个好主意,而且非常像ruby。< p>

but am not sure what the each method is really doing under the hood.

但我不确定每个方法在幕后真正在做什么。

The each method for Array just loops through all the elements (using a for loop, I think). If you want to add your own each method (which is also very 'Ruby'-ish), you could do something like this:

数组的每个方法只是循环遍历所有元素(我认为是使用for循环)。如果您想添加您自己的每个方法(也是非常“Ruby”-ish),您可以这样做:

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

#4


8  

Your << method is perfectly fine and very Ruby like.

您的 <方法非常好,非常像ruby。< p>

To make a class act like an array, without actually inheriting directly from Array, you can mix-in the Enumerable module and add a few methods.

要使类像数组一样运行,而不直接从数组继承,您可以在可枚举模块中混合并添加一些方法。

Here's an example (including Chuck's excellent suggestion to use Forwardable):

这里有一个例子(包括Chuck使用可转发的优秀建议):

# 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}"

Which outputs:

输出:

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

#5


5  

If you create a class Results that inherit from Array, you will inherit all the functionality.

如果您创建一个从数组继承的类结果,您将继承所有功能。

You can then supplement the methods that need change by redefining them, and you can call super for the old functionality.

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

For example:

例如:

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

Alternatively, you can use the builtin library forwardable. This is particularly useful if you can't inherit from Array because you need to inherit from another class:

或者,您可以使用可转发的内建库。如果您不能从数组继承,因为您需要从另一个类继承,那么这一点特别有用:

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

In both of these forms, you can use it as you want:

在这两种形式中,你可以任意使用:

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

#6


4  

Not sure I'm adding anything new, but decided to show a very short code that I wish I could have found in the answers to quickly show available options. Here it is without the enumerator that @shelvacu talks about.

我不确定我添加了什么新东西,但决定显示一个非常短的代码,我希望我能在答案中找到,以快速显示可用的选项。这里没有@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

Lets check performance:

允许检查性能:

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

Here's the result:

结果:

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

This means yield is marginally faster than &block what we already know btw.

这意味着收益率比我们已经知道的要快一些。

UPDATE: This is IMO the best way to create an each method which also takes care of returning an enumerator

更新:在我看来,这是创建每个方法的最佳方式,它还负责返回一个枚举器

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

#7


2  

If you really do want to make your own #each method, and assuming you don't want to forward, you should return an Enumerator if no block is given

如果您确实想要创建自己的#每个方法,并且假设您不想转发,那么如果没有给出块,您应该返回一个枚举器。

class MyArrayLikeClass
  include Enumerable

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

This will return an Enumerable object if no block is given, allowing Enumerable method chaining

如果没有给定块,则返回可枚举对象,允许可枚举方法链接

#1


61  

For the general case of implementing array-like methods, yes, you have to implement them yourself. Vava's answer shows one example of this. In the case you gave, though, what you really want to do is delegate the task of handling each (and maybe some other methods) to the contained array, and that can be automated.

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

require 'forwardable'

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

This class will get all of Array's Enumerable behavior as well as the Array << operator and it will all go through the inner array.

这个类将获得数组的所有可枚举行为以及数组<< <运算符,它将通过内部数组。< p>


Note, that when you switch your code from Array inheritance to this trick, your << methods would start to return not the object intself, like real Array's << did -- this can cost you declaring another variable everytime you use <<.

注意,当您将代码从数组继承切换到这个技巧时,您的< <方法将开始返回而不是对象intself,就像实数组的<< did一样——这将使您每次使用<<时都要声明另一个变量。< p>

#2


35  

each just goes through array and call given block with each element, that is simple. Since inside the class you are using array as well, you can just redirect your each method to one from array, that is fast and easy to read/maintain.

每个元素都经过数组并调用给定的块和每个元素,这很简单。由于在类中也使用数组,所以可以将每个方法从数组重定向到一个方法,这是快速且容易读取/维护的。

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

Note that I have mixed in Enumerable. That will give you a bunch of array methods like all?, map, etc. for free.

注意,我已经混合了枚举。这会给你一系列的数组方法?、免费地图等。

BTW with Ruby you can forget about inheritance. You don't need interface inheritance because duck-typing doesn't really care about actual type, and you don't need code inheritance because mixins are just better for that sort of things.

顺便说一句,使用Ruby您可以忘记继承。您不需要接口继承,因为duck-typing并不真正关心实际类型,而且您不需要代码继承,因为mixins只适合于此类事情。

#3


8  

This feels very c-like and I know Ruby has better way.

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

If you want an object to 'feel' like an array, than overriding << is a good idea and very 'Ruby'-ish.

如果你想让一个对象“感觉”像一个数组,那么重写<< <是一个好主意,而且非常像ruby。< p>

but am not sure what the each method is really doing under the hood.

但我不确定每个方法在幕后真正在做什么。

The each method for Array just loops through all the elements (using a for loop, I think). If you want to add your own each method (which is also very 'Ruby'-ish), you could do something like this:

数组的每个方法只是循环遍历所有元素(我认为是使用for循环)。如果您想添加您自己的每个方法(也是非常“Ruby”-ish),您可以这样做:

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

#4


8  

Your << method is perfectly fine and very Ruby like.

您的 <方法非常好,非常像ruby。< p>

To make a class act like an array, without actually inheriting directly from Array, you can mix-in the Enumerable module and add a few methods.

要使类像数组一样运行,而不直接从数组继承,您可以在可枚举模块中混合并添加一些方法。

Here's an example (including Chuck's excellent suggestion to use Forwardable):

这里有一个例子(包括Chuck使用可转发的优秀建议):

# 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}"

Which outputs:

输出:

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

#5


5  

If you create a class Results that inherit from Array, you will inherit all the functionality.

如果您创建一个从数组继承的类结果,您将继承所有功能。

You can then supplement the methods that need change by redefining them, and you can call super for the old functionality.

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

For example:

例如:

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

Alternatively, you can use the builtin library forwardable. This is particularly useful if you can't inherit from Array because you need to inherit from another class:

或者,您可以使用可转发的内建库。如果您不能从数组继承,因为您需要从另一个类继承,那么这一点特别有用:

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

In both of these forms, you can use it as you want:

在这两种形式中,你可以任意使用:

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

#6


4  

Not sure I'm adding anything new, but decided to show a very short code that I wish I could have found in the answers to quickly show available options. Here it is without the enumerator that @shelvacu talks about.

我不确定我添加了什么新东西,但决定显示一个非常短的代码,我希望我能在答案中找到,以快速显示可用的选项。这里没有@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

Lets check performance:

允许检查性能:

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

Here's the result:

结果:

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

This means yield is marginally faster than &block what we already know btw.

这意味着收益率比我们已经知道的要快一些。

UPDATE: This is IMO the best way to create an each method which also takes care of returning an enumerator

更新:在我看来,这是创建每个方法的最佳方式,它还负责返回一个枚举器

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

#7


2  

If you really do want to make your own #each method, and assuming you don't want to forward, you should return an Enumerator if no block is given

如果您确实想要创建自己的#每个方法,并且假设您不想转发,那么如果没有给出块,您应该返回一个枚举器。

class MyArrayLikeClass
  include Enumerable

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

This will return an Enumerable object if no block is given, allowing Enumerable method chaining

如果没有给定块,则返回可枚举对象,允许可枚举方法链接