我有一个对象结果包含result
个对象的数组,以及有关数组中对象的一些缓存统计信息。我希望Results对象能够像数组一样运行。我的第一个切入是添加这样的方法
def <<(val)
@result_array << val
end
这感觉很像c,我知道Ruby有更好的方法。
我也希望能够做到这一点
Results.each do |result|
result.do_stuff
end
但我不确定each
方法究竟在做什么。
目前我只是通过一种方法返回底层数组,然后调用它们,这似乎不是最优雅的解决方案。
任何帮助都将不胜感激。
答案 0 :(得分:61)
对于实现类似数组的方法的一般情况,是的,你必须自己实现它们。 Vava的答案显示了这方面的一个例子。但是,在您提供的情况下,您真正想要做的是将处理each
(可能还有其他一些方法)的任务委托给包含的数组,并且可以自动完成。
require 'forwardable'
class Results
include Enumerable
extend Forwardable
def_delegators :@result_array, :each, :<<
end
这个类将获得Array的所有Enumerable行为以及Array <<
运算符,它将全部通过内部数组。
注意,当你将代码从数组继承切换到这个技巧时,你的<<
方法将开始返回不是自身的对象,就像真正的数组的<<
那样 - 这可能会花费你的声明每次使用<<
时的另一个变量。
答案 1 :(得分:37)
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
等数组方法。
答案 2 :(得分:9)
这感觉很像,我知道Ruby 有更好的方法。
如果你想让一个对象“感觉”像一个数组,而不是覆盖&lt;&lt;是一个好主意,非常'Ruby'-ish。
但我不确定每种方法是什么 真的是在幕后做。
Array的每个方法只循环遍历所有元素(使用for循环,我认为)。如果你想添加自己的每个方法(也非常'Ruby'-ish),你可以这样做:
def each
0.upto(@result_array.length - 1) do |x|
yield @result_array[x]
end
end
答案 3 :(得分:8)
你的&lt;&lt;方法非常好,非常像Ruby。
要使类像数组一样,而不直接从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 :(得分: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}"
答案 5 :(得分:4)
不确定我是否添加了任何新内容,但我决定展示一个非常简短的代码,我希望我能在答案中找到这些代码以快速显示可用选项。这里没有@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
稍微快于阻止我们已经知道的事情。
UPDATE :这是IMO创建每个方法的最佳方式,该方法也负责返回枚举器
class Test
def each
if block_given?
@data.each{|x| yield(x)}
else
return @data.each
end
end
end
答案 6 :(得分:2)
如果你真的想制作自己的#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方法链接