[英]Ruby (Monkey Patching Array)
返回獲取更多有關Bloc課程的幫助。 決定帶給你們一些我正在使用Monkey Patching the Array Class的問題。 這個任務有8個規格可以滿足,我現在卡在一個左邊,我不知道該怎么辦。
我只會給你RSpecs和我遇到麻煩的部分的書面要求,因為其他一切似乎都在傳遞。 此外,我將包括他們開始給我的啟動腳手架,因此代碼中沒有混淆或無用的添加。
以下是Array Class Monkey Patch的書面要求:
編寫一個在Array
類的實例上調用的新new_map
方法。 它應該使用它被調用的數組作為隱式( self
)參數,但在其他方面表現相同。 ( 未完成 )
寫一個new_select! 行為與select類似的方法,但會改變調用它的數組。 它可以使用Ruby的內置集合選擇方法。 ( 完整 )
以下是有關Array類需要滿足的RSpec:
注意:“返回具有更新值的數組”是唯一未傳遞的規范。
describe Array do
describe '#new_map' do
it "returns an array with updated values" do
array = [1,2,3,4]
expect( array.new_map(&:to_s) ).to eq( %w{1 2 3 4} )
expect( array.new_map{ |e| e + 2 } ).to eq( [3, 4, 5, 6] )
end
it "does not call #map" do
array = [1,2,3,4]
array.stub(:map) { '' }
expect( array.new_map(&:to_s) ).to eq( %w{1 2 3 4} )
end
it "does not change the original array" do
array = [1,2,3,4]
expect( array.new_map(&:to_s) ).to eq( %w{1 2 3 4} )
expect( array ).to eq([1,2,3,4])
end
end
describe '#new_select!' do
it "selects according to the block instructions" do
expect( [1,2,3,4].new_select!{ |e| e > 2 } ).to eq( [3,4] )
expect( [1,2,3,4].new_select!{ |e| e < 2 } ).to eq( [1] )
end
it "mutates the original collection" do
array = [1,2,3,4]
array.new_select!(&:even?)
expect(array).to eq([2,4])
end
end
end
這是他們開始使用的腳手架:
class Array
def new_map
end
def new_select!(&block)
end
end
最后,這是我的代碼:
class Array
def new_map
new_array = []
self.each do |num|
new_array << num.to_s
end
new_array
end
def new_select!(&block)
self.select!(&block)
end
end
Ruby數組類:
map { |item| block } → new_ary
block
就像一個方法,並在方法調用后指定一個塊,例如:
[1, 2, 3].map() {|x| x*2} #<---block
^
|
method call(usually written without the trailing parentheses)
該塊被隱式發送到該方法,並且在該方法內部,您可以使用yield
調用該塊。
yield
- >調用方法調用后指定的塊。 在ruby中, yield
等價於yield()
,這在概念上等同於調用塊: block()
。
yield(x)
- >調用在方法調用之后指定的塊,向它發送參數x,這在概念上等同於調用塊: block(x)
。
那么,這是你如何實現new_map():
class Array
def new_map
result = []
each do |item|
result << yield(item)
end
result
end
end
arr = [1, 2, 3].new_map {|x| x*2}
p arr
--output:--
[2, 4, 6]
這個注釋有點高級,但實際上你不必編寫self.each()
來調用self.each()
中的each()方法。 所有方法都由某個對象調用,即點左側的對象,稱為接收器 。 例如,當你寫:
self.each {....}
self是方法調用each()的接收者。
如果您沒有指定receiver
並且只寫:
each {....}
...然后對於接收器,ruby使用當時分配給self
變量的任何對象。 在上面的new_map()中,ruby會將調用new_map()方法的Array分配給self,因此each()將遍歷該Array中的項目。
你必須對自變量有點小心,因為ruby不斷地改變自變量的值而不告訴你。 因此,您必須知道 ruby在代碼中的任何特定點為自變量賦予了什么 - 這是經驗所帶來的。 雖然,如果您想知道ruby在代碼中的某個特定點為自己分配了什么對象,您可以簡單地寫:
puts self
經驗豐富的Ruby開發者將烏鴉和咯咯叫舌頭,如果他們看到你寫self.each {...}
里面new_map(),但在我看來代碼的清晰度勝過代碼trickiness,因為它更有意義,初學者寫自那里,去前進並做到這一點。 當您獲得更多經驗並想要炫耀時,您可以在不需要時消除顯式接收器。 這與具有明確回報的情況類似:
def some_method
...
return result
end
和隱含的回報:
def some_method
...
result
end
請注意,您可以像這樣編寫new_map():
class Array
def new_map(&my_block) #capture the block in a variable
result = []
each do |item|
result << my_block.call(item) #call the block
end
result
end
end
arr = [1, 2, 3].new_map {|x| x*2}
p arr
--output:--
[2, 4, 6]
將其與使用yield()的示例進行比較。 當你使用yield()時,就好像ruby為你創建一個名為yield
的參數變量來捕獲塊。 但是,使用yield會使用不同的語法來調用塊,即()
,或者如果它們不是塊的參數,則可以刪除括號 - 就像調用方法時一樣。 另一方面,當您創建自己的參數變量來捕獲塊時,例如def new_map(&my_block)
,您必須使用不同的語法來調用塊:
my_block.call(arg1, ...)
要么:
myblock[arg1, ...]
請注意,#2就像調用方法的語法一樣 - 除了用[]
代替()
。
有經驗的rubyists將再次使用yield來調用塊而不是在參數變量中捕獲塊。 但是,在某些情況下,您需要在參數變量中捕獲塊,例如,如果要將塊傳遞給另一個方法。
看看這里的規格:
it "returns an array with updated values" do
array = [1,2,3,4]
expect( array.new_map(&:to_s) ).to eq( %w{1 2 3 4} )
expect( array.new_map{ |e| e + 2 } ).to eq( [3, 4, 5, 6] )
end
看起來他們只是想讓你重新編寫Array.map
以便它可以與任何給定的塊一起使用。 在您的實現中,您告訴方法以非常特定的方式工作,即在所有數組元素上調用.to_s
。 但是你不希望它總是對數組元素進行字符串化。 在調用方法時,您希望它對每個元素執行任何塊提供。 嘗試這個:
class Array
def new_map
new_array = []
each do |num|
new_array << yield(num)
end
new_array
end
end
請注意,在我的示例中,方法定義未指定要對self
每個元素執行的任何特定操作。 它只是循環遍歷數組的每個元素,產生元素( num
)到調用.new_map
時傳遞的任何塊,並將結果鏟到new_array
變量中。
通過.new_map
和給定array = [1,2,3,4]
,您可以調用array.new_map(&:to_s)
( 該塊是對數組的每個元素執行的任意操作的位置指定)並獲取["1","2","3","4"]
或者你可以調用array.new_map { |e| e + 2 }
array.new_map { |e| e + 2 }
並獲得[3,4,5,6]
。
我想談談new_select!
。
選擇vs選擇!
你有:
class Array
def new_select!(&block)
self.select!(&block)
end
end
您可以改為寫:
class Array
def new_select!
self.select! { |e| yield(e) }
end
end
這使用方法Array#select! 。 你說可以使用Array #select ,但是沒有提到select!
。 如果你不能使用select!
,你必須做這樣的事情:
class Array
def new_select!(&block)
replace(select(&block))
end
end
我們來試試吧:
a = [1,2,3,4,5]
a.new_select! { |n| n.odd? }
#=> [1, 3, 5]
a #=> [1, 3, 5]
顯式vs隱式接收器
請注意,我沒有為Array#replace和Array#select
方法提供任何明確的接收器。 當沒有明確的接收者時,Ruby假定它是self
,即a
。 因此,Ruby評估replace(select(&block))
,好像它是用顯式接收器編寫的:
self.replace(self.select(&block))
由您來決定是否要包含self.
。 有些Rubiests有,有些則沒有。 你會注意到self.
不包含在Ruby中實現的Ruby內置方法中。 另一件事: self.
在某些情況下需要避免歧義。 例如,如果taco=
是實例變量@taco
的setter,則必須編寫self.taco = 7
來告訴Ruby你指的是setter方法。 如果你寫taco = 7
,Ruby會假設你要創建一個局部變量taco
並將其值設置為7。
兩種形式的選擇!
你的方法是new_select!
直接替代select!
? 也就是說,兩種方法在功能上是否相同? 如果你看一下Array#select!
的文檔Array#select!
,你會發現它有兩種形式,一種你模仿的形式,另一種你沒有實現的形式。
如果select!
沒有給出一個塊,它返回一個枚舉器。 現在你為什么要這樣做? 假設你想寫:
a = [1,2,3,4,5]
a.select!.with_index { |n,i| i < 2 }
#=> [1, 2]
有select!
被給了一塊? 不,所以它必須返回一個枚舉器,它成為方法Enumerator #with_index的接收者。
我們來試試吧:
enum0 = a.select!
#=> #<Enumerator: [1, 2, 3, 4, 5]:select!>
現在:
enum1 = enum0.with_index
#=> #<Enumerator: #<Enumerator: [1, 2, 3, 4, 5]:select!>:with_index>
您可以將enum1
視為“復合枚舉器”。 仔細查看定義enum1
時Ruby返回的對象的描述(上面)。
我們可以通過將枚舉器轉換為數組來查看枚舉器的元素:
enum1.to_a
# => [[1, 0], [2, 1], [3, 2], [4, 3], [5, 4]]
這五個元素中的每一個都傳遞給塊,並由Enumerator#each (它們調用Array#each )分配給塊變量。
足夠的,但關鍵是當沒有給出塊時讓方法返回枚舉器是允許我們鏈接方法的原因。
您沒有確保new_select!
的測試new_select!
當沒有給出塊時返回一個枚舉器,所以也許這不是預期的,但為什么不給它一個去?
class Array
def new_select!(&block)
if block_given?
replace(select(&block))
else
to_enum(:new_select!)
end
end
end
試試吧:
a = [1,2,3,4,5]
a.new_select! { |n| n.odd? }
#=> [1, 3, 5]
a #=> [1, 3, 5]
a = [1,2,3,4,5]
enum2 = a.new_select!
#=> #<Enumerator: [1, 2, 3, 4, 5]:new_select!>
enum2.each { |n| n.odd? }
#=> [1, 3, 5]
a #=> [1, 3, 5]
a = [1,2,3,4,5]
enum2 = a.new_select!
#=> #<Enumerator: [1, 2, 3, 4, 5]:new_select!>
enum2.with_index.each { |n,i| i>2 }
#=> [4, 5]
a #=> [4, 5]
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.