2

I'm trying to find out if a user inputs a string or an integer. I can find it out if I enter a fixed value with user input's requirement. I can't get its class.

This works:

name = 5 
type = name.class

This doesn't work:

print "Enter the value to findout its class "
name = gets
type = name.class
if type == String
  puts "this is a string"
elsif type == Fixnum
  puts "this is a fixnum"
else
  puts "this is unknown type"
end
sawa
  • 165,429
  • 45
  • 277
  • 381
lenso_here
  • 110
  • 2
  • 10

5 Answers5

2

I think the question I would have to ask you is how is the user expected to enter a string?

If the user enters:

10 #=> Fixnum
1_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000 #=> Bignum
1.0 #=> Float
"hello" #=> String
'hello' #=> String

Then you can just do:

def test_input
  input = gets.chomp
  puts eval(input).class
rescue NameError
  puts "Unknown Input Class"
end

This actually should work with all defined objects, so if you only want to catch those four that you mentioned then:

def test_input
  input = gets.chomp
  klass = eval(input).class
  raise NameError unless [Fixnum, Bignum, Float, String].include?(klass)
  puts klass
rescue NameError
  puts "Unknown Input Class"
end

EDIT:

To circumvent the use of eval with direct user input, which is a vulnerability, Gumbo suggests to use a parser instead.

$ gem install parser 

#!/usr/bin/ruby
require 'parser/current'

def test_input
  input = Parser::CurrentRuby.parse(gets.chomp)

  puts case input.type
  when :str then "String"
  when :float then "Float"
  when :int 
    eval(input.to_sexp.slice(/\d+/)).class 
    # Yes using eval again to get Fixnum vs Bignum, but this time 
    # we know what the input will be, and that's ok to eval
  else
    "Unknown Input Class"
  end
end
  • `eval`? Are you kidding me? – Gumbo May 02 '14 at 08:59
  • `eval` does not evaluate just literals but also *any* Ruby code. – Gumbo May 02 '14 at 09:06
  • yeah I realize that. it depends on how the user input is accepted. but it seems the best way to get the desired outcome. with conversions, even kernel conversions like Kernel#Integer ect. you have to make a choice of which to convert first or do as Stefan does in his answer and create a list of valid classes, but that doesn't seem to be the intended result and look, it converts any numeric input to a float, so... –  May 02 '14 at 09:07
  • You could better use a Ruby parser, which doesn’t execute the given code. – Gumbo May 02 '14 at 09:10
  • still ended up using eval to get Fixnum and Bignum because the parser just cares that it is an int –  May 02 '14 at 09:43
  • did you get an error? did you gem install parser first? –  May 02 '14 at 17:26
2

You could call Kernel#String, Kernel#Integer and Kernel#Float to find "valid" classes, i.e. classes you can convert the input into.

def valid_classes(value)
  classes = []
  classes << String(value) rescue nil
  classes << Integer(value) rescue nil
  classes << Float(value) rescue nil
  classes.map(&:class)
end

valid_classes("foo")                   #=> [String]
valid_classes("123.456")               #=> [String, Float]
valid_classes("123")                   #=> [String, Fixnum, Float]
valid_classes("100000000000000000000") #=> [String, Bignum, Float]

Other notations are recognized, too:

valid_classes("1_000")                 #=> [String, Fixnum]
valid_classes("1e20")                  #=> [String, Float]
valid_classes("0b111")                 #=> [String, Fixnum]

If you need more control, use a regular expression.

Here's a simple REPL:

loop do
  print "Enter a value to find out its class: "
  input = gets.chomp
  puts "'#{input}' could be a: #{valid_classes(input).join(', ')}"
end
Stefan
  • 109,145
  • 14
  • 143
  • 218
  • Great use of `Kernel#Integer` ; I reused your stuff in my answer =) +1 – Abdo May 02 '14 at 09:25
  • hi Stefan, I just started learning ruby 15 days ago. I already learned methods and classes. however i'm still not enough experienced to enter your answer into my code properly. can you help me ? it will be dummy proof. – lenso_here May 02 '14 at 14:07
  • Awesome.It worked. How should i narrow down the result? when i integer number 10 . It tell me it can be string , fixnum, float. How to get it is a fixnum ? – lenso_here May 02 '14 at 16:37
  • @lenso_here you can't :) It could be the string `"10"` or the number `10`. The user just hit `1`, `2` and `enter`, it's up to you to interpret the input. – Stefan May 02 '14 at 17:01
  • it is getting complicated. 10 is an integer. and i want to get it recognized as integer. I think you helped a lot. Thank you so much. However, I'm still at chapter I of coding. So i need more time. – lenso_here May 02 '14 at 17:06
1

As @ArupRakshit mentioned in his comment above, gets always gets you a String.

What you want to do is get the user's input and determine what it is.

For instance, "1" is a String but 1 is a Fixnum.

[2] pry(main)> "1".class
=> String
[3] pry(main)> 1.class
=> Fixnum

In your comment above, you mentioned that gets.to_i gives you an integer. There's a problem with that. String#to_i returns 0 for strings that are not numeric:

[6] pry(main)> gets.to_i
hello
=> 0

[7] pry(main)> "hello".to_i
=> 0

So basically you're getting a String and will be determining its possible class(es).

# add more Kernel methods here
POSSIBLE_CLASSES = [Integer, Float, Bignum, String]

str = gets.chomp # chomp gets rid of the \n that gets appended 
                 # to your String because you press enter

# As @Stefan does it, but with meta-programming
POSSIBLE_CLASSES.collect { |p| 
  (Kernel.method(p.name).call(str); p) rescue nil 
}.compact

Explanation of the last line:

Kernel.Integer("1") returns 1 Kernel.Float("1.0") returns 1.0

So basically, I want to call a Kernel's method which name is the same as my class.

Kernel.method( method_name ) returns the method to me, which I then call with the string str.

Kernel.Integer("hello") would throw an ArgumentError; which will be rescued and a nil will be collected.

So basically, the line above will loop through the possible classes and attempt initializing them with the string we got from the console. We collect the class if there's no exception and nil otherwise. Then we compact the array (remove the nils), which will then contain the 'valid' classes.

Note that our code above only supports Kernel types and can be easily tweaked to support other classes.

Abdo
  • 13,549
  • 10
  • 79
  • 98
0

How about this?

class String
  def is_number?
    true if Float(self) rescue false
  end
end

Now call is_number? on user_input

user_input.is_number?
Prabhakar
  • 6,458
  • 2
  • 40
  • 51
0

(gets.chomp == '0' || gets.chomp.to_i != 0) => true for input is integer

Rahul Patel
  • 1,386
  • 1
  • 14
  • 16