Stack too deep error on ruby, sqrt program for learning - ruby

So I'm aware of the Math.sqrt(number) method in Ruby, but as part of learning the language I thought it'd be a good idea to make some simple functions. I could be going about finding the square root wrong from a fundamental level, but here's what I did:
def negative?(number)
number < 0
end
#start = 2
def sqrt(number)
if negative? number
puts "Can't do negative"
else
if #start**2 < number
#start += 0.01
sqrt(number)
else
puts "Square root of #{number} is #{#start}!"
end
end
end
def start_sqrt
print "Input text: "
number = Integer(gets.strip)
sqrt(number)
end
begin
start_sqrt
rescue ArgumentError
puts "Need a positive number!"
end
This works fine for smaller numbers, but when I get to entering larger numbers I need to change the value of #start if I want to get an answer, otherwise it says "stack too deep". Is there something wrong I'm doing in the code, or is Ruby doing everything fine and I just am asking it to find the square root of a number in a resource-intensive way? I guess this is maybe even less of a programming question and more of a math question, cause I know I could just do:
def sqrt(number)
if negative? number
puts "Can't do negative"
else
root = number**0.5
puts "Square root of #{number} is #{root}!"
end
end

It looks like Ruby is working as expected. You are asking it to find the square root of a number in a resource-intensive way (recursion).
In fact, you have created a stack overflow error. :)

Related

Simple program but so very stuck- Loops in Ruby

I have to write a program which asks the user to enter a number.
The program keeps on asking the user for a number until the user types 'Stop'
at which point the sum of the numbers that the user has entered should be printed.
I've tried many,many things and none of my ideas work.
This is what I have - but I can that it isn't correct. What am I doing wrong?
I've only used while loops and arrays
total_user_input = []
# As long as the user inputs a number, the program will keep putting Give me a number
# and then adding that number to the total_user_input array.
puts "Give me a number: "
while user_input = gets.chomp.to_i
#add the input to the array total_user_input
total_user_input.push(user_input.to_i)
puts "Give me a number: "
# If the user however types stop, then the loop is broken and we jump down to the
# sum bit - where all of the numbers in the total_user_input array are added together
# and printed. End of program!
if user_input == "stop"
break
end
sum = 0
total_user_input.each { |num|
sum += num
}
puts sum
end
The output isn't as it should be.
As others have identified the problems with your code let me suggest how you might reorganize it. Ruby provides many ways to execute loops but you many find it desirable to primarily relay on the method Kernel#loop and the keyword break. (As you will learn in time, loop is particularly convenient when used with enumerators.)
def sum_numbers
tot = 0
loop do
print 'Gimme a number: '
s = gets.chomp
break if s == 'Stop'
tot += s.to_i
end
tot
end
The keyword break can optionally take an argument (though why that is not mentioned in the doc I cannot say), in which case it (if a literal) or its value (if a variable or method) is returned by loop. Here one would generally see
break tot if s == 'Stop'
without the final line, tot. As the loop returns tot and that is the last calculation performed by the method, the method will return the final value of tot.
You could have instead written
return tot if user_input == 'Stop'
but I think most coders believe best practice dictates that one should not return from a method from within a loop (or from within nested loops) unless there is a good reason for doing so.
Some small points:
I used print rather than puts to that the user's entry will be shown on the same line as the prompt.
I used s (for "string") rather than user_input because it reduces the chance of spelling mistakes (e.g., user_imput), speeds reading, and (possibly a foible of mine), looks neater. True, s is not descriptive, but one only has to remember its meaning for three consecutive lines of code. Others may disagree.
You could write, break if s.downcase == 'stop' if you want, say, 'stop' or 'STOP' to have the same effect as 'Stop'.
'23O3'.to_i #=> 23 (that's an an oh, not a zero), so in real life you'd want to confirm that either 'Stop' or the string representation of a number had been typed.
This is how I would do this preferring to use loop do end syntax with a break when it should. Also added a bit more text so user knows what's happening.
total_user_input = []
puts 'Give me a number or "stop" to end: '
loop do
user_input = gets.chomp
total_user_input << user_input.to_i
puts "Give me a number: "
break if user_input.downcase == "stop"
end
puts "Total entered: #{total_user_input.inject(&:+)}" unless total_user_input.empty?
puts 'goodbye!'
Note these few things:
get.chomp.to_i will convert every input to integer. ("stop" or any non integer string will be 0)
Arrangement of the flow is quite messy.
total_user_input = []
puts "Give me a number: "
while user_input = gets.chomp.strip
total_user_input.push(user_input.to_i)
sum = 0
total_user_input.each { |num|
sum += num
}
puts sum
if user_input == "stop"
break
end
end
Hope you understand this.

'*' can't be coerced into Fixnum(TypeError)

I'm trying to write a program to do factorials. I made this:
continue_loop=true
def fact(n)
if n==0
1
else
n*fact(n-1)
end
end
while continue_loop
puts "Welcome to the factorial machine. Write a number! Write exit to exit the program"
user=gets.chomp
if user=="exit"
continue_loop=false
else puts fact(user.to_i)
puts "--------"
end
end
I get * can't be coerced into Fixnum(TypeError) on lines:
n*fact(n-1)
and
else puts fact(user.to_i)
I found that I get this error because the right hand side of n*fact(n-1) is nil. But then, I don't see how I can do recursion.
It can be done with inject, but I'd like to learn to use recursion.
Your function:
def fact(n)
if n==0
1
else
n*fact(n-1)
end
end
is fine. if/else returns values from both blocks. Just to be sure I just tried with fact(5) and it returned 120. If something were wrong, it would not. Of course, your function fact(-1) fails badly, but that's another problem.
Your function cannot return nil. It will either return 1 or result of multiplication. Therefore in n*fact(n-1) the right hand can't be nil. The left hand probably is. Find out why.

program using while with array values

I am trying to write a small program that goes through an array's values outputting each individual value. When it reaches 15 it stops and outputs "too big".
Why is my logic below wrong, it makes sense to me..
x = [10,11,12,13,14,15,16,17,18]
def counting
for x[y]
while y < 15
puts y
else
puts "too big"
end
puts counting
I'm just learning sorry if this is a really simple solution.
That's nothing at all like Ruby syntax. You want a .each and a simple if statement:
x.each do |y|
if y < 15
puts y
else
puts "too big"
break
end
end
It appears though you are trying to use Ruby like you would a c-style programming language. It's possible and viable, albeit not recommended.
Code Blocks
Ruby has structure known as code blocks. Code blocks are sort of like anonymous functions.
You can read more about code blocks here.
x = [10,11,12,13,14,15,16,17,18]
# This is a code block.
x.each do |y| # The y between the '|'s is the parameter caught by the code block
if y < 15
puts y
else
puts "Too big."
break # Break out of the loop
end
end
If you want a one liner:
x.each {|y| y < 15 ? puts(y) : puts("too big") || break }
If you insist using while, it can be done as following:
i = 0
while i do
x[i] < 15 ? puts(x[i]) : puts("too big") || break
i+=1
end
I think the cleanest way to do this using while would be:
def counting(x)
i = 0
while x[i] < 15
puts x[i]
i += 1
end
puts 'too big'
end
counting([10,11,12,13,14,15,16,17,18])
Why is my logic below wrong, it makes sense to me..
Given that you program isn't even syntactically legal, it's impossible to tell what its semantics would be if it were syntactically legal, let alone why those semantics were wrong. However, here's an idiomatic Ruby solution (you will almost never use nor see while in any Ruby code, and you will certainly never see for):
puts x.take_while {|n| n < 15 }
puts 'too big'
I prefer writing in point-free style, however in this case the only way to do that is to make use of the symmetry of the condition and invert the order of operands, which will lead to awkward logic:
x.take_while(&15.method(:>))
Ao, in this case I would rather avoid point-free style, because it no longer matches the original specification literally. Instead of "take all numbers less than 15", we now have "take all numbers such that 15 is greater than those numbers", which reads awkward.

How to run a simple Ruby script

I would like to make a program that checks to see if the number you enter is an even number. Sort of like making a leap year program but for any number divisible by 2.
Something along the lines of:
num = gets.chomp
while num != 0
if (num%2) == 0
puts 'yess'
else
puts 'nooo'
end
end
I knows there's something easy that I need to change for it to run.
(btw I just started learning Ruby yesterday!)
There are two problems here.
First being something that others have put, you need to make sure you turn the input into an integer using ".to_i" on your num variable.
Secondly, this code puts you into an infinite loop since you are using a "while" loop.
Since the number is only input once, you get stuck in the "while" loop forever no matter what the input is. Basically, "num" never stops being not 0.
You'd be better off using an if..else statement. Something like:
num = gets.chomp.to_i
if num != 0
if (num%2) == 0
puts 'yess'
else
puts 'nooo'
end
else
puts "that's 0, dude"
end
Integers have two methods for this. They are even? and odd?.
You can use this in your if statement as so:
if num.even?
puts 'yess'
else
puts 'nooo'
end
However, an easier way to write this is with ternary expressions:
puts num.even? ? "yes" : "no"
However, make sure num is an Integer. Anything coming from gets will be a String. So, you should be doing num = gets.chomp.to_i. Anything that is not a number, like "h", will return 0.
"5".to_i #=> 5
"h".to_i #=> 0

Catch and throw not working in ruby

I am trying to make a number guessing game in Ruby but the program exits after I type in yes when I want to play again. I tried using the catch and throw but it would not work. Could I please get some help.
Here is my code.
class Game
def Play
catch (:start) do
$a=rand(11)
puts ($a)
until $g==$a
puts "Guess the number between 0-10."
$g=gets.to_i
if $g>$a
puts "The number you guessed is too high."
elsif $g==$a
puts "Correct you won!!!"
puts "Would you like to play again?"
$s=gets()
if $s=="yes"
$c=true
end
if $c==true
throw (:start)
end
elsif $g<$a
puts "The number you guessed is too low."
end
end
end
end
end
Game.new.Play
Edit: Here's my new code after trying suggestions:
class Game
def Play
catch (:start) do
$a=rand(11)
puts ($a)
while $s=="yes"
until $g==$a
puts "Guess the number between 0-10."
$g=gets.chomp.to_i
if $g>$a
puts "The number you guessed is too high."
elsif $g==$a
puts "Correct you won!!!"
puts "Would you like to play again?"
$s=gets.chomp
if $s=="yes"
throw (:start)
end
elsif $g<$a
puts "The number you guessed is too low."
end
end
end
end
end
end
Game.new.Play
Your first problem is here:
$s=gets()
if $s=="yes"
$c=true
end
The gets method will read the next line including the new line character '\n', and you compare it to only "yes":
> gets
=> "yes\n"
The idiomatic way to fix this in Ruby is the chomp method:
> gets.chomp
=> "yes"
That said, your code has two other deficiencies.
You may come from a language such as PHP, Perl, or even just Bash scripting, but Ruby doesn't require the dollar sign before variables. Using a $ gives a variable global scope, which is likely not what you want. In fact, you almost never want a variable to have global scope.
Ruby uses three types of symbol prefixes to indicate scope - # for instance, ## for class, and $ for global. However the most common type of variable is just local which doesn't need any prefix, and what I would suggest for your code.
I have always been told that it is very bad practice to use exceptions for control structure. Your code would be better served with a while/break structure.
When you do gets(), it retrieves the full line with a '\n' in the end. You need to trim the new line character by using:
$g=gets.chomp.to_i
Same for other gets
Based on your updated code (where you fixed the newline problem shown by others), your new problem is that you have wrapped all your game inside while $s=="true". The very first time your code is run, $s is nil (it has never been set), and so you never get to play. If you used local variables instead of global variables (s instead of $s) this would have become more obvious, because the code would not even have run.
Here's one working way that I would re-write your game.
class Game
def play
keep_playing = true
while keep_playing
answer = rand(11) # Make a new answer each time
puts answer if $DEBUG # we don't normally let the user cheat
loop do # keep going until I break from the loop
puts "Guess the number between 0-10."
guess = gets.to_i # no need for chomp here
if guess>answer
puts "The number you guessed is too high."
elsif guess<answer
puts "The number you guessed is too low."
else
puts "Correct you won!!!",
"Would you like to play again?"
keep_playing = gets.chomp.downcase=="yes"
break
end
end
end
end
end
Game.new.play
I know this doesn't really answer your question about why your code isn't working, but after seeing the code you posted I just had to refactor it. Here you go:
class Game
def initialize
#answer = rand(11)
end
def play
loop do
guess = get_guess
display_feedback guess
break if guess == #answer
end
end
def self.play_loop
loop do
Game.new.play
break unless play_again?
end
end
private
def get_guess
puts "Guess the number between 0-10."
return gets.chomp.to_i
end
def display_feedback(guess)
if guess > #answer
puts "The number you guessed is too high."
elsif guess < #answer
puts "The number you guessed is too low."
elsif guess == #answer
puts "Correct you won!!!"
end
end
def self.play_again?
puts "Would you like to play again?"
return gets.chomp == "yes"
end
end
Game.play_loop

Resources