CGI/Ruby - converting string to an integer - ruby

Below is my test.cgi file. The params coming in from the html are:
{"FirstNum"=>["3"], "SecondNum"=>["3"]}
Why isn't the a.class showing up as an integer? I need to do math with the values, and the form sent them as an integers. Even the Ruby .to_i doesn't work.
#!/RailsInstaller/Ruby2.1.0/bin/ruby
require 'cgi'
cgi = CGI.new
puts "Content-type: text/html\n\n"
arr = cgi.params
a = arr['FirstNum']
a.each do |a|
puts a.class
end

From the fine manual:
The method params() returns a hash of all parameters in the request as name/value-list pairs, where the value-list is an Array of one or more values. The CGI object itself also behaves as a hash of parameter names to values, but only returns a single value (as a String) for each parameter name.
So when you say:
arr = cgi.params
you get a Hash in arr whose keys are the CGI parameter names and whose values are arrays of strings; arr is a bad name here because it is a Hash, not an Array. That means that this:
a = arr['FirstNum']
gives you an array of strings in a. If you only want one value then use the [] method on cgi to get a string and then String#to_i to get a number:
a = cgi['FirstNum'].to_i

Related

Mixing keyword argument and arguments with default values duplicates the hash?

So i discovered this ruby behaviour, which kept me going crazy for over an hour. When I pass a hash to a function which has a default value for hash AND a keyword argument, it seems like the reference doesn't get passed correctly. As soon as I take away the default value OR the keyword argument, the function behaves as expected. Am I missing some obvious ruby rule here?
def change_hash(h={}, rand: om)
h['hey'] = true
end
k = {}
change_hash(k)
k
#=> {}
It works fine as soon as I take out the default or the keyword arg.
def change_hash(h, rand: om)
h['hey'] = true
end
k = {}
change_hash(k)
k
#=> {'hey' => true}
def change_hash(h={})
h['hey'] = true
end
k = {}
change_hash(k)
k
#=> {'hey' => true}
EDIT
Thanks for your answers. Most of you pointed out that ruby parses the hash as a keyword argument in some cases. However, I am talking about the case when a hash has string keys. When I pass the hash, it seems like the value that gets passed is correct. But modifying the hash inside the function doesn't modify the original hash.
def change_hash(hash={}, another_arg: 300)
puts "another_arg: #{another_arg}"
puts "hash: #{hash}"
hash['hey'] = 3
end
my_hash = {"o" => 3}
change_hash(my_hash)
puts my_hash
Prints out
another_arg: 300
hash: {"o"=>3}
{"o"=>3}
TL;DR ruby allows passing hash as a keyword argument as well as “expanded inplace hash.” Since change_hash(rand: :om) must be routed to keyword argument, so should change_hash({rand: :om}) and, hence, change_hash({}).
Since ruby allows default arguments in any position, the parser takes care of default arguments in the first place. That means, that the default arguments are greedy and the most amount of defaults will take a place.
On the other hand, since ruby lacks pattern-matching feature for function clauses, parsing the given argument to decide whether it should be passed as double-splat or not would lead to huge performance penalties. Since the call with an explicit keyword argument (change_hash(rand: :om)) should definitely pass :om to keyword argument, and we are allowed to pass an explicit hash {rand: :om} as a keyword argument, Ruby has nothing to do but to accept any hash as a keyword argument.
Ruby will split the single hash argument between hash and rand:
k = {"a" => 42, rand: 42}
def change_hash(h={}, rand: :om)
h[:foo] = 42
puts h.inspect
end
change_hash(k);
puts k.inspect
#⇒ {"a"=>42, :foo=>42}
#⇒ {"a"=>42, :rand=>42}
That split feature requires the argument being cloned before passing. That is why the original hash is not being modified.
This is particularly tricky case in Ruby indeed.
In your example you have optional argument which is a hash and you have an optional keyword argument at the same time. In this situation if you pass only one hash, Ruby interprets it as a hash which contains keyword arguments. Here is the code to clarify:
change_hash({rand1: 'om'})
# ArgumentError: unknown keyword: rand1
To work around this you can pass two separate hashes into the method with second one (the one for keyword arguments) being empty:
def change_hash(h={}, rand: 'om')
h['hey'] = true
end
k = {}
change_hash(k, {})
k
#=> {'hey' => true}
From the practical point of view it is better to avoid metdhod signature like that in production code, because it is very easy to make an error while using the method.

Accidental Type conversion in Ruby with strings

I'm trying to solve a challenge where you take in a string of words but return the longest word in the string. My strategy is to break the string into an array of individual words and search the array. However, I'm getting a type conversion error. What is causing the type conversion error? This is particularly strange to me because I don't actually see any type conversion happening here.
def LongestWord(sen)
sen1 = sen.split("/\W+/")
grt = 0
sen1.each do |i|
if sen1[i].length > sen1[grt].length # Type conversion error
grt = i
end
end
sen1[grt]
end
# keep this function call here
puts LongestWord(STDIN.gets)
The type conversion is caused by the array entry i being converted (probably unsuccessfully) into an integer (though I suppose it could be ruby trying to convert the array into a hash, and use i as a key to the hash).
Your misunderstanding is that you think you're getting the array's indices passed into the block for each. What is passed in to that block is each individual value in the array. I.e., if your string sen is 'this is a silly string', then the values passed are 'this', 'is', 'a', 'silly', and 'string'.
You get the error because, when the code is running, i is the first value of sen1, which results in sen1['some string'] being evaluated.
An array can't have a string index, only a hash can, resulting in the Type error.
Meditate on this:
def longest_word(sen)
sen1 = sen.split # => ["foo", "barbaz"]
grt = 0
sen1.each do |i|
i # => "foo"
sen1 # => ["foo", "barbaz"]
sen1[i] # =>
sen1[grt] # =>
sen1[i].length # =>
sen1[grt].length # =>
if sen1[i].length > sen1[grt].length #Type conversion error
grt = i # =>
end
end
sen1[grt]
end
# keep this function call here
longest_word('foo barbaz')
Breaking it down further, here's the offending problem:
sen1 = 'foo barbaz'.split
sen1['foo'] # =>
# ~> TypeError
# ~> no implicit conversion of String into Integer
You don't see the type conversion, but it is there. In more than one place.
As Derrell Durrett pointed out in his answer, your are assuming (wrongly) that the index of the array is passed to the block, not its elements.
Then you write if sen1[i].length > sen1[grt].length. Let's consider the string is 'this is a silly string'. The first element is 'this' and what you are trying to do is if sen1['this'].length > sen1[0].length. As Ruby arrays always have integer indexes, Ruby tries to convert 'this' to an integer in order to find the element at the specified position. Of course this fails!
But your code is not that far from being right. A few small changes and it will run perfectly well:
def longest_word(sen)
sen1 = sen.split(" ")
grt = 0
sen1.each_index do |i|
if sen1[i].length > sen1[grt].length
grt = i
end
end
sen1[grt]
end
puts LongestWord(STDIN.gets)
Now you'd be passing the indexes with sen1.each_index and it'd be working fine.
Notice that I changed the name of your method to longest_word. This is much better, in fact, because this first capital letter is reserved to constants and class names.
I also would like to point that you are not using a good Ruby style. This could be written like this:
def longest_word(str)
str.split(" ").max_by{ |s| s.length }
end
and the result would be the same.

Explain why TypeError is thrown in this Ruby script

In this script:
dictionary = [
"below","down","go","going","horn","how","howdy","it","i","low","own","part",
"partner","sit"
]
def substrings(string, dictionary)
frequencies = Hash.new(0)
dictionary.each_index do |substring|
frequencies.store(dictionary.fetch(substring), string.scan(/#{dictionary[substring]}/i).length)
end
frequencies.each_pair {|word, count| puts "#{word} => #{count}"}
end
substrings("Howdy partner, sit down! How's it going?", dictionary)
if I change dictionary.each_index to dictionary.each, I get the following error:
in `fetch': no implicit conversion of String into Integer (TypeError)"
Please explain why. I understand that each returns the values of the array and that each_index returns the index. I cannot get the code to work using each and want to understand why.
Array#each_index iterates through each of the array. This means that for your dictionary array, the substring variable will be set to 0, then 1, then 2, etc. Each of these values is an Integer.
Array#fetch expects an Integer as it's argument and returns the value at that index in the array. When you use each, you are passing the actual string value instead of the index. Thus, the error you see.
If you want to use Array#each, you will need to update your each block like this.
dictionary.each do |substring|
frequencies.store(substring, string.scan(/#{substring}/i).length)
end
dictionary is an array. Array#fetch expects a numerical index and will fetch the value at that index. If you use dictionary.each then substring is going to be a string (ie. below, down, go, going, horn, etc.) which isn't a valid argument to the method.
each_index works because then substring is an integer (the index into the array).

Convert ruby array to string

I'm fairly new to ruby. I have the following csv:
Office (1), Test
Office (Test)(2), Test
In "data.csv".
Then in my ruby script I have;
CSV.foreach("data.csv") do |line|
registeredOffice = line[0].to_s()
macOffice = registeredOffice.scan(/\(([^\)]+)\)/).last
csvText = "#{csvText}\n#{macOffice}"
end
Which gives me
["1"]
["2"]
However I want to know how to convert the above to a string so the output is
1
2
Using .join or [0] returns a nil:NilClass (NoMethodError)
You probably want something like:
macOffice = registeredOffice[/(\d+)\)$/, 1]
scan with capture groups will give you multi-d arrays
The following line:
macOffice = registeredOffice.scan(/\(([^\)]+)\)/).last
returns array since scan returns array of array. For the first line of data.csv, it is ["1"].
I guess you need scalar value for macOffice, thus, you want to use match which only returns non-repetive match using match, which returns array of matches once. For example, you can grab first match from the returned array using [1] subscript, thus:
macOffice = registeredOffice.match(/\(([^\)]+)\)/)[1]
which returns 1.
Assuming you want an array you can write like this:
out = []
CSV.foreach("data.csv") do |line|
registeredOffice = line[0].to_s()
macOffice = registeredOffice.match(/\((\d+)\)/)[1]
out.push(macOffice)
end
puts out.join(",")
to produce 1,2

A string (thats looks like an array) into an array Ruby

I have an output from an API that look like this... (its a string)
[[2121212,212121,asd],[2323232,23232323,qasdasd]]
Its a string - not an array. I want to convert it to an array and then extract the first two elements in each array in the nested array to:
[2121212,212121],[2323232,23232323]
What's the best way to do this ruby? I could use regexp and extract - but basically the string is already an array, however the class is a string.
I tried
array.push(response)
but that just put the string in to the array as one element. I guess what would be nice is a to_array method
You will need to use regular expression anyway if not eval (shrudder...), this is the shortest one
str = "[[2121212,212121,asd],[2323232,23232323,qasdasd],[2424242,24242424,qasdasd]]"
p str.scan(/(\d+),(\d+)/)
=>[["2121212", "212121"], ["2323232", "23232323"], ["2424242", "24242424"]]
Assuming this is a JSON response (and if so, it is badly malformed and you should talk to the people that are responsible for this) you could write something like:
require 'json'
input= '[[2121212,212121,Asd],[2323232,23232323,qasdasd]]'
input.gsub!(/([A-Za-z ]+)/,'"\1"')
json = JSON.parse input
output = json.map{|x| x[0...2]}
p output
this prints
[[2121212, 212121], [2323232, 23232323]]
Using eval is very bad but I have no other easy option.
test_str = "[[2121212,212121,asd],[2323232,23232323,qasdasd]]"
test_str.gsub!(/([a-z]+)/) do
"'#{$1}'"
end
=> "[[2121212,212121,'asd'],[2323232,23232323,'qasdasd']]"
test_array = eval(test_str)
=> [[2121212, 212121, "asd"], [2323232, 23232323, "qasdasd"]]
test_array.each do |element|
element.delete(element.last)
end
=> [[2121212, 212121], [2323232, 23232323]]

Resources