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

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
...

Related

Datatype conversion error in Ruby for-loop

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].

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

Can I put an if/unless clause on the next line in Ruby?

In Perl, I often find myself using the following pattern:
croak "incompatible object given: $object"
unless $object->isa('ExampleObject') and $object->can('foo');
I tried to translate this into Ruby like this:
raise ArgumentError, "incompatible object given: #{object.inspect}"
unless object.is_a?(ExampleObject) and object.respond_to?(:foo)
But that does not work because Ruby interprets unless as the start of a new statement. As far as I understand, I can put a backslash at the end of the first line, but that looks ugly and feels wrong. I could also use a regular unless condition raise error end structure, but I like the style of the original form more. Is there a nice (and idiomatic) way to write this as a single statement in Ruby?
Can I put an if/unless clause on the next line in Ruby?
You can't. From page 107 (PDF page 127) of the final draft of ISO Ruby which usually isn't relevant, but basic things like this are and it also spares us from having to read parse.y:
unless-modifier-statement ::
statement [no line-terminator here] unless expression
This is pretty clear. It just doesn't get more similar to your Perl example than:
raise ArgumentError, "incompatible object given: #{object.inspect}" unless
object.is_a?(ExampleObject) and object.respond_to?(:foo)`
or:
raise ArgumentError, "incompatible object given: #{object.inspect}" \
unless object.is_a?(ExampleObject) and object.respond_to?(:foo)
Just as you feel wrong to put a backslash at the end to force a single line statement, it is wrong to use a single line statement when it extends beyond a single line.
This is not really a solution, I was sloppy when reading the question. The OP wants a solution without backslash.
You should be able to do this:
raise ArgumentError, "incompatible object given: #{object.inspect}" \
unless object.is_a?(ExampleObject) and object.respond_to?(:foo)
The \ characters tells ruby to keep reading as if there was no line break.
As far as I know there is no other way than a \, since otherwise, as you already said, Ruby thinks it's a new statement.
Keep in mind that style guides and conventions differ from language to language. In Ruby I'd not expect an if/unless statement in a line coming after it's code. In fact I even dislike putting if/unless at the end of a line, since it reverses the reading direction from If this, then that to that, if this (then what? Ah, I need to read back again), especially when the condition is more complex than raise 'foo' if bar.empty?.
In Perl and other languages though this might be different, since you have other conventions, style guides and this ;-thingy ;)

What is the purpose of "!" and "?" at the end of method names?

Sometimes I see methods in Ruby that have "?" and "!" at the end of them, e.g:
name = "sample_string"
name.reverse
name.reverse!
name.is_binary_data?
I was wondering what their purpose is? Are they just syntax sugarcoating?
It's "just sugarcoating" for readability, but they do have common meanings:
Methods ending in ! perform some permanent or potentially dangerous change; for example:
Enumerable#sort returns a sorted version of the object while Enumerable#sort! sorts it in place.
In Rails, ActiveRecord::Base#save returns false if saving failed, while ActiveRecord::Base#save! raises an exception.
Kernel::exit causes a script to exit, while Kernel::exit! does so immediately, bypassing any exit handlers.
Methods ending in ? return a boolean, which makes the code flow even more intuitively like a sentence — if number.zero? reads like "if the number is zero", but if number.zero just looks weird.
In your example, name.reverse evaluates to a reversed string, but only after the name.reverse! line does the name variable actually contain the reversed name. name.is_binary_data? looks like "is name binary data?".
Question mark indicates that the method returns boolean. Already answered here:
What does the question mark operator mean in Ruby?
The bang indicates that the method acts on the object itself. Already answered here:
Why are exclamation marks used in Ruby methods?
In Ruby the ? means that the method is going to return a boolean and the ! modifies the object it was called on. They are there to improve readability when looking at the code.
In contrast to the – I suppose – majority of programming languages ...
Ruby, methods are allowed to end with question marks or exclamation marks.
By convention, methods that answer questions (i.e. Array#empty? returns true if the receiver is empty) end in question marks.
Potentially “dangerous” methods (ie methods that modify self or the arguments, exit! etc.) by convention end with exclamation marks.
From: http://www.ruby-lang.org/en/documentation/ruby-from-other-languages/, Section Funny method names
Beware, this isn't always the case. Take for example, Ruby Array#concat http://docs.ruby-lang.org/en/2.0.0/Array.html#method-i-concat.
Where you can get burnt badly is something like MyActiveRecordModel.column_names.concat([url]). Later calls related to MyActiveRecordModel will try to look for a column of 'url' for MyActiveRecordModel and throw.
Instead you must clone it before doing the concat. Fortunately my test suite caught this one, but.. heads up!

Resources