Ruby bang strange behaviour - ruby

I wrote the following code:
print "Please enter a string"
user_input = gets.chomp
user_input.downcase!
if user_input.include? "s"
print "Changing #{user_input} to #{user_input.gsub!(/s/,'th')}"
else
print "No s in the string"
end
and I found that user_input and user_input.gsub!(/s/,'th') contain the same value. This happens because of the bang operation on gsub and because they are part of the same string.
Splitting the print in two lines changes the behavior:
print "Changing #{user_input} to "
print "#{user_input.gsub!(/s/,'th')}"
I wonder if this is a bug.

It's a feature, not a bug.
String interpolation (the "#{}" operator) works by finding all the dynamic parts, evaluating them and then glueing static and dynamic parts together into one string. Since all dynamic parts are evaluated prior to concatenation, gsub! mutates the object which user_input points to. So the first appearance of inner_page in that string will see the updated value.
Solution: don't use dangerous gsub!, use safe gsub.
You can observe here, how it really happens (VM instructions). This will probably be Chinese to you at the moment, but in a few years you'll understand. :)
code = <<-RUBY
user_input = 'blah'
print "Changing \#{user_input} to \#{user_input.gsub!(/s/,'th')}"
RUBY
puts RubyVM::InstructionSequence.new(code).disasm

Related

Ruby: issues with if-statement output

I am VERY new to Ruby. I was trying to make a simple "How's your day?" kind of thing, but when I answer with "Good" it's supposed to return "Good to hear" but it skips it and goes to my else statement that returns "Not valid". Same thing when I enter "Bad", it's supposed to give me "Oh no" but instead it gives me "Not valid". I know your usually supposed to use ==, but I don't know what I am missing here. Thank you for your help.
puts "How are you?"
answer = gets
if (answer == "Good");
print("Good to hear")
elsif (answer == "Bad");
print("Oh no")
else;
print("Not valid")
end
gets will capture a string including an endline character (\n). You're comparing the string Good (for example) against Good\n, and it obviously doesn't match.
You can observe this by adding a line after you populate answer, like puts answer.inspect (or more tersely, p answer). This will show you the string along with any not-normally-visible characters.
The easiest way to fix this will be to use answer = gets.strip, which will remove whitespace characters (including spaces, tabs, and newlines) from end of the captured input string.

So I want the answer for the user just to include numbers from 0 to 9.. How do I do this?

So as I ask for in the title. How do I make a loop that breaks when the user has entered some values that contain only number, and if not it will say try again.
prompt = "> "
puts "What is the salary for the accountant: "
print prompt
while accountant = gets.chomp
if accountant == (0..9)
puts "Nice"
break
else
"try again"
print prompt
end
end
end
A simple solution with no regex would be:
accountant.chars.all?{|n| ("0".."9") === n}
You may want to read about the "===" operator in Ruby if you don't how it works yet as it may be confusing if you come from PHP or Javascript.
What does the "===" operator do in Ruby?
Your problem is in this line:
if accountant == (0..9)
This is checking whether the value is equal to a range - which is not what you wanted.
Since the input from gets.chomp will always be a string, you need to check whether it only contains the characters: "0", "1", "2", ... and "9".
One way to do this is with a regular expression:
if accountant =~ /\A\d+\z/
\A means "start of string"
\z means "end of string"
\d+ means "one or more digit" (0-9)
Note that the solution you suggested in the comments, /^-?[0-9]+$/, is flawed since:
^ means "start of line" (so it would be possible to insert arbitrary other characters before a newline)
$ means "end of line" (so it would be possible to insert arbitrary other characters after a newline)
-? also allows an optional hyphen character. Which is presumably not what you want in this context, since the input is a salary - which surely cannot be negative!

How to display modified string?

I am creating a Daffy Duck speech converter (Very simple. Straight from CodeCademy) and I am having an issue with displaying the modified entry from the user.
Code:
puts "What would you like to convert to Daffy Duck language?"
user_input = gets.chomp
user_input.downcase!
if user_input.include? "s"
user_input.gsub!(/s/, "th")
print #{user_input}
else puts "I couldn't find any 's' in your entry. Please try again."
end
It will change any 's' in your entry to a 'th', therefore, making it sound like a Daffy Duck once read aloud. When I enter it into the interpreter, it will not display the modified string. It will just display the original entry by the user.
EDIT:
Thanks to the users below, the code is fixed, and I added a notice to the user with converted text. Thanks guys!
A # outside a string starts a comment, so #{user_input} is ignored, i.e.
print #{user_input}
is equivalent to
print
You might wonder why a single print outputs the original input. This is because without arguments print will print $_. That's a global variable which is set by gets:
user_input = gets.chomp # assume we enter "foo"
user_input #=> "foo"
$_ #=> "foo\n"
Everything works as expected if you pass a string literal:
print "#{user_input}"
or simply
print user_input
Note that gsub! returns nil if no substitutions were performed, so you can actually use it in your if statement:
if user_input.gsub!(/s/, "th")
print user_input
else
puts "I couldn't find any 's' in your entry. Please try again."
end
You just need to add double quotes around the string interpolation. Otherwise your code was just returning the input.
puts "What would you like to convert to Daffy Duck language?"
user_input = gets.chomp
user_input.downcase!
if user_input.include? "s"
user_input.gsub!(/s/, "th")
print "#{user_input}"
else
puts "I couldn't find any 's' in your entry. Please try again."
end
You don't even need interpolation, actually. print user_input works. Notice how StackOverflow was even syntax highlighting your code as a comment. :)

Formatting issue with Ruby and puts

When we were younger, my friend and I had our own secret language. Recently he challenged me to make a translator so that I could input a word and it would convert it to the right word. It's silly, but it was just for fun.
The problem I have is that the converted word output is on separate lines.
What it should do:
Word to translate: Banana
Translated word: ananabang
What it does:
Word to translate: Banana
Translated word:
anana
bang
This is the code I've written.
puts "Enter word to translate \n \n"
input = gets
firstCharacter = input[0].chr
ang = "ang"
if firstCharacter =~ /\A(a|b|c|d|A|B|C|D)\Z/
input.slice!(0)
puts firstCharacter + input + "ang"
end
I wonder if anyone can help me with the outputting issue..?
Thanks
EDIT: Fixed it with gets.chomp.
gets returns the entered string with a newline at the end. If you change your second line to input = gets.chomp, it will work as expected because chomp removes the trailing newline.
You can also refactor your code into something more concise and clear:
print "Enter word to translate: "
word = gets.chomp.downcase
puts word.sub(/^(.)/, '') << "#{$1}ang"
Or slightly more verbosely:
print "Enter word to translate: "
word = gets.chomp.downcase
translation = word[1,1000] << word[0,1]
translation << 'ang'
puts translation
There are a lot of ways to do anything in Ruby, and it often pays to step back and consider whether you're overengineering when you could be creating something simpler and more maintainable.

Capitalization of strings

Let us imagine, that we have a simple abstract input form, whose aim is accepting some string, which could consist of any characters.
string = "mystical characters"
We need to process this string by making first character uppercased. Yes, that is our main goal. Thereafter we need to display this converted string in some abstract view template. So, the question is: do we really need to check whether the first character is already written correctly (uppercased) or we are able to write just this?
theresult = string.capitalize
=> "Mystical characters"
Which approach is better: check and then capitalize (if need) or force capitalization?
Check first if you need to process something, because String#capitalize doesn't only convert the first character to uppercase, but it also converts all other characters downcase. So..
"First Lastname".capitalize == "First lastname"
That might not be the wanted result.
If I understood correctly you are going to capitalize the string anyway, so why bother checking if it's already capitalized?
Based on Tonttu answer I would suggest not to worry too much and just capitalize like this:
new_string = string[0...1].capitalize + string[1..-1]
I ran in to Tonttu's problem importing a bunch of names, I went with:
strs = "first lastname".split(" ")
return_string = ""
strs.each do |str|
return_string += "#{str[0].upcase}#{str[1..str.length].downcase} "
end
return_string.chop
EDIT: The inevitable refactor (over a year) later.
"first lastname".split(" ").map do |str|
"#{str[0].upcase}#{str[1..str.length].downcase}"
end.join(' ')
while definitely not easier to read, it gets the same result while declaring fewer temporary variables.
I guess you could write something like:
string.capitalize unless string =~ /^[A-Z].*/
Personally I would just do string.capitalize
Unless you have a flag to be set for capitalized strings which you going to check than just capitalize without checking.
Also the capitalization itself is probably performing some checking.

Resources