Ruby Blocks (Yield)
I'm just getting started with Ruby, I'm pretty up to date with my Bloc course, but now I'm stuck with an exercise dealing with harvest and blocks (which I find the hardest concept so far when it comes to learning ruby).
Here are the required specifications in plain text format:
- Define new_map method
- It should take an array as an argument and return a new array, modified according to the instructions passed as a block.
- You cannot use .map or .map! Methods
- However, feel free to use each in the method
- You need to store the return value from each block call in a new array
- It should display any object
Here are the RSpecs to follow:
describe "new_map" do
it "should not call map or map!" do
a = [1, 2, 3]
a.stub(:map) { '' }
a.stub(:map!) { '' }
expect( new_map(a) { |i| i + 1 } ).to eq([2, 3, 4])
end
it "should map any object" do
a = [1, "two", :three]
expect( new_map(a) { |i| i.class } ).to eq([Fixnum, String, Symbol])
end
end
Here is the original def method they gave me to start with:
def new_map(array)
new_array = []
array.each do |item|
# invoke the block, and add its return value to the new array
end
end
Then here is my current code (Updated):
def new_map(a)
new_array = []
a.each do |item|
# invoke the block, and add its return value to the new array.
yield(item, new_array)
end
end
a = [2, 3, 4]
new_map(a) do |i, e|
e << i
end
Finally, when I post the current code I just listed, I get the following errors (Updated):
new_map should not be named map or map! (Incomplete)
expected: [2, 3, 4]
got: [1, 2, 3]
(compared using ==)
exercise_spec.rb:9:in `block (2 levels) in <top (required)>'
new_map should map any object (INCOMPLETE)
expected: [Fixnum, String, Symbol]
got: [1, "two", :three]
(compared using ==)
exercise_spec.rb:14:in `block (2 levels) in <top (required)>'
source to share
What you don't understand is that yield can make a difference. The last statement executed in the block is the return value.
This way you can get the results from each yield call and add it to the resulting array.
We then get the resulting array as the return value from your method new_map
.
def new_map(a)
new_array = []
a.each do |item|
# invoke the block, and add its return value to the new array.
new_array << yield(item)
end
new_array
end
source to share
Try the following:
def new_map(a)
new_array = []
a.each do |item|
# # invoke the block, and add its return value to the new array.
puts yield(item) # just experimenting
end
end
new_map(a) { |i| i + 1 }
This element yield
simply takes each element from the array and runs it through the block. This experimental code simply prints the results; they must be collected in an array. Not difficult:
def new_map(a)
new_array = []
a.each do |item|
new_array = []
# invoke the block, and add its return value to the new array.
new_array << yield(item)
end
end
This will not pass all tests, but the last step should be doable.
source to share
new_array
is created in the definition new_map
, which is a different "lexical scope" than the block you write when you call new_map
. Basically, the code in the method new_map
can see new_array
, but the code in your block cannot. One way to fix this problem is by looking at methods Enumerable
inject
or methods each_with_object
that might work instead each
in your method new_map
.
source to share