Return doesn't always exit my method

Hi, I’m new to Ruby and programming in general.
I’m coding a little script to train on multiplication tables but
something’s wrong:

If I type ‘exit’, the function should normally exit, via a return
statement. But this does not always happen and I really can’t understand
why. I’ve been scratching my head for a few hours on that so I’m posting
here hoping somebody will be able to spot my obvious mistake!

I’m trying to apply here the concept of recursion so maybe this is where
I’m messing up.

Thanks for your input,
Ben

#########################################################################
#!/usr/bin/ruby
require ‘yaml’

system “clear”
filename = “tested_list.txt”

if File.exist?(filename)
imported_list = File.read filename
$tested = YAML::load imported_list
else
$tested = {}
end

def rand_multi()
num1 = rand(10)
num2 = rand(10)
got_wrong = false
operation = “#{num1}*#{num2}”
if $tested[operation].nil?
puts “creation!”
$tested[operation] = 0
elsif $tested[operation] > 1
puts “need restart”
rand_multi() # forces function call again
end
puts “iteration #{$tested[operation]}”
print operation, " = "
before = Time.now
time_before = (before.min * 60) + before.sec # in case I go for a
coffee :slight_smile:
answer = gets.chomp
if answer =~ /exit/
puts “will exit now”
return ##### <-- return statement does not always exit method; why??

# exit

elsif answer =~ /[0-9]+/
answer = answer.to_i
else
puts “Please enter numbers only!”
rand_multi()
end
if answer != num1 * num2
print “e[0;31m” # terminal colors
puts “inside not equal”
puts “No! Answer was ‘#{num1*num2}’”
print “e[0;m”
sleep(1)
# system “clear”
got_wrong = true
$tested[operation] -= 5 # give negative value
rand_multi()
elsif answer == num1 * num2
after = Time.now
time_after = (after.min * 60) + after.sec
answer_time = time_after - time_before
print “e[1;34m”
puts “inside equal”
puts “Yes!, you answered in #{answer_time} seconds”
print “e[0;m”
sleep(0.3)
# system “clear”
if got_wrong == false
if answer_time < 3
$tested[operation] += 1 # give positive value
else
$tested[operation] -= 1 # small penalty if answer too
slow
end
end
rand_multi()
end
end

puts “Multiplications!”
puts “Type ‘exit’ to exit”
puts “\n\n”

rand_multi()
puts “Bad answers:”
$tested.each_pair {|key, value| puts “#{key} => #{value}” if value < 0}

File.open filename, ‘w’ do |f|
f.write $tested.to_yaml
end
###############################################

On Wed, Mar 11, 2009 at 6:18 AM, Benjamin T.
[email protected] wrote:

I’m messing up.

Thanks for your input,
Ben

Your intuition on where the problem is is correct. Note that when
using recursion, each call to the function is a separate “layer”, and
calling return only jumps you out of the current layer. So if you are
in the invocation of rand_multi() that you called from the top level,
return will exit rand_multi() back to the top level, but if you are in
an invocation of rand_multi() called from another invocation of
rand_multi, return will return you from the current invocation of
rand_multi() back to the calling invocation.

I hope that’s clear enough. If not, you can get an illustration by
changing your rand_multi() method as follows:

def rand_multi(call_depth=0) # ← changed
puts “entering rand_multi(#{call_depth})” # ← added
num1 = rand(10)
num2 = rand(10)
got_wrong = false
operation = “#{num1}*#{num2}”
if $tested[operation].nil?
puts “creation!”
$tested[operation] = 0
elsif $tested[operation] > 1
puts “need restart”
rand_multi(call_depth+1) # ← changed: forces function call
again
puts “returning to rand_multi(#{call_depth}” # ← added
end
puts “iteration #{$tested[operation]}”
print operation, " = "
before = Time.now
time_before = (before.min * 60) + before.sec # in case I go for a
coffee :slight_smile:
answer = gets.chomp
if answer =~ /exit/
puts “will exit now”
return ##### ← return statement does not always exit method; why??

exit

elsif answer =~ /[0-9]+/
answer = answer.to_i
else
puts “Please enter numbers only!”
rand_multi()
end
if answer != num1 * num2
print " [0;31m" # terminal colors
puts “inside not equal”
puts “No! Answer was ‘#{num1*num2}’”
print " [0;m"
sleep(1)

system “clear”

got_wrong = true
$tested[operation] -= 5 # give negative value
rand_multi(call_depth+1) # ← changed
puts “returning to rand_multi(#{call_depth}” # ← added
elsif answer == num1 * num2
after = Time.now
time_after = (after.min * 60) + after.sec
answer_time = time_after - time_before
print " [1;34m"
puts “inside equal”
puts “Yes!, you answered in #{answer_time} seconds”
print " [0;m"
sleep(0.3)

system “clear”

if got_wrong == false
if answer_time < 3
$tested[operation] += 1 # give positive value
else
$tested[operation] -= 1 # small penalty if answer too
slow
end
end
rand_multi(call_depth+1) # ← changed
puts “returning to rand_multi(#{call_depth}” # ← added
end
end

Benjamin T. [email protected] wrote:

If I type ‘exit’, the function should normally exit, via a return
statement. But this does not always happen and I really can’t understand
why. I’ve been scratching my head for a few hours on that so I’m posting
here hoping somebody will be able to spot my obvious mistake!

I’m trying to apply here the concept of recursion so maybe this is where
I’m messing up.

To abort a deep call chain, you can use catch() and throw():

def go_deeper(level)
throw(:done) if level > 3
puts “Level #{level}”
go_deeper(level+1)
end

catch(:done) {go_deeper(0)}
puts “finished”

m.

On Wed, Mar 11, 2009 at 6:18 AM, Benjamin T.
[email protected] wrote:

I’m messing up.

Thanks for your input,
Ben

Obviously the flow in your pro gramme is due to the recursive nature of
it.As correctly pointed out by Christopher , return in a recursive
algorithm
doesn’t necessarily mean your returning from the function entry point
you
have started it. It does only mean that you have returned from a
specific
instance of the function (or a stack level one may call it…) and ONLY
when
u have returned from all the instances of the function in the flow , we
can
safely say function has finally returned it’s control . So don’t be
fooled
by a return statement in a recursive funtion as it only provide a way
for
you to return the control in the current stack .Also Always remember to
provide a return (or termination logic) in the context of a recursive
function which otherwise will lead to disasterous unending loops in the
flow.

Regards
-Udayanga

Christopher D. wrote:

Your intuition on where the problem is is correct. Note that when
using recursion, each call to the function is a separate “layer”, and
calling return only jumps you out of the current layer.

Thanks very much Christopher! I wasn’t aware of this at all. I did run
the code changes you provided and it helps!
I think I grasp the general concept but some aspects remain a mistery.
Mainly I do not understand what the code is doing when it reverts to a
previous “layer”:

If the hash is new and fairly empty, the layers pop off in cascading
style. No other bit of code seems to get executed.

this is the output I get when I type ‘exit’

##########console output#############
creation!
iteration 0
18 = exit
will exit now
returning to rand_multi(12)
returning to rand_multi(11)
returning to rand_multi(10)
returning to rand_multi(9)
returning to rand_multi(8)
returning to rand_multi(7)
returning to rand_multi(6)
returning to rand_multi(5)
returning to rand_multi(4)
returning to rand_multi(3)
returning to rand_multi(2)
returning to rand_multi(1)
returning to rand_multi(0)
Bad answers:
7
6 => -1
######################################

But if the hash gets populated, the first part of the function gets
executed from time to time and ‘return’ “sticks” to some layers.

This is the output I get then:

###############console output###################
61 = exit
will exit now
returning to rand_multi(30)
returning to rand_multi(29)
iteration 2
6
7 = exit
will exit now
returning to rand_multi(28)
iteration 2
67 = exit
will exit now
returning to rand_multi(27)
iteration 2
1
8 = exit
will exit now
returning to rand_multi(26)
returning to rand_multi(25)
returning to rand_multi(24)
returning to rand_multi(23)
iteration 2
1*3 =
#################################################
I would guess return brings me back to the exact same spot the recursive
call got called. Is that correct? And in that case, wouldn’t that mean
that my function logic is fundamentally flawed because unpredictable?

Thanks very much Matt for your suggestion. Unfortunatly I cannot apply
it here because I’ve barely touched procs and only have a vague idea of
what throw and catch do. But it’s definitly material for me to study so
thanks!

Thank you also Udayanga. On first analysis, it’s true that my
“termination logic” was to input ‘exit’ at the prompt but I have also an
infinite loop going on when I have answered all possible operations
correctly with a score of 2 (the badly named variable ‘iteration’).