My script needs to accept only 'plain' integers, and raise an argument error in any other cases (letters, special symbols, floats, multiple arguments). Unfortunately, when the passed arguments is a representation for a hexadecimal like 0b1111 the script interprets it as an integer and accepts it.
Here is my script:
begin
price = Integer ARGV[0]
rescue
raise ArgumentError "the entered value has to be an integer"
end
if ARGV.length > 1
raise ArgumentError, "wrong number of arguments (given #{ARGV.length}, expected 1)"
end
if price < 8
raise ArgumentError, "the entered value has to be at least 8"
end
if price.match?(/[A-Za-z]/)
raise ArgumentError "the entered value has to be an integer"
end
Basically the solution I'm trying is the last block:
if price.match?(/[A-Za-z]/)
raise ArgumentError "the entered value has to be an integer"
end
This gives me an error: undefined method `match?' for 2:Integer (NoMethodError)
If I understand correctly match? only works with strings, but because of the first block the variable price is converted to an integer.
How could I fix my code and would price.match?(/[A-Za-z]/) even solve my problem or do I need a completely different approach?
The desired result:
Input: 0b1111
output: ArgumentError "the entered value has to be an integer
You can use exception: false key for Integer method
Integer(nil, exception: false)
# => nil
Integer("blablabla", exception: false)
# => nil
Integer("9.3", exception: false)
# => nil
Integer("9", exception: false)
# => 9
There is also base (second) arg in this method
Integer("0b1111", exception: false)
# => 15
Integer("0b1111", 10, exception: false)
# => nil
Integer("9.3", 10, exception: false)
# => nil
Integer("9", 10, exception: false)
# => 9
Finally you can use it such way
price = Integer(ARGV[0], 10, exception: false)
raise ArgumentError("the entered value has to be an integer") if price.nil?
After this line
price = Integer(ARGV[0])
the input is already transformed into an integer if a transformation is possible as defined in the example in the documentation of Kernel#Integer.
If you want to ensure only strings that contain pure integer values are transformed then you will need to check the format of the input string first. For example, with a Regexp like this
ARGV[0].match?(/\A\d+\z/)
that returns true when the input only contains digits.
Just replace the first block in your script with
unless ARGV[0].match?(/\A\d+\z/)
raise ArgumentError "the entered value has to be an integer"
end
price = Integer(ARGV[0])
# ...
To simplify testing the preconditions I would change it to
unless ARGV&.size != 1
raise ArgumentError, "wrong number of arguments (given #{ARGV&.size || 0}, expected 1)"
end
unless ARGV[0].match?(/\A\d+\z/)
raise ArgumentError "the entered value has to be an integer with at least 8 digits"
end
price = Integer(ARGV[0])
if price < 8
raise ArgumentError, "the entered value has to be at least 8"
end
Related
I am trying this :
print " Enter Value "
num = gets.chomp
also tried .kind_of? but didn't work
if num.is_a? Float
print " Number is in float "
also tried .kind_of? but didn't work
else num.is_a? Integer
print " Number is in integer "
end
Your problem here is that gets returns a String you can actually determine this based on the fact that you are chaining String#chomp, which removes the trailing "separator" which by default is return characters (newlines) e.g.
num = gets.chomp # User enters 1
#=> "1"
In order to turn this String into a Numeric you could explicitly cast it via to_i or to_f however you are trying to determine which one it is.
Given this requirement I would recommend the usage of Kernel#Integer and Kernel#Float as these methods are strict about how they handle this conversion. knowing this we can change your code to
print " Enter Value "
num = gets.chomp
if Integer(num, exception: false)
puts "#{num} is an Integer"
elsif Float(num, exception: false)
puts "#{num} is a Float"
else
puts "#{num} is neither an Integer or a Float"
end
Note: the use of exception: false causes these methods to return nil if the String cannot be converted to the desired object, without it both methods will raise an ArgumentError. Additionally when I say these methods are strict about conversion a simple example would be Integer("1.0") #=> ArgumentError because while 1 == 1.0, 1.0 itself is a Float and thus not an Integer
Some examples:
num = 6.6
num.is_a?(Integer)
=> false
num.is_a?(Float)
=> true
num = 6
num.is_a?(Integer)
=> true
num.is_a?(Float)
=> false
Minor note, #print is not the correct method to print to the screen. The correct method is #puts.
So your code would look like this:
if num.is_a?(Float)
puts 'Number is a float.'
elsif num.is_a?(Integer)
puts 'Number is an integer.'
else
puts 'Number is neither a float nor an integer.'
end
I suspect #gets returns a string though, which means you'll need to convert from a string into a numeric first.
I'm getting started with Crystal, and I've run into something I don't understand. I've written a simple program to demonstrate, which takes a number from the console and adds one.
Ruby
# Add one program.
puts "Enter a number."
number = gets
number = number.to_i
puts "You entered #{number}. #{number} + 1 = #{number + 1}"
Crystal
# Add one program.
puts "Enter a number."
number = gets
number = number.to_s.to_i # Why is to_s needed?
puts "You entered #{number}. #{number} + 1 = #{number + 1}"
As you can see, the programs are nearly identical, however, in crystal I must take the input from the console and convert it to a string before it can be converted into an integer.
What I want to know is:
What is being returned by gets in crystal?
Is there another way to do this without chaining methods?
This may seem like a basic question, but it's still early days for crystal, and documentation is sparse.
Error
Error in example.cr:6: undefined method 'to_i' for Nil (compile-time type is (String | Nil)) (did you mean 'to_s'?)
number = number.to_i # Why is to_s needed?
^~~~
================================================================================
Nil trace:
example.cr:4
number = gets
^~~~~~
example.cr:4
number = gets
^~~~
/usr/share/crystal/src/kernel.cr:105
def gets(*args, **options)
/usr/share/crystal/src/kernel.cr:105
def gets(*args, **options)
^
/usr/share/crystal/src/kernel.cr:106
STDIN.gets(*args, **options)
^~~~
/usr/share/crystal/src/io.cr:574
def gets(chomp = true) : String?
/usr/share/crystal/src/io.cr:574
def gets(chomp = true) : String?
^
/usr/share/crystal/src/io.cr:574
def gets(chomp = true) : String?
/usr/share/crystal/src/io.cr:574
def gets(chomp = true) : String?
^~~~
/usr/share/crystal/src/io.cr:575
gets '\n', chomp: chomp
^~~~
/usr/share/crystal/src/io.cr:604
def gets(delimiter : Char, chomp = false) : String?
^~~~
/usr/share/crystal/src/io.cr:605
gets delimiter, Int32::MAX, chomp: chomp
^~~~
/usr/share/crystal/src/io.cr:618
def gets(delimiter : Char, limit : Int, chomp = false) : String?
^~~~
/usr/share/crystal/src/io.cr:619
raise ArgumentError.new "Negative limit" if limit < 0
^
/usr/share/crystal/src/io.cr:632
if ascii && !decoder && (peek = self.peek)
^
/usr/share/crystal/src/io.cr:633
if peek.empty?
^
/usr/share/crystal/src/io.cr:634
nil
^
In most cases gets will return a String, but it is possible that it returns nil too.
This isn't an issue in Ruby, because in your example you will ever have nil returned at runtime and even if you had, there is NilClass#to_i in Ruby which always returns 0.
But the Crystal compiler checks object types upfront and therefore makes sure that your code can handle all possible return types. Unfortunately, in Crystal, there is no to_i method on Nil yet and therefore you get the compiler error:
undefined method 'to_i' for Nil (compile-time type is (String | Nil))
When running either program, try giving Ctrl+D (EOF) as input. Is the program behaving as you expected?
Crystal is protecting you against these kinds of programming mistakes by making you handle all the possible types a method may return. A more correct version of the program could for example be:
print "Enter a number: "
number = gets.try &.to_i?
if number
puts "You entered #{number}. #{number} + 1 = #{number + 1}"
else
puts "Please enter a valid number"
end
Note: String#to_i? returns nil when the conversion fails, rather than raising an exception as String#to_i does.
I've noticed this strange behavior with the begin/rescue block in Ruby, when I define a variable, and an exception occurs and I try to call that variable that the exception occurred on it returns nil.
For example:
begin
print "Enter a number: "
input = Integer(gets.chomp)
sum = input + 5
puts "This is your number plus five: #{sum}"
rescue ArgumentError
puts "#{input}" #This outputs nil
end
Why does the begin/rescue block work like this, and is there a way to print the variable without it returning nil?
I'm not sure this is what you want but I try
input = gets.chomp
begin
number = Integer(input)
puts "your number plus five: #{number + 5}"
rescue ArgumentError
puts "#{input} is not a valid number"
end
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
How do I write code to fore a user to enter a certain value type such as an int, and then force or loop a prompt until a user enters an int, rather than a string or numbers with string characters? I am thinking some type of Boolean with for or while loop.
Let's start with some basics. Put this into a file userinput.rb:
print "Please enter a number: "
input = gets
puts input
Then run with ruby userinput.rb. You get a prompt and the program outputs whatever you type in.
You want your input to be an integer, so let's use Integer() to convert the input:
print "Please enter a number: "
input = gets
puts Integer(input)
Type in an integer and you'll get an integer output. Type in anything else and you'll get something like this:
userinput.rb:3:in `Integer': invalid value for Integer(): "asdf\n" (ArgumentError)
from userinput.rb:3:in `<main>'
Now you can build a loop that prompts the user until an integer is typed in:
input = nil # initialize the variable so you can invoke methods on it
until input.is_a?(Fixnum) do
print "Please enter a number: "
input = Integer(gets) rescue nil
end
The interesting part is input = Integer(gets) rescue nil which converts the integer and, in case of an ArgumentError like above, the error gets rescued and the input var is nil again.
A more verbose way of writing this (except that this catches only ArgumentError exceptions) would be:
input = nil # initialize the variable so you can invoke methods on it
until input.is_a?(Fixnum) do
print "Please enter a number: "
begin
input = Integer(gets)
rescue ArgumentError # calling Integer with a string argument raises this
input = nil # explicitly reset input so the loop is re-entered
end
end
Some notes:
Please don't get confused by Integer and Fixnum. Integer is the parent class that also encapsulates big numbers, but it's fairly standard to test for Fixnum (as in the loop head). You could also just use .is_a?(Integer) without changing the behavior.
Most Ruby tutorials probably use puts over print, the latter's output doesn't end with a newline, which makes the prompt appear in one line.