Converting to_i doesn't work (Ruby) - ruby

I'm pretty new to programming and i'm doing fundamentals on codewars.com and I'm having some trouble with this one. The objective is to take a group of integers, reverse them, and put them into an array. Here's my code. (I made the tf function to see what was going on in the code.)
def digitize(n)
answer = n.to_s.split(//).reverse!
def tf(it)
str_test = it.is_a? String
int_test = it.is_a? Integer
puts "String: #{str_test}"
puts "Integer: #{int_test}"
end
Array(answer)
unless answer.is_a? Integer
for item in answer
item.to_i
puts item
tf(item)
end
end
return answer
end
Sample test:
Test.assert_equals(digitize(35231),[1,3,2,5,3])
When tested, it returns:
1
String: true
Integer: false
3
String: true
Integer: false
2
String: true
Integer: false
5
String: true
Integer: false
3
String: true
Integer: false
Can one of you guys help me figure out where it goes wrong?

Assigning
item = item.to_i
Would fix the output in tf, but your returned answer would still be all strings. If you want to do this one by one like you're doing you would need to assign it back into the index of the array:
answer.each_with_index do |item, index|
answer[index] = item.to_i
end
Though, an even better way to do this would be with map (returns a new array) or map! (in-place):
# return this line (make it the last line in the method) or make sure
# to re-assign answer
answer.map(&:to_i)
# or do this one to use `answer` later on with all integers.
answer.map!(&:to_i)
(See this question about that &:to_i syntax).
It should also be noted (maybe), that Rubyists in general don't like for loops and prefer each loops.
Also, the line:
Array(answer)
doesn't modify the answer in place, and returns it cast to an array, so the line is doing nothing:
a = "1"
Array(a) # => ["1"]
a # => "1"
a = Array(a) # => ["1"]
a # => ["1"]
You also, don't even need to do this, since answer is already an array from where you split it (You could also have used chars instead of split(//)). The line unless answer.is_a?(Integer) will thusly never be true.
The last major thing, I see is that in newer versions of ruby, there's a built-in method to do all this, digits:
35231.digits # => [1, 3, 2, 5, 3]

Related

Optimize print output where i use check on zero. Ruby

Currently, I'm having print like this
print ((stamp_amount[0], 'first mark') unless stamp_amount[0].zero?), (', ' if !stamp_amount[0].zero? && !stamp_amount[1].zero?),
((stamp_amount[1], 'second mark') unless stamp_amount[1].zero?)
stamp_amount is an array with 2 integer values
Let's say in the current situation stamp_amount[0] = 10 and stamp_amount[1] = 3
Output preview:
10 first mark, 3 second mark
So if stamp_amount[0] = 0 the 10 first mark, part won't be show. Same if stamp_amount[1] = 0 the , 3 second mark part won't be shown
For me, it seems a little bit incorrect in terms of theory. Could you please suggest me the more correct or less painful print of this? :)
Cheers!
Your code is trying to join a sequence of up to two elements with a separator. The joining is a solved problem, see Array#join.
The problem can be then reduced to "how can I produce the correct sequence, given my stamp_amount input". Now this can be done in a thousand ways. Here's one:
def my_print(stamp_amount)
ary = [
!stamp_amount[0].zero? && stamp_amount[0],
!stamp_amount[1].zero? && stamp_amount[1],
].select{|elem| elem }
ary.join(', ')
end
my_print([10, 3]) # => "10, 3"
my_print([0, 3]) # => "3"
my_print([10, 0]) # => "10"
my_print([0, 0]) # => ""
Here's another
ary = []
ary << stamp_amount[0] unless stamp_amount[0].zero?
ary << stamp_amount[1] unless stamp_amount[1].zero?
ary.join(', ')
Here's yet another. This version can handle stamp_amount of any length.
ary = stamp_amount.reject(&:zero?)
ary.join(', ')
I'd go with the third, but the second one may be the easiest to understand for a beginner.
Use the select, as an alternative to reject (shown in part 3 of the answer by Sergio Tulentsev). It is just asa readable, and depending on the context and on the future changes to the code, you may prefer one versus the other.
puts stamp_amount.select{ |a| !a.zero? }.join(", ")
A few examples of inputs and outputs are:
stamp_amount output
--------------------------------------------------------------------------
10, 3 10, 3
10, 0 10
0, 3 3
0, 0 (prints an empty line, because the selected array is empty)
You're calculating zero? on index points more often than is needed, but the first thing I would look at refactoring here is the readability of the code. It might be nicer to calculate the message to print outside of the print method and explain what is happening with variable names.
# rubocop is going to complain about variable assignment like this
first_amount, second_amount = *stamp_amount
We can actually use the reason rubocop prefers the .zero? over == 0 or .empty? method to guide our development. zero? is in essence just empty? but it communicates the meaning of what you are attempting to do in a better manner. I would use this reasoning when assigning strings to variables that explain what they are doing.
some_name_that_explains_what_this_is_0 = "#{first_amount} piecu centu marka"
some_name_that_explains_what_this_is_1 = "#{second_amount} tris centu marka"
Your current code is confusing as you have the possibility of printing a string like "10 tris centu marka" which does not make lexical sense and probably not what you are after considering tis evaluates to 'second mark', which would pose an issue if the first value is zero. We also could reject zero integers before we start converting them to strings.
array = [1, 0].reject(&:zero?)
Now we can take the array and do something like:
string = []
array.each_with_index { |e, i| string << "#{e} #{Ordinalize.new(i).ordinalize} mark" }
message = string.join(', ')
print(message)
# ord class
class Ordinalize
def initialize(value)
#value = value
end
def ordinalize
mapping[#value]
end
def mapping
# acounting for zero index
['first', 'second']
end
end
where we are calculating the ordinalization and letting our new class handle the sentence structure for us.
Outputs:
[1, 0] => "1 first mark"
[0, 1] => "1 first mark"
[1, 2] => "1 first mark, 2 second mark"

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.

Match/compare a value with members of an array

I have an array containing four ranges:
[0..25, 26..50, 51..75, 76..100]
How can I match/compare an integer with this array? For example:
28 # => 26..50
89 # => 76..100
What is the best way to do this?
[0..25, 26..50, 51..75, 76..100].find{|r| r.include?(28)} # => 26..50
As #WandMaker said, there are range methods that can help you here
http://ruby-doc.org/core-2.2.0/Range.html#method-i-cover-3F
If I understand your question correctly, you want to check if an int is contained within your ranges?
my_array.each do |range|
if range.cover?(my_integer)
return true
end
end
return false
Regarding your comment:
I matched the number with a hash [:range => :score]. So, I have 4 ranges, scoring "bad" "so so" "ok" and "super". If my variable fits a range, it returns the score
You can use a case statement:
def score(number)
case number
when 0..25 then :bad
when 26..50 then :so_so
when 51..75 then :ok
when 76..100 then :super
end
end
score(28) #=> :so_so
score(89) #=> :super
Or if you just need to know if it matches any range:
p [0..25, 26..50, 51..75, 76..100].any?{|range| range.cover?(my_int) } #=> true
Note that cover? is faster than include?, and is safe to use when working with integers.

Division: Ruby no method error

I am trying to run this:
def ArithGeo(arr)
if arr[2]/arr[1] == arr[3]/arr[2]
return "Geometric"
else
return "Arithmetic"
end
end
print ArithGeo(STDIN.gets)
It comes back with line 2 having an "undefined method" when I run this in terminal.
Why? The array is all numbers when testing with [1,2,3,100]. (And yes I know that this will return "Arithmetic" when it isn't. I haven't gotten to that part yet.)
Also, is to_i necessary? If items in an array are already considered an integer, they're an integer, right? I also tried with to_i on each array item but it returned a "division by zero" error even when none of the items in the array were 0, and I wasn't using position 0.
One (or more) of the elements in your arr is a String.
irb(main):009:0> "string"/"another string"
NoMethodError: undefined method `/' for "string":String
When you call to_i on a String it becomes 0.
irb(main):013:0* "string".to_i
=> 0
When you divide by 0 you get an error because you can't do that.
irb(main):011:0> "string".to_i/"another string".to_i
ZeroDivisionError: divided by 0
You can fix your code by changing this line:
print ArithGeo(STDIN.gets)
to this:
print ArithGeo(STDIN.gets.strip.split(',').map(&:to_i))
Then enter your inputs like this:
1,2,3,100 # don't include the "[]" around the numbers
Since your input is of ruby syntax [1,2,3,100] you need to evaluate it.
def ArithGeo(arr)
puts "#{arr.class} , #{arr}"
if arr[2]/arr[1] == arr[3]/arr[2]
return "Geometric"
else
return "Arithmetic"
end
end
puts ArithGeo(eval STDIN.gets )
The input:
[1, 2, 3, 100]
The result:
Array , [1, 2, 3, 100]
Arithmetic
Also , I would recommend using floats to prevent integer rounding.
if arr[2].to_f/arr[1] == arr[3].to_f/arr[2]
Edit:
A much better (safer + more generic) is to use:
JSON.parse( array_string )
For example:
JSON.parse("[1 , 2]")
=> [1, 2]
JSON.parse("[1 , 2]").class
=> Array
And if you really want to be on the safe side , you'll need to add exception handling for JSON parsing errors.
You're passing a String to your method (IO.gets returns a string) when what you really want is an array of integers. If you just pass in this string, you will find that division is not defined for Strings. If you attempt to convert the input to an integer first, any leading non-numeric characters will cause the string to be converted to 0.
Try
arr = STDIN.gets.split(', ').map(&:to_i)
ArithGeo(arr)
It depends on your arr elements. Say, if arr elements are strings, then you will get a undefined method '/' for String (NoMethodError).
You need to make sure your arr elements are numbers i.e. integer or floats etc. on which the division (/) method is implemented.
Update
You can input the values comma separated and convert that string to an array using String#split method as I mentioned in the comment:
def ArithGeo(str)
arr = str.split(',').map(&:to_i) # split the values by comma and make them integer and put in array arr
# then you can use the following beause now your arr is an array, make sure you have at least 4 elements as you
# used index 3 in the calculation
if arr[2]/arr[1] == arr[3]/arr[2]
return "Geometric"
else
return "Arithmetic"
end
end
print ArithGeo(STDIN.gets)

Guessing game for ruby

I'm currently trying to create a simply made guessing game, In the code there will be three set number (for now) that a person has to guess. If he/she guesses all the numbers correctly it puts, "Congrats, you win!"
Now as a beginning test i just wanted the user to guess one number correctly and the code gives back correct or incorrect.
random_guess = [1, 3, 5]
puts "Please Pick a number, 1-5"
pick_num = gets.chomp
if pick_num == random_guess = true
puts "Correct!"
else
puts "Incorrect!"
end
(I know this code is very beginner, i'm very new to ruby.) for some reason every time i run this program it puts incorrect.
Your if statement is wrong. It should be:
if random_guess.include? pick_num.to_i
Note that if you leave off the to_i the equality check will always fail because you're comparing the integer 3 against the string "3".
Just to help you a bit more. Since your end goal is to have the user guess all the numbers correctly. You can just loop on the same if statement I wrote above. And every time they guess a correct number you can remove it from the array like such:
random_guess.delete(pick_num.to_i)
Once the array is empty, the user has won.
Making random_guess random:
random_guess = []
3.times{random_guess << rand(1..5)}
random_guess
# => [5, 1, 4] # will be random in every other iteration
Checking if the number exists, you can either use index or include?:
if random_guess.index(pick_num.to_i) # alternatively random_guess.index(pick_num.to_i)
puts "Correct!"
else
puts "Incorrect!"
end
Why is your code always printing incorrect?
You are doing if pick_num == random_guess = true, which is a blunder. What is actually happening here is:
you are assigning true to random_guess. i.e. irrespective to what value (Array) random_guess holds, you are overwriting it with true.
Then you are comparing random_guess with pick_num.
So essentially you are doing this:
if pick_num == (random_guess = true) # say pick_num = "1"
# "1" == true # which is obviously false.
The correct conditional statement should be:
if pick_num == random_guess
However this will also print false every time. Reason?
pick_num is a string.
random_guess is an array that contains integer values.
You are comparing two different object types. So its always false.
Therefore the right way to solve this is checking whether the user entered value exists in the Array. For that you can use Array#index or Array#include?. Hence the statement in my proposed solution:
if random_guess.index(pick_num.to_i)
NOTE: pick_num.to_i converts pick_num (a character) into an integer. This is required as your array contains only integers and not characters.

Resources