Inner working of Integer('string') call? - ruby

If I want to see if a string is a valid integer, I can do:
puts Integer('1') #=> 1
as any non-integer would raise an error:
puts Integer('a') #=> invalid value for Integer(): "a" (ArgumentError)
If I want to make sure my variable is both a Float and an Integer, I do not want to repeat myself so I try to put the classes in a list:
x = '1'
[Integer, Float].each{|c| puts c(x) } #=> undefined method `c' for main:Object (NoMethodError)
Will someone explain why this does not work, and if there is some way to achieve what I am looking for? What kind of method call is Integer(var)?
Note, I have no real world problem I'm trying to solve here, I'm just curious.

When you do:
Integer('10')
you are essentially calling the Integer class method defined in Kernel, passing it the argument 10.
To achieve what you want you can do:
[:Integer, :Float].each { |c| puts method(c).call(x) }

Related

How can I solve undefined method `[]' on Ruby?

I'm trying to get an if statement for users who put incorrect data.
Here's my code:
class Breweries::CLI
def start
puts "Hello!"
puts "---------------------------"
puts "Please enter your location:"
input = gets.strip.downcase
#data = Breweries::API.get_breweries(input)
#objects = Breweries::HoppyCode.all
if input.length < 1
puts "Sorry!!"
puts "```````"
start
else
display_info
end
end
def display_info
puts "You'll love the following spots!"
puts "********************************"
#objects.each.with_index(1) {|brewery, index| puts "#{index}. #{brewery.name}"}
puts "Please make a selection by index number for more information:"
input = gets.strip.downcase
if(input.to_i > 0)
#brewery = #objects[input.to_i - 1]
puts "name: #{#brewery.name}"
puts "street: #{#brewery.street}"
puts "city: #{#brewery.city}"
puts "phone: #{#brewery.phone}"
puts "website_url: #{#brewery.website_url}"
display_info
elsif (input == "quit")
quit
elsif (input == "menu")
start
end
end
def quit
puts "Goodbye. Drink responsibly and enjoy."
end
end
When I put something that would generate an error, it returns the following:
Please enter your location: nvifpejvf80ejvip
Traceback (most recent call last):
2: from bin/breweriesCLI:6:in `<main>'
1: from /home/munificent-format-5297/Development/breweries/lib/breweries/cli.rb:8:in `start' /home/munificent-format-5297/Development/breweries/lib/breweries/api.rb:6:in `get_breweries': undefined method `[]' for nil:NilClass (NoMethodError)
How can I solve the undefined method '[]' error? Here's the API code in case that's necessary.
class Breweries::API
def self.get_breweries(input)
#breweries_hash = HTTParty.get("https://api.openbrewerydb.org/breweries?by_city=#{input}")
breweries_obj = {
name: #breweries_hash[1]["name"],
street: #breweries_hash[3]["street"],
city: #breweries_hash[4]["city"],
phone: #breweries_hash[10]["phone"],
website_url: #breweries_hash[11]["website_url"]
}
Breweries::HoppyCode.new(breweries_obj)
end
end
When the input is invalid, the call to
#breweries_hash = HTTParty.get("...")
returns not the object you expect (I’d suggest it returns an empty hash.) That makes it impossible to get to details in the following lines. Depending on how are you to handle it, you might decide to e. g. early return from this function, or raise, or do something else.
To approach this, start with debugging the issue, like this:
#breweries_hash = HTTParty.get("...")
puts #breweries_hash.inspect
...
That way you’ll see what gets returned and get the ideas of how to handle it.
If I am right, and what is returned is an empty hash, you might want to early return from this function.
#breweries_hash = HTTParty.get("...")
return if #breweries_hash.empty?
...
Identifying the Problem
There are lots of ways to solve for the nil problem, but at a quick glance it seems like part of the problem here is that you're somehow expecting input to return a valid Hash object from your API call, but an empty String or an instance of FalseClass may not do that. Consider the following:
input = gets.strip.downcase # <RETURN> here gets an empty string
input #=> ""
input.to_i > 0 #=> false
Then consider that some downstream of Breweries::API.get_breweries is expecting to operate on a Hash object instead if an instance of NilClass. In this case, that looks like #breweries_hash[1]["name"] and other operations on #breweries_hash.
Some Options
Without knowing more about your code, I don't want to be prescriptive here. But in general, you can do one or more of the following:
Coerce arguments into the expected class in the method call, the method signature, or the method body. For example, for Array objects:
# coerce a String to an Array, raising an exception if it can't
input = ""
Array(input)
#=> [""]
# coerce some Array to a Hash
array = [:name, "foo", :street, "bar"]
Array(array.each_slice 2).to_h
#=> {:name=>"foo", :street=>"bar"}
Explicitly check that you have an Hash object:
fail "#breweries is not a Hash" unless #breweries.is_a? Hash
Raise an exception rather than return 0 if input isn't actually a valid Integer representation in the first place:
input = Integer(gets.strip.downcase)
Check if your Hash or Array object responds to the relevant method calls, and raise a more helpful exception message:
raise sprintf("#brewery: %s", #brewery.class) unless #brewery.respond_to? :[]
There are other things you might do as well. Broadly speaking, you need to adjust your code to check the return value of your call to ensure it's not nil, then branch/raise/rescue appropriately depending on whether or not you ever expect nils as a valid return value from Breweries::API.get_breweries.
A Note on Using Exceptions for Non-Exceptional Circumstances
As a rule of thumb, you should only raise exceptions for truly unexpected circumstances, or when the program should halt because some condition can't (or shouldn't) be handled within the program during runtime. Which is best in your particular use case is really a design decision, and outside the scope of the original question. However, you might want to read Avdi Grimm's Exceptional Ruby for a deeper explanation of when exceptions might better than branching or handlers (or vice versa), but the choice in your code is a little left of center of the problem you're actually dealing with right now.

Understanding strange output in multidimensional array

I am new to ruby and I was trying to iterate over a 2d array. I made a mistake in my code. From that mistake, I noticed some unexpected output.
s = [["ham", "swiss"], ["turkey", "cheddar"], ["roast beef", "gruyere"]]
i = 0;
s.each{
|array| so = array[i] # pin
puts so[i]
}
Due to #pin, if i = 0, output is h t r. i = 1 gives w h r. i > 1 gives an error:
C:/Ruby.rb in `block in <main>': undefined method `[]' for nil:NilClass (NoMethodError)
from C:/Ruby.rb:3:in `each'
from C:/Ruby.rb:3:in `<main>'
If I use |array| so = array # pin, then my code does not produce strange output. I'll just fix the remaining stuff to make my code iterate for all values that 'i' can have.
Please explain this.
PS: Working code is here
s = [["ham", "swiss"], ["turkey", "cheddar"], ["roast beef", "gruyere"]]
s.each{
|array| so = array
array.each{
|str| puts str
}
}
For each type of sandwich, when i is 0, so is the 1st element, which is the meat. so[0] is the first letter of the meat.
When i is 1, which is the 2nd element, which is the cheese. so[1] is the second letter of the cheese.
When i is 3, there is no third component to the sandwich. so so is nil. so[2] is asking for the nil[2].
nil is a class, like everything in ruby. But nil does not implement the [] method, as arrays and other classes that implement the Enumerable module do.
Since nil does not support the [] method, then you get the undefined method error.
Even operations that are built into other languages, like +, [], and == are methods that can be overridden in Ruby.
To understand exactly what's happening, try this bit of code:
class NilClass
def [] (i)
nil
end
end
Executing that will open up the existing NilClass, and add a method called []. Then you can do nil[1] and it will return nil. Changing an existing class like this is known as monkey patching in the Ruby world.
When you ask for so[2] you are actually asking for the third element, and if it doesn't exist, you'll get an error.
I recommend structuring your blocks like so:
s.each do |pair|
puts pair
end
Note the do/end instead of {} and the placement of the iteration variable inline with the each. Also note that this is equivalent to your "working code" so you don't need the extra iterator in this case.

Trouble when using match on an array

What i'm trying to do is create a method that can be given an array as an argument. The array should have some numbers in it. The method will return the number of times the array includes each number inside of it. I understand that there are probably many ways to do this, but I'd appreciate it if folks could help me understand why my way is not working rather than just advising me to do something completely different.
So I start by trying this method out
def score (dice)
dice.each do |die|
x = /(die)/.match(dice.to_s).length
end
x
end
and calling it with score ([5])expecting to get an output of 1. However, I get
NoMethodError: undefined method `length' for nil:NilClass
from t2.rb:22:in `block in score'
from t2.rb:21:in `each'
from t2.rb:21:in `score'
from (irb):2
from /home/macs/.rvm/rubies/ruby-2.0.0-p247/bin/irb:13:in `<main>'
I have also tried changing the match statement slightly (getting rid of the to_s) so it is
def score (dice)
dice.each do |die|
x = /(die)/.match(dice).length
end
x
end
and calling it with score ([5]) I get
TypeError: no implicit conversion of Array into String
from t2.rb:22:in `match'
from t2.rb:22:in `block in score'
from t2.rb:21:in `each'
from t2.rb:21:in `score'
from (irb):2
from /home/macs/.rvm/rubies/ruby-2.0.0-p247/bin/irb:13:in `<main>'
Really not sure how I'm supposed to accomplish this matching.
In this line
/(die)/.match(dice.to_s).length
the method match returns nil if the argument you are passing doesn't match the regular expression, which leads to this error
nil.length
# => NoMethodError: undefined method `length' for nil:NilClass
The method will return the number of times the array includes each
number inside of it.
You can try this
a = [1,1,1,2,2,1,3]
a.uniq.map { |x| a.count(x) }
# => [4, 2, 1]
a.uniq.map { |x| {x => a.count(x)} }
# => [{1=>4}, {2=>2}, {3=>1}]
If you want to count the occurence of each elements in the array, then you can do something like this
def score (dice)
count_hash = {}
dice.uniq.each do |die|
count_hash[die] = dice.count(die)
end
count_hash
end
I'd appreciate it if folks could help me understand why my way is not working ...
/(die)/ creates a Regexp, a pattern that can be matched against a string. Your pattern matches and captures die.
Regexp#match returns a MatchData object if there was a match:
/(die)/.match('a string with die') #=> #<MatchData "die" 1:"die">
# here's the match: ^^^
or nil if there was no match:
/(die)/.match('a string with dice') #=> nil
You are not working with string but with an array of integers. You convert this array to a string using Array#to_s:
dice = [5]
dice.to_s #=> "[5]"
This string doesn't contain die and therefore match returns nil:
/(die)/.match("[5]") #=> nil
Calling nil.length then raises the NoMethodError.
Passing the array "as-is" doesn't work either, because match expects a string:
/(die)/.match([5]) #=> TypeError: no implicit conversion of Array into String
Using a Regexp is not going to work here, you'll have to approach this problem in another way.
This is probably the most rubyish way to solve the problem:
a = [1,1,1,2,2,1,3]
p Hash[a.group_by{|x|x}.map{|key, val| [key,val.size]}]
#=> {1=>4, 2=>2, 3=>1}
An example that might help you implement your logic
a = [2,3,2,8,3]
a.uniq.each {|i| print i, "=>", a.to_s.scan(/#{i}/).length, " times \n" } #=> this works but ugly.
a.uniq.each {|i| print i, "=>", a.count(i), " times \n" } #=> borrowed from one of the answers.
2=>2 times
3=>2 times
8=>1 times
You're getting errors because in both cases you are trying to match a string (5) with the wrong thing.
This tries to match die with the entire array dice converted to a string:
dice.each do |die|
x = /(die)/.match(dice.to_s).length
end
This tries to match die with the dice array itself:
dice.each do |die|
x = /(die)/.match(dice).length
end

Checking the type of a method parameter

I am not sure if an object I pass to a method is of the proper type. I might pass a string to a function that can only handle integers. What about some kind of runtime ensurance? I couldn't see a better option than:
def someFixNumMangler(input)
raise "wrong type: integer required" unless input.class == FixNum
other_stuff
end
Any better alternatives?
Use the Kernel#Integer method to convert the input before using it. It will raise an ArgumentError when the input could not be converted to an integer in any reasonable fashion.
def my_method(number)
number = Integer(number)
# do something with number, which is now guaranteed to be an integer
end
I recommend Avdi Grimm's new book Confident Ruby for more insight into this.
If you really need to do type checks, then yes, you only have runtime checking. Code in the question is ok. You can also use .is_a?.
def someFixNumMangler(input)
raise "wrong type: integer required" unless input.is_a?(FixNum)
other_stuff
end
The checks may take different forms. If you expect, say, a string and you call string methods on it (upcase, gsub, etc), the code will blow up if anything other than string is passed. Unless, of course, you pass an object that is not a string, but behaves just like one (has the same methods that you call). This is the essence of duck typing.
What if your method looked like this?
def someFixNumMangler(input)
input = input.to_i
puts "got this: #{input} of #{input.class}"
end
someFixNumMangler(10)
someFixNumMangler('42')
someFixNumMangler(File)
# >> got this: 10 of Fixnum
# >> got this: 42 of Fixnum
# ~> -:2:in `someFixNumMangler': undefined method `to_i' for File:Class (NoMethodError)
# ~> from -:9:in `<main>'
As long as an argument responds to #to_i, you don't actually care what its type is.

What does 'yield called out of block' mean in Ruby?

I'm new to Ruby, and I'm trying the following:
mySet = numOfCuts.times.map{ rand(seqLength) }
but I get the 'yield called out of block' error. I'm not sure what his means. BTW, this question is part of a more general question I asked here.
The problem is that the times method expects to get a block that it will yield control to. However you haven't passed a block to it. There are two ways to solve this. The first is to not use times:
mySet = (1..numOfCuts).map{ rand(seqLength) }
or else pass a block to it:
mySet = []
numOfCuts.times {mySet.push( rand(seqLength) )}
if "numOfCuts" is an integer,
5.times.foo
is invalid
"times" expects a block.
5.times{ code here }
You're combining functions that don't seem to make sense -- if numOfCuts is an integer, then just using times and a block will run the block that many times (though it only returns the original integer:
irb(main):089:0> 2.times {|x| puts x}
0
1
2
map is a function that works on ranges and arrays and returns an array:
irb(main):092:0> (1..3).map { |x| puts x; x+1 }
1
2
3
[2, 3, 4]
I'm not sure what you're trying to achieve with the code - what are you trying to do? (as opposed to asking specifically about what appears to be invalid syntax)
Bingo, I just found out what this is. Its a JRuby bug.
Under MRI
>> 3.times.map
=> [0, 1, 2]
>>
Under JRuby
irb(main):001:0> 3.times.map
LocalJumpError: yield called out of block
from (irb):2:in `times'
from (irb):2:in `signal_status'
irb(main):002:0>
Now, I don't know if MRI (the standard Ruby implementation) is doing the right thing here. It probably should complain that this does not make sense, but when n.times is called in MRI it returns an Enumerator, whereas Jruby complains that it needs a block.
Integer.times expects a block. The error message means the yield statement inside the times method can not be called because you did not give it a block.
As for your code, I think what you are looking for is a range:
(1..5).map{ do something }
Here is thy rubydoc for the Integer.times and Range.

Resources