This page contains a set of Ruby drills that were written by David S. Touretzky during the Fall 2012 session of the course. These offer excellent exercise material on a selection of topics that proved to be challenging for students in the past. Note that this page only inlcudes a subset of the full set of exercies developed by Dave. We will add more material as we cover further topics.
The full document is available at the Web page from Fall 2012 .
print vs. return
- What is the difference between print and return?
Answer
print displays a value on the terminal; return terminates a
function's execution and supplies a return value to whoever called the function.
- What value does print return?
Answer
print returns nil.
- If a method does not execute a return statement, what value does it return?
Answer
It returns the value of the last expression it executed.
- What do each of the following functions do when called
on the list [1,2,3,4,5]?
def f1(list)
for x in list do
print x if x.odd?
end
end
|
def f2(list)
for x in list do
return x if x.odd?
end
end
|
Answer
f1 prints 135 and returns [1,2,3,4,5] because that is the value of the for statement. f2 returns 1.
- What values do f1 and f2 return for the input [1,2,3,4,5]?
Answer
f1 returns [1,2,3,4,5], while f2 returns 1.
- What values do f1 and f2 return for the input [2,4,6,8]?
Answer
Both functions return [2,4,6,8], because neither executes a return statement.
- Fill in the blanks:
- When we want to go through a list to search for an element that
passes a test, and stop searching as soon as we have found it, we
should use _________ to exit the loop and deliver the element as the
result of the function.
Answer
return
- When we want to go through a list and display all the elements that
pass a test, we should use _________ to display each element.
Answer
print
- The _________ statement always produces output on the console.
Answer
print
- The _________ statement specifies the result of the function
containing it. That result may or may not be displaed on the console,
depending on the context in which the function is being called.
Answer
return
Lists
- Given x = ["b", "c", "d"], write a short Ruby expression
to produce each of the following results. Your expression can include
x, a quoted string, the + operator, and/or brackets and commas for
constructing lists. You may not use the << operator.
- ["a", "b", "c", "d"]
Answer
["a"] + x
- ["b", "c", "d", "e"]
Answer
x + ["e"]
- [["b", "c", "d"]]
Answer
[x]
- ["a", ["b", "c", "d"]]
Answer
["a", x]
- ["b", "c", "d", "b", "c", "d"]
Answer
x + x
- [["b", "c", "d"], ["b", "c", "d"]]
Answer
[x,x]
- ["b", "c", "d", ["b", "c", "d"]]
Answer
x + [x]
Recursion
Read the Dragon
stories (sections 8.1, 8.2, 8.4, 8.6, 8.8, and 8.9) to help you
understand recursive thinking.
Thinking Recursively
To solve a problem recursively you must answer three questions:
- What is the base case? This question asks how will we
recognize an input for which we should not make a recursive call? Is
it zero? The empty list? A non-list value? Pick the simplest base
case you can. Often, for list problems, this will be the empty list,
but that's not always the case.
- How can I derive a smaller problem from this one? For
numbers, this often means subtracting one from the input. For flat
(non-nested) lists, it may mean removing the first element from the
input and recursing on what's left. For nested lists (e.g., trees) it
may mean recursing on the first element of the input, and then perhaps
recursing again on everything except the first element.
- How can I make progress with each recursive call? Making
progress might mean doing something with the input, or with the result
you got back from the recursive call, or both.
Let's try an example: write a recursive function f(n) to
print a row of n asterisks, followed by a newline, and return
nil. Example:
>> f(8)
********
=> nil
Let's work through our three questions:
- What should the base case be? ___________
Answer
n is 0.
- Given an input n, how can you make a smaller
problem from this one? _________________
Answer
Replace n by n-1.
- How can you make progress with each recursive call? Think about
it this way: if your job is to print eight asterisks, and you can get
somebody else to print the last seven of them, then what do you need
to do yourself? __________________
Answer
Print one asterisk.
Here is Ruby code to solve the problem. For each of the three
questions above, write that number on the line that implements your
answer to that question.
def print_asterisks(n)
if n == 0 then ____
print "\n"
else
print "*" ____
print_asterisks(n-1) ____
end
Answer
i, iii, ii
Breaking A Problem Down
The neat thing about looping is that you can solve an arbitrarily
complex problem by just doing one relatively simple thing over and
over. This is true both for recursion and iteration; the difference
is in how you break the problem down:
- When we iterate over a list, using for or
while, we always have the complete list available, and we use
an index variable i to move across the elements, writing
list[i] to get to the element we're currently operating on.
- When we recurse over a list, we don't use an index
variable. Instead, we generate a series of recursive calls on smaller
and smaller lists by eliminating some elements each time, and making a
tiny bit of progress before or after each recursive call.
If it's not clear how to break down a particular list processing
problem recursively, here's an interesting way to approach the
question. Pick an example list and write down expressions to access
each of the elements your function needs to examine. Then look for a
pattern.
For example: suppose x = ["apple", "banana", "cherry",
"date"] and we want a function to print each element followed by
the string " pie", on a separate line, producing:
apple pie
banana pie
cherry pie
date pie
We could do this for the first element by writing:
puts x.first + " pie"
Since we're trying to come up with a recursive solution, we don't want
to use subscripting to access later elements; we want to construct
smaller lists. We can use drop(1) to
non-destructively remove the first element of a list and return what's
left, so for banana pie we could do:
puts x.drop(1).first + " pie"
For the third and fourth elements we would do:
puts x.drop(1).drop(1).first + " pie"
puts x.drop(1).drop(1).drop(1).first + " pie"
Now you can see a pattern: we are dropping one more element each time
we move to the next position in the list. We still only have one call
to first and one use of + at the end. It's the
drop(1) part that repeats, so this is the part that will be
in the recursive call. We'll use first and + in the
portion of the function where we make a little bit of progress each
time. Now all we need is the base case, which obviously should be the
empty list. Thus, the solution to our problem is:
def print_pies(list)
if list.empty? then # base case
return nil
else
puts list.first + " pie" # make a little bit of progress
print_reversed(list.drop(1)) # recurse on a smaller problem
end
end
Now suppose we want to print the items in the reverse order, like this:
date pie
cherry pie
banana pie
apple pie
We can do this simply by making the recursive call before the
"make a little progress" step instead of after it:
def print_reversed_pies(list)
if list.empty? then # base case
return nil
else
print_reversed_pies(list.drop(1)) # recurse on a smaller problem
puts list.first + " pie" # make a little bit of progress
end
end
If you don't see why this program works, try inserting a puts
"list = " + list.inspect statement at the beginning of
the function to trace the recursion.
Recursion on Lists
- Write a recursive function to print out each element of a
list of numbers on a line by itself. Hint: print the first element,
then call recursively.
Answer
def f(list)
if not list.empty? then
puts list[0]
f(list.drop(1))
end
end
- Write a recursive function to print out each element of a
list on a line by itself, in reverse order (the last element is
printed first). Hint: do the recursive call first, then print the
first element.
Answer
def f(list)
if not list.empty? then
f(list.drop(1))
puts list[0]
end
end
- Write a recursive function to add up all the elements of a
list of numbers.
Answer
def f(list)
if list.empty? then
return 0
else
return list[0] + f(list.drop(1))
end
end
- Write a recursive function to return the first odd number in a list.
Answer
def f(list)
if list.empty? then
return nil
elsif list[0].odd? then
return list[0]
else
return f(list.drop(1))
end
end
- Write a recursive function to return a list of all the odd
numbers in its input, e.g., f([1,2,3,4,5,6]) should return [1,3,5].
Answer
def f(list)
if list.empty? then
return []
elsif list[0].odd? then
return [list[0]] + f(list.drop(1))
else
return f(list.drop(1))
end
end
- Write a recursive function to return the last element of a
non-empty list. Note: you can do this non-recursively by writing
list.last or list[-1] or
list[list.length-1], but the idea here is to do it
recursively.
Answer
def f(list)
if list.drop(1).empty? then
return list[0]
else
return f(list.drop(1))
end
end
- Write a recursive function to return the next to last element
of a list whose length is at least 2.
Answer
def f(list)
if list.drop(2).empty? then
return list[0]
else
return f(list.drop(1))
end
end
Recursion on Integers
- Write a recursive function f(n) to print the numbers n through 1 in descending order.
Answer
def f(n)
if n > 0 then
puts n
f(n-1)
end
end
- Write a recursive function f(n) to print the numbers 1 through n in ascending order.
Answer
def f(n)
if n > 0 then
f(n-1)
puts n
end
end
- Write the recursive version of the factorial function
fact(n) that returns the product 1 × 2 × ... × (n-1) × n.
Answer
def f(n)
if n == 0 then
return 1
else
return n * f(n-1)
end
end
- Write a recursive function num_digits(n) that returns
the number of digits in a positive integer n. Example:
num_digits(268) should return 3, while
num_digits(9175) should return 4. Hint: you can eliminate
the last digit of an integer by dividing it by 10.
Answer
def num_digits(n)
if n == 0 then
return 0
else
return 1 + num_digits(n/10)
end
end
- Write a recursive function to_bits(n) that converts a positive integer into
an array of bits. Example: to_bits(131) should return [1,0,0,0,0,0,1,1]. Recall
that you can obtain the low-order bit of a number by writing n%2, and you can shift
a number one bit to the right (which eliminates the low-order bit) by writing n/2.
Answer
def to_bits(n)
if n == 0 then
return []
else
return to_bits(n/2) + [n%2]
end
end
|