Recall how we pass a block to a function:
def dostuff
yield(7)
end
puts dostuff {|x| x + 9}
This code passes the block {|x| x + 9}
to the method
dostuff
which calls the block on 7
Below is an alternative way to accomplish exactly the same thing (try
it!). The
difference, however, is that the block now is now a parameter with a name
code
. Note the syntax of the parameter:
&code
since we are passing the address of the block. Also
note that instead of yield
I now call a method
call
on the code that makes it execute with the parameter
given to call
.
def dostuff(&code)
code.call(7)
end
puts dostuff {|x| x + 9}
This style of passing blocks to a function is very important since it allows you to re-pass the code to function calls inside the function that initially gets the code. This enables recursion with a block being passed as a parameter to the recursive call.
Unfortunately, there does not seem to be a way to pass two code blocks to a function. It is possible to pass another parameter as a lambda-procedure. This is covered in the optional material below.
Suppose we want to write a class of nested arrays so that we can call methods of Enumerable interface on such arrays. The code below shows the beginning of such an implementation. Your task will be to add more methods to this class.
Enumerable is a module which for the purposes of this problem set is
similar to a Java interface: it gives a list of methods that a class
supports but does not provide an implementation. Note that Ruby does
not mandate that all methods are actually implemented. The
initialize
method stores whatever is passed to it in the
variable @narray
. The intended value is an array of
arbitrary nesting, as shown in the call to new
below. Note that p na
just prints the address of the
object since no to_s
is defined.
class NestedArray
include Enumerable # Enumerable is a module, not a class
# modules are included, not subclassed
def initialize obj
@narray = obj
end
end
# testing the new class
na = NestedArray.new([[6,7],[8],[[9, 6]]])
p na
#p na.each {|z| puts (z + z)} # need to define each for this to work
Now we define an each
method for the NestedArray. The
method is recursive. However, to use recursion, we need to pass the
sub-arrays to the recursive call. The private method
_each
does that, and the public each
just
calls the private one. In Ruby private and public keywords
apply to a group of methods, i.e. all methods defined after
"private" and until "public" are private.
Study the code for the recursive call carefully. Note that
obj
is just a normal array so we can use its
each
method. Likewise you can use other methods of the
sub-arrays in your code so your code would be really not that long.
class NestedArray
# defining each for nested arrays
private
# a private recursive method, iterates over nested arrays
# with the code block in &code
# unfortunately, you can only pass one code parameter
def _each (obj,&code)
obj.each do |x|
if (x.instance_of? Array)
_each(x,&code)
else
code.call(x)
end
end
end
public
# the public each method, calls the recursive one with the
# nested arrays storage as the parameter
def each(&code)
_each(@narray, &code)
end
end
na.each {|y| puts "Here is #{y}"}
Your task is to implement the following public methods of the NestedArray class. Consult the description of Enumerable module for methods' behavior.
to_s
method to print all the nested
array. Make sure that all the nested elements are printed and the
structure of the nesting is displayed.find
method that returns any one element (in
any of the nested arrays) for
which the given block is not false. For instance, if I consider the
nested array NestedArray.new([[6,7],[8],[[9, 6]]]).find {|y| y >
7}
then the method may return 8. Note that returning 9 would
not be wrong. If the block is false on all elements, nil is
returned.any?
method that returns true if the block
returns a value other than false or nil on at least one element,
otherwise it returns falsemap
method that returns a new nested array of
the same structure with the results of running block once for every
element. For instance,
NestedArray.new([[6,7],[8],[[9, 6]]]).map {|y| y * y}
returns a NestedArray object [[36,49],[64],[[81, 36]]]
.
inject
method that takes a "seed"
and works on all elements according to the definition in the
Enumerable module. For instance,
NestedArray.new([[6,7],[8],[[9, 6]]]).inject(1) {|prod, y| prod * y}
returns the product of all elements: 18144.
Don't worry about default parameters or type mismatches for any of the methods: they need to work on the correct input and are allowed to break on any incorrect data.
You may define any private methods that you find helpful. If you run into any problems with making methods private, you can make your helper methods public without loss of credit, just use _ in front of the name to distinguish "private" methods from public ones.
There are two ways to approach this problem: use the each
method above as a sample and just write all the other methods one by
one, or write a more general traverse-like method and use it to
generate all of the needed methods. The latter approach requires using
a lambda rather than a block since we need to pass two functions to
traverse (action
and combine
), but we can
pass only one block. The optional material below shows how to create
first-class functions using a lambda.
You may use either approach. No extra credit is given for the lambda approach because once you get "traverse" to work, the rest of the methods can be written in 20 minutes, if not less, so you are simplifying the work quite a bit.
For convenience all the code used in this page can be copy/pasted from here.
It is possible to define and use first-class functions in
Ruby. They are defined using a familiar lambda notation (see below).
You can pass as many functions as you want to a function. Note that to
call the function, you use call
method, just like for a
block. Syntactically, however, blocks are different from lambdas and
allow you to write more natural code where blocks are used as a "loop"
body. You cannot do it with a lambda.
Lambda can be used to pass the "combine" function to a traverse-like general function in NestedArray. Then the "traverse" function can be used to generate all of the methods you are trying to define. If you are using this approach, don't forget about the "seed" parameter to "traverse".
#################### Optional material ##################################
###### may be used to write a traverse-like function in #################
###### Nested Array to write all other methods as calls to traverse #####
# creating a function that you can pass to another function
add_two = lambda {|x| x + 2}
p add_two.call(5)
repeat = lambda {|y, z| y.call(y.call(z))}
# functions can now be passed around
# cannot do it with blocks :-(
def call_two_functions(f1, f2)
return f2.call(f1, 5)
end
p call_two_functions(add_two, repeat)