Forum: Ruby return doesn't always exit my method

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
Cd4cb399ee95c9279604092bee3c1618?d=identicon&s=25 Benjamin Thomas (bento)
on 2009-03-11 15:20
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 :)
  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 ""                             # terminal colors
    puts "inside not equal"
    puts "No! Answer was '#{num1*num2}'"
    print ""
    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 ""
    puts "inside equal"
    puts "Yes!, you answered in #{answer_time} seconds"
    print ""
    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
###############################################
D7463bd611f227cfb2ef4da4a978a203?d=identicon&s=25 Christopher Dicely (Guest)
on 2009-03-11 15:41
(Received via mailing list)
On Wed, Mar 11, 2009 at 6:18 AM, Benjamin Thomas
<benjamin.thomas81@free.fr> 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 :)
 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
Fbb4d027695dfdf76bf448b15d7e306a?d=identicon&s=25 matt neuburg (Guest)
on 2009-03-11 17:25
(Received via mailing list)
Benjamin Thomas <benjamin.thomas81@free.fr> 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.
312bab66708aab09c40c8a4176211e5b?d=identicon&s=25 Udayanga Wickramasinghe (usw)
on 2009-03-11 20:46
(Received via mailing list)
On Wed, Mar 11, 2009 at 6:18 AM, Benjamin Thomas
<benjamin.thomas81@free.fr> 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
Cd4cb399ee95c9279604092bee3c1618?d=identicon&s=25 Benjamin Thomas (bento)
on 2009-03-11 21:47
Christopher Dicely 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
1*8 = 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###################
6*1 = 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
6*7 = 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').
This topic is locked and can not be replied to.