I'm trying to figure out how to call methods inside methods in ruby.
Here's my code:
def testNegative number
if number < 0 # No negative numbers.
return 'Please enter a number that isn\'t negative.'
end
end
def testIfZero number
if number == 0
return 'zero'
end
end
def englishNumber number
testNegative(number)
testIfZero(number)
end
puts englishNumber -1
puts englishNumber 0
Currently, the output I'm getting is an empty line and then "zero". I was wondering why I don't see "Please enter a number that isn't negative" as the output of put englishNumber -1. How can I fix things so that "Please enter a number that isn't negative" is returned and the programs ends?
I'll use underscores instead of camel case in my answer, because that is what people usually do for Ruby methods.
Your code doesn't work right now because test_negative is returning a value but english_number is not doing anything with the value. You could change english_number to:
def english_number(number)
test_negative(number) or test_if_zero(number)
end
That way if test_negative returns a non-nil and non-false value, then english_number will use that as its return value and not bother running test_if_zero.
Later, if you need to add more lines of code to english_number, you can do it like this:
def english_number(number)
error_message = test_negative(number) || test_if_zero(number)
return error_message if error_message
# More lines of code here
end
By the way, it sounds like you might actually want to use exceptions. When you raise an exception, the program will end and the exception message will be printed to the user unless you do something to catch and handle the exception. I would change test_negative to:
def test_negative(number)
if number < 0 # No negative numbers.
raise 'Please enter a number that isn\'t negative.'
end
end
Your code
def englishNumber number
testNegative(number)
testIfZero(number)
end
calls first testNegative(number) and ignores the result.
Then it calls testIfZero(number) and returns the result to the caller.
For englishNumber -1 the result of testIfZero(number)is nil and putswrites an empty line.
For englishNumber 0 you get the expected string 'zero'.
If you need a the results as alternative you must return the results with an or condition (or ||).
A complete example:
def testNegative number
if number < 0 # No negative numbers.
return 'Please enter a number that isn\'t negative.'
end
end
def testIfZero number
if number == 0
return 'zero'
end
end
def englishNumber number
testNegative(number) or testIfZero(number)
end
puts englishNumber -1
puts englishNumber 0
or with an alternate syntax:
def testNegative number
return 'Please enter a number that isn\'t negative.' if number < 0 # No negative numbers.
end
def testIfZero number
return 'zero' if number == 0
end
def englishNumber number
testNegative(number) || testIfZero(number)
end
puts englishNumber -1
puts englishNumber 0
I'm not sure if I understand your comments correct, but maybe you are looking for a solution like this:
def testNegative number
puts 'Please enter a number that isn\'t negative.' if number < 0 # No negative numbers.
end
def testIfZero number
puts 'zero' if number == 0
end
def englishNumber number
testNegative(number)
testIfZero(number)
end
englishNumber -1
englishNumber 0
The puts command can also be used inside the methods, you don't need to return a result.
Remark outside the scope of the question: The usage of a method call inside a method is not the simplest solution (but I think you want to check the usage of method calls).
I would use:
def english_number number
if number < 0 # No negative numbers.
return 'Please enter a number that isn\'t negative.'
elsif number == 0
return 'zero'
end
end
Related
I wrote a simple guess the number game. But it keeps looping even when I input the correct number. Please help, thanks!
puts "Pick a number between 0 - 1000."
user_guess = gets.chomp.to_i
my_num = rand(831)
guess_count = 0
until user_guess == my_num do
if user_guess == my_num
guess_count += 1
puts "you got it!"
elsif user_guess <= 830
guess_count += 1
puts "higher"
else user_guess >= 1000
guess_count += 1
puts "lower"
end
end
puts "You guessed my number in #{guess_count} attempts. Not bad"
The part of the code that asks for a number from the user is outside the loop, so it will not repeat after the answer is checked. If you want to ask the user to guess again when their guess is wrong, that code needs to be inside the loop.
my_num = rand(831)
guess_count = 0
keep_going = true
while keep_going do
puts "Pick a number between 0 - 1000."
user_guess = gets.chomp.to_i
if user_guess == my_num
guess_count += 1
puts "you got it!"
keep_going = false
elsif user_guess <= 830
guess_count += 1
puts "higher"
else user_guess >= 1000
guess_count += 1
puts "lower"
end
end
puts "You guessed my number in #{guess_count} attempts. Not bad"
This code still has some bugs in it that stops the game from working correctly though, see if you can spot what they are.
As #Tobias has answered your question I would like to take some time to suggest how you might make your code more Ruby-like.
Firstly, while you could use a while or until loop, I suggest you rely mainly on the method Kernel#loop for most loops you will write. This simply causes looping to continue within loop's block until the keyword break is encountered1. It is much like while true or until false (commonly used in some languages) but I think it reads better. More importantly, the use of loop protects computations within its block from prying eyes. (See the section Other considerations below for an example of this point.)
You can also exit loop's block by executing return or exit, but normally you will use break.
My second main suggestion is that for this type of problem you use a case statement rather than an if/elsif/else/end construct. Let's first do that using ranges.
Use a case statement with ranges
my_num = rand(831)
guess_count = 0
loop do
print "Pick a number between 0 and 830: "
guess_count += 1
case gets.chomp.to_i
when my_num
puts "you got it!"
break
when 0..my_num-1
puts "higher"
else
puts "lower"
end
end
There are a few things to note here.
I used print rather than puts so the user will enter their response on on the same line as the prompt.
guess_count is incremented regardless of the user's response so that can be done before the case statement is executed.
there is no need to assign the user's response (gets.chomp.to_i) to a variable.
case statements compare values with the appropriate case equality method ===.
With regard to the last point, here we are comparing an integer (gets.chomp.to_i) with another integer (my_num) and with a range (0..my_num-1). In the first instance, Integer#=== is used, which is equivalent to Integer#==. For ranges the method Range#=== is used.
Suppose, for example, that my_num = 100 and gets.chomp.to_i #=> 50 The case statement then reads as follows.
case 50
when 100
puts "you got it!"
break
when 0..99
puts "higher"
else
puts "lower"
end
Here we find that 100 == 50 #=> false and (0..99) === 50 #=> true, so puts "higher" is displayed. (0..99) === 50 returns true because the integer (on the right of ===) is covered by the range (on the left). That is not the same as 50 === (0..90), which loosely reads, "(0..99) is a member of 50", so false is returned.
Here are a couple more examples of how case statements can be used to advantage because of their reliance on the triple equality method.
case obj
when Integer
obj + 10
when String
obj.upcase
when Array
obj.reverse
...
end
case str
when /\A#/
puts "A comment"
when /\blaunch missiles\b/
big_red_button.push
...
end
Use a case statement with the spaceship operator <=>
The spaceship operator is used by Ruby's Array#sort and Enumerable#sort methods, but has other uses, as in case statements. Here we can use Integer#<=> to compare two integers.
my_num = rand(831)
guess_count = 0
loop do
print "Pick a number between 0 and 830: "
case gets.chomp.to_i <=> my_num
when 0
puts "you got it!"
break
when -1
puts "higher"
else # 1
puts "lower"
end
end
In other applications the spaceship operator might be used to compare strings (String#<=>), arrays (Array#<=>), Date objects (Date#<=>) and so on.
Use a hash
Hashes can often be used as an alternative to case statements. Here we could write the following.
response = { -1=>"higher", 0=>"you got it!", 1=>"lower" }
my_num = rand(831)
guess_count = 0
loop do
print "Pick a number between 0 and 830: "
guess = gets.chomp.to_i
puts response[guess <=> my_num]
break if guess == my_num
end
Here we need the value of gets.chomp.to_i twice, so I've saved it to a variable.
Other considerations
Suppose we write the following:
i = 0
while i < 5
i += 1
j = i
end
j #=> 5
j following the loop is found to equal 5.
If we instead use loop:
i = 0
loop do
i += 1
j = i
break if i == 5
end
j #=> NameError (undefined local variable or method 'j')
Although while and loop both have access to i, but loop confines the values of local variables created in its block to the block. That's because blocks create a new scope, which is good coding practice. while and until do not use blocks. We generally don't want code following the loop to have access to local variables created within the loop, which is one reason for favouring loop over while and until.
Lastly, the keyword break can also be used with an argument whose value is returned by loop. For example:
def m
i = 0
loop do
i += 1
break 5*i if i == 10
end
end
m #=> 50
or
i = 0
n = loop do
i += 1
break 5*i if i == 10
end
n #=> 50
1. If you examine the doc for Kernel#loop you will see that executing break from within loop's block is equivalent to raising a StopIteration exception.
Can't seem to get my data to be read as an integer and print out the data, instead gets the 2nd option which is Error: first line of file is not a number.
def write(aFile, number)
aFile.puts(number)
index = 0
while (index < number)
aFile.puts(index)
index += 1
end
end
def read(aFile)
count = aFile.gets
if (is_numeric?(count))
count = count.to_i
else
count = 0
puts "Error: first line of file is not a number"
end
index = 0
while (count < index)
line = aFile.gets
puts "Line read: " + line
index += 1
end
end
def main
aFile = File.new("mydata.txt", "w") # open for writing
if aFile # if nil this test will be false
write(aFile, 10)
aFile.close
aFile = File.new("mydata.txt", "r")
read(aFile)
aFile.close
else
puts "Unable to open file to write or read!"
end
end
def is_numeric?(obj)
if /[^0-9]/.match(obj) == nil
true
end
false
end
main
Any help on how to fix this would be great.
Your problem is a lack of return
def is_numeric?(obj)
if /[^0-9]/.match(obj) == nil
true
end
false
end
This function ALWAYS returns false. There is no case where it ever returns true. Ruby functions always return at an explicit return() and if none is called, then the last line is returned. That means the true you have there does nothing. It's simply thrown away and false is returned.
A simplified form of this existing function is just:
def is_numeric?(obj)
false
end
To fix this problem, you need to return when it’s true:
def is_numeric?(obj)
if /[^0-9]/.match(obj) == nil
return(true)
end
false
end
You can also simplify this to:
def is_numeric?(obj)
/[^0-9]/.match(obj).nil?
end
Also, if you’re using Ruby 2.4+, a more efficient way to do this would be to use the match? method and a negation. match sets up some handy MatchData (and backreferences), but since you if you don't need any of that, you can save the overhead by using match?, which simply returns a boolean.
def is_numeric?(obj)
!/[^0-9]/.match?(obj)
end
Another problem is your logic of count < index.
while (count < index)
line = aFile.gets
puts "Line read: " + line
index += 1
end
Since index is 0, the only time count will be less than index, is if count is less than 0. Perhaps you meant while (count > index)?
Notes:
https://www.ruby-lang.org/en/news/2016/12/25/ruby-2-4-0-released/
Below is the code for my script.
As you can see, i have an array, and an index. I pass that to the block called 'raise_clean_exception'. The integer part of it does actually raise a Standard Error exception which is great. I have an issue when I use an index that is out of bounds. So if my array only has 4 elements (0-3) and I use an index of 9, it will not raise the exception, and instead it prints out a blank line because nothing is there. Why would it do this?
#!/usr/bin/ruby
puts "I will create a list for you. Enter q to stop creating the list."
array = Array.new
i = 0
input = ''
print "Input a value: " #get the value from the user
input = STDIN.gets.chomp
while input != 'q' do #keep going until user inputs 'q'
array[i] = input #store the value in an array of strings
i += 1 #increment out index where the inputs are stored
print "Input a value: " #get the value from the user
input = STDIN.gets.chomp
end #'q' has been entered, exit the loop or go back through if not == 'q'
def raise_clean_exception(arr, index)
begin
Integer(index)
puts "#{arr[index.to_i]}"
# raise "That is an invalid index!"
rescue StandardError => e # to know why I used this you can google Daniel Fone's article "Why you should never rescue exception in Ruby"
puts "That is an invalid index!"
end
# puts "This is after the rescue block"
end
# now we need to access the array and print out results / error messages based upon the array index value given by the user
# index value of -1 is to quit, so we use this in our while loop
index = 0
arrBound = array.length.to_i - 1
while index != '-1' do
print "Enter an index number between 0 and #{arrBound} or -1 to quit: "
index = STDIN.gets.chomp
if index == '-1'
exit "Have a nice day!"
end
raise_clean_exception(array, index)
end
Consider using a subclass of StandardError, IndexError, which is specific to the problem you are experiencing. Also, using else prevents a blank space from being printed if the index is out of bounds and when raising exceptions within a method, a begin...end block is implied.
def raise_clean_exception(arr, index)
Integer(index)
raise IndexError if index.to_i >= arr.length
rescue StandardError
puts "That is an invalid index!"
else
puts "#{arr[index.to_i]}"
end
Accessing an array element that's outside the range of existing elements returns nil. That's just the way Ruby works.
You could add the following line before the "puts" to trap that condition...
raise StandardError if index.to_i >= arr.size
I'm trying to recreate Enumerable's count method as found in "Projects: Advanced Building Blocks".
The definition in the Ruby docs is that count
"Returns the number of items in enum through enumeration. If an argument is given, the number of items in enum that are equal to item are counted. If a block is given, it counts the number of elements yielding a true value."
What exactly is the default argument though?
The way I approached this so far is as follows:
The parameter is set to something when no argument is passed so:
Case, when self is not a string:
when argument given and block given (eg. [1,2,3].count(3) { |x| x == 3 }):
returns warning and count of the argument.
when argument given and no block (eg. [1,2,3].count(3)):
returns count of the argument.
when no argument and no block (eg. [1,2,3].count):
returns size of the instance.
else (no argument given and block given) (eg. [1,2,3].count { |x| x == 3 }:
returns count based on specifications given in block.
The two questions I have are basically:
What is the default argument for count?
What is the global variable used in warning?
Here's my code:
module Enumerable
def my_count arg='default value'
if kind_of? String
# code
else # I think count works the same way for hashes and arrays
if arg != 'default value'
count = 0
for index in 0...size
count += 1 if arg == self[index]
end
warn "#{'what goes here'}: warning: block not used" if block_given?
count
else
return size if arg == 'default value' && !block_given?
count = 0
for index in 0...size
count += 1 if yield self[index]
end
count
end
end
end
end
Don't use a default argument. Use *args to collect all the arguments into an array.
def my_count(*args, &block)
if args.length == 1 && !block_given?
# use args[0]
elsif args.length == 1 && block_given?
# use block
elsif args.length == 0 && !block_given?
# no argument/block
else
# raise error
end
end
I'm hoping to get my Ruby script to start at an inputted number, say 100, and itterate all the way up to the end of the range; 1000. Having all the numbers in between saved to a file.
This is a code I have so far:
#!/usr/bin/env ruby
if ARGV.length ==0;
puts "Enter the start number"
else puts ARGV.size
ARGV.each do |a|
for i in 0..1000 do
puts i;
end
end
end
To run it I'm typing:
ruby increment.rb 100 > increment.txt
However, it ignores the input number and starts at 1 regardless.
What am I doing wrong?
It starts at 0 because you're giving it the range 0..1000, which starts at 0. If you want to use the numeric value of a as the starting point, use a.to_i instead of 0.
ARGV is an array and the first argument is stored in ARGV[0] second in ARGV[1] etc
if ARGV[0]
start = ARGV[0].to_i
else
puts "Enter the start number"
start = gets.to_i
end
(start .. 1000).each do |i|
puts i
end