Datatype conversion error in Ruby for-loop - ruby

I'm looking for some help understanding why I get an error (no implicit conversion of nil into String) when attempting to use a for-loop to search through an array of letters (and add them to a resulting string, which seems to be the real problem), but not when I use a while-loop or 'each' for the same purposes. I've looked through a lot of documentation, but haven't been able to find an answer as to why this is happening. I understand that I could just use the "each" method and call it a day, but I'd prefer to comprehend the cause as well as the effect (and hopefully avoid this problem in the future).
The following method works as desired: printing "result" which is the original string, only with "!" in place of any vowels.
s="helloHELLO"
result=""
vowels=["a","e","i","o","u","A","E","I","O","U"]
string_array=s.split("")
string_array.each do |i|
if vowels.include?(i)
result+="!"
else
result+=i
end
end
puts result
However, my initial attempt (posted below) raises the error mentioned above: "no implicit conversion of nil into String" citing lines 5 and 9.
s="helloHELLO"
result=""
vowels=["a","e","i","o","u","A","E","I","O","U"]
string_array=s.split("")
for i in 0..string_array.length
if vowels.include?(string_array[i])
result+= "!"
else
result+=string_array[i]
end
end
puts result
Through experimentation, I managed to get it working; and I determined--through printing to screen rather than storing in "result"--that the problem occurs during concatenation of the target letter to the string "result". But why is "string_array[i]" (line #9) seen as NIL rather than as a String? I feel like I'm missing something very obvious.
If it matters: This is just a kata on CodeWars that lead me to a fundamental question about data types and the mechanics of the for..in loop. This seemed very relevant, but not 100% on the mark for my question: "for" vs "each" in Ruby.
Thanks in advance for the help.
EDIT:
Okay, I think I figured it out. I'd still love some answers though, to confirm, clarify, or downright refute.
I realized that if I wanted to use the for-loop, I should use the array itself as the "range" rather than "0..array.length", like so:
s="helloHELLO"
result=""
vowels=["a","e","i","o","u","A","E","I","O","U"]
string_array=s.split("")
for i in string_array
if vowels.include?(i)
result+= "!"
else
result+=i
end
end
puts result
So, is it that since the "each" method variable (in this case, "i") doesn't exist outside the scope of the main block, its datatype become nil after evaluating whether it's included in the 'vowels' array?

You got beaten by the classical error when iterating an array starting with index 0, instead of length as end position it should be length-1.
But it seems like you come from some other programming language, your code is not Rubyesque, a 'For' for example is seldom used.
Ruby is a higher language than most others, it has many solutions build in, we call it 'sugared' because Ruby is meant to make us programmers happy. What you try to achieve can be done in just one line.
"helloHELLO".scan(/[aeoui]/i).count
Some explanation: the literal array "hello HELLO" is a String, meaning an object of the String class and as such has a lot of methods you can use, like scan, which scans the string for the regular expression /[aeoui]/ which means any of the characters enclosed in the [], the i at the end makes it case insentitive so you don't have to add AEOUI. The scan returns an array with the matching characters, an object of the Array class has the method count, which gives us the ... Yeah once you get the drift it's easy, you can string together methods which act upon each other.

Your for loop:
for i in 0..string_array.length
loops from 0 to 10.
But string[10] #=> nil because there is no element at index 10. And then on line 9 you try to add nil to result
result = result + string_array[i] #expanded
You can't add nil to a string like this, you have to convert nil to a string explicitly thus the error. The best way to fix this issue is to change your for loop to:
for i in 0..string_array.length-1
Then your loop will finish at the last element, string[9].

Related

Ruby idiom to shovel into a string or nil? (e.g. shovel or assign / safe shovel)

I'd like to do this:
summary << reason
In my case, summary is a string, containing several sentences, and reason is one such sentence.
This works fine if the target already has a value, but sometimes summary can be nil. In that case, this raises:
NoMethodError: undefined method `<<' for nil:NilClass
So, I could write something like this:
if summary
summary << reason
else
summary = reason
end
This is cumbersome and ugly. I can hide it away in a new method like append(summary, reason), but I'm hoping there's a ruby idiom that can wrap this up concisely.
I've optimistically tried a few variants, without success:
summary += reason
summary &<< reason
In other scenarios, I might build an array of reasons (you can shovel into an empty array just fine), then finally join them into a summary...but that's not viable in my current project.
I also can't seed summary with an empty string (shoveling into an empty string also works fine), as other code depends on it being nil at times.
So, is there a "safe shovel" or simple "shovel or assign" idiom in Ruby, particularly for strings that might be nil?
I prefer #Oto Brglez's answer, but it inspired another solution that might be useful to someone:
summary = [summary, reason].join
This may or may not be easier to read, and probably is less performant. But it handles the nil summary problem without explicit alternation.
You can solve this with something like this; with the help of ||.
summary = (summary || '') + reason
Or like so with the help of ||= and <<:
(summary ||= '') << reason

Ruby iterator yield

I'm wondering why the following tag methods produce different results:
Method 1:
def tag(html)
print "<#{html}>#{yield}</#{html}>"
end
Method 2:
def tag(html)
print "<#{html}>"
print yield
print "</#{html}>"
end
When I ran the following code in terminal using the above methods:
tag(:ul) do
tag(:li) { "It sparkles!" }
tag(:li) { "It shines!" }
tag(:li) { "It mesmerizes!" }
end
The first one gave me:
<li>It sparkles!</li><li>It shines!</li><li>It mesmerizes!</li><ul></ul>
The second one gave me:
<ul><li>It sparkles!</li><li>It shines!</li><li>It mesmerizes!</li></ul>
The second one is the output that I'm looking.
How come the first method prints 'yield' before it prints what comes before 'yield' in the string?
Just to echo #tadman's answer: order of evaluation AND inconsistency of api. Your block sometimes returns strings and sometimes prints strings as a side-effect.
print "<#{html}>"
print yield
print "</#{html}>"
Here you print, then yield. If the block returns a string (one of :li blocks), then it's printed right here. If it's a :ul block, then its side-effects happen (printing of li blocks) and nil is printed after that.
In the other case
print "<#{html}>#{yield}</#{html}>"
Ruby has to assemble one string to print. Which means yielding before any printing. Which means that side-effects happen before printing the opening <ul>. As the ul block returns nil, that's why it's printed empty at the end of the string (<ul></ul>).
Does it make sense?
The main problem is the order of operations. When you call print it will print immediately, there's no delay, which can cause problems.
In Ruby it's often easier to deal with code that returns strings rather than code that causes side-effects like printing. If they return strings you have control over where that output goes. If they print things immediately you need to be very careful about the order you call them in.
The way you're calling that code in the final assembly with the tag(:ul) call is actually going to be trouble. The second version of your method coincidentally orders things correctly.
It's not necessarily easy to fix this. If you return a string, then only the last string from your three tag calls will be used. If you print, you'll have to be sure you're using the second method to make it work.
Within the Rails system there's a way of capturing the output of these things for buffering purposes, but that's a very messy thing to try and do, it can get really confused when you try and handle all cases.
Where possible create some kind of buffer these things can write to, then when everything's done write that out with print or whatever.

How to reset value of local variable within loop?

I'd like to point out I tried quite extensively to find a solution for this and the closest I got was this. However I couldn't see how I could use map to solve my issue here. I'm brand new to Ruby so please bear that in mind.
Here's some code I'm playing with (simplified):
def base_word input
input_char_array = input.split('') # split string to array of chars
#file.split("\n").each do |dict_word|
input_text = input_char_array
dict_word.split('').each do |char|
if input_text.include? char.downcase
input_text.slice!(input_text.index(char))
end
end
end
end
I need to reset the value of input_text back to the original value of input_char_array after each cycle, but from what I gather since Ruby is reference-based, the modifications I make with the line input_text.slice!(input_text.index(char)) are reflected back in the original reference, and I end up assigning input_text to an empty array fairly quickly as a result.
How do I mitigate that? As mentioned I've tried to use .map but maybe I haven't fully wrapped my head around how I ought to go about it.
You can get an independent reference by cloning the array. This, obviously, has some RAM usage implications.
input_text = input_char_array.dup
The Short and Quite Frankly Not Very Good Answer
Using slice! overwrites the variable in place, equivalent to
input_text = input_text.slice # etc.
If you use plain old slice instead, it won't overwrite input_text.
The Longer and Quite Frankly Much Better Answer
In Ruby, code nested four levels deep is often a smell. Let's refactor, and avoid the need to reset a loop at all.
Instead of splitting the file by newline, we'll use Ruby's built-in file handling module to read through the lines. Memoizing it (the ||= operator) may prevent it from reloading the file each time it's referenced, if we're running this more than once.
def dictionary
#dict ||= File.open('/path/to/dictionary')
end
We could also immediately make all the words lowercase when we open the file, since every character is downcased individually in the original example.
def downcased_dictionary
#dict ||= File.open('/path/to/dictionary').each(&:downcase)
end
Next, we'll use Ruby's built-in file and string functions, including #each_char, to do the comparisons and output the results. We don't need to convert any inputs into Arrays (at all!), because #include? works on strings, and #each_char iterates over the characters of a string.
We'll decompose the string-splitting into its own method, so the loop logic and string logic can be understood more clearly.
Lastly, by using #slice instead of #slice!, we don't overwrite input_text and entirely avoid the need to reset the variable later.
def base_word(input)
input_text = input.to_s # Coerce in case it's not a string
# Read through each line in the dictionary
dictionary.each do |word|
word.each_char {|char| slice_base_word(input_text, char) }
end
end
def slice_base_word(input, char)
input.slice(input.index(char)) if input.include?(char)
end

Ruby: "undefined method" error; how to use gsub more effectively?

So I'm trying to find a way to Donald Duck-ify statements inputed by users (judge me later).
This is my code so far:
puts "Wanna get Donald Duck-ified?"
print "Type some text here:"
user_input = gets.chomp
if user_input.gsub!(/s/,"th").gsub!(/ce/,"th").gsub!(/ci/,"th").gsub!(/cy/,"th")
puts "Boop - there go your s's and soft c's!"
else
puts "Dang, you didn't have any s's or soft c's!"
end
puts "#{user_input}"
Upon testing it with some input of my own ("square cycle caesar circle", specifically), I'm getting "undefined method `gsub!' for nil:NilClass" as an error.
How is gsub! undefined? If the code runs with user_input.gsub!(/s/,"th") on it own, without any other methods behind it, it works fine. Once a second method is added, the else code runs and only replacements for "s" are made. All four and I get the error above.
Does there happen to be another way of substituting multiple patterns (as named by the Ruby docs) with a single replacement? I've spent the last hours researching the problem and I still can't totally tell what the issue is.
New to Ruby. Encouraged and motivated.
Many thanks in advance.
Don't use #gsub! chained. (Actually, don't use #gsub! at all for most code.)
[gsub!] Performs the substitutions of String#gsub in place, returning str, or nil if no substitutions were performed.
Switch the code to #gsub which doesn't cause side-effects (yay!) and always returns a string (yay!) - simply compare the result with the original (unmodified) string.
Also, one could use the gsub form that accepts a hash (since Ruby 1.9.something). This has a subtle difference that replaced values will not be themselves replaced, although it doesn't matter here.
user_input.gsub(/s|ce|ci|cy/, { "s"=>"th", "ce"=>"th", "ci"=>"th", "cy"=>"th" })
# or since all are replaced with "th" (which I just noticed =^_^=) ..
user_input.gsub(/s|ce|ci|cy/, "th")
(I still recommend against gsub! because I find side effects upon strings disconcerting. However, it would work reliably when used with the non-chained forms above.)
Ruby's gsub! returns nil if it performs no substitutions. This means you can't reliably chain it like you do. If you want to verify that any of the gsubs have made any change, you can chain non-destructive gsubs (without the bang; return a new string instead of modifying the current one) instead:
input = gets.chomp
replaced = input.gsub(/s/,"th").gsub(/ce/,"th").gsub(/ci/,"th").gsub(/cy/,"th")
if input == replaced
...

Struggling to make sense of an array

So I am trying to make the transition from PHP to ruby(finally). I am attempting to complete the rubymonk challenges but I am stuck on the third challenge.
The challenge itself is easy and I've already found a solution, but I cant figure out what type of data I'm looking at or how to process it properly.
The challenge simply wants you create a method that takes a array containing some strings, and return a count of each string in that same position. so ["I","suck","at","ruby"] == ["1","4","2","4"].
That part is Ez-pz, but I cant for the life of me figure out how to process the input properly.
It gives you a shell of method and tells you to complete it
def lenght_finder(input_array)
#I added the print input_array
print input_array #=> ["I","am","genius"]["things","are","","awesome"]
end
Is this a multidimensional array?
I've tried to replicate this in IRB with
input_array = ["I","am","genius"]["things","are","","awesome"]
but it returns and error
input_array = [["I","am","genius"],["things","are","","awesome"]]
works, but that is clearly not that same.
Because of this I am struggling to traverse the array to process that data properly.
I can't get anything like input_array.flatten to work, or input_array[0] which returns "Ithings".
This is confusing me. Am I looking at a single array? a multidimensional array? Clearly it cant be a string. Why does it skip "am" when accessing input_array[0]?
Ha, like Justin Ko suggested in his comment above, what you're seeing is the stdout of running the function twice.
Since you used print, there's no newline. Use puts instead.
This should help you see it more clearly:
def length_finder(input_array)
puts '*** '+input_array.inspect
return 0
end

Resources