Why does `gsub!` return `nil`? - ruby

I am using a hash map to advance the character by position: "a" into "b", etc., and to capitalize vowels.
def LetterChanges(str)
str.to_s
puts str
h = ("a".."z").to_a
i = ("b".."z").to_a.push("a")
hash = Hash[h.zip i]
new_str = str.downcase.gsub(/[a-z]/,hash)
new_str.gsub!(/[aeiou]/) {|n| n.upcase }
end
LetterChanges("hello world")
LetterChanges("sentence")
LetterChanges("replace!*")
LetterChanges("coderbyte")
LetterChanges("beautiful^")
LetterChanges("oxford")
LetterChanges("123456789ae")
LetterChanges("this long cake#&")
LetterChanges("a b c dee")
LetterChanges("a confusing /:sentence:/[ this is not!!!!!!!~")
The above code works as expected except for the examples "replace!*" and "123456789ae", for which it returns nil. Why is this?

String#gsub! modifies the original string, and returns that string or nil if no replacements were performed.
String#gsub does not modify the original string but always return the result even if nothing was changed.
Either return new_str at the end of your method, or use gsub instead of gsub!.
This is somewhat of a pattern in Ruby - when multiple version of a method exist, the one with ! will modify the receiver and the one without will simply return the result.
As an aside, it looks like you're not using the result of str.to_s for anything. If you know it's a string, to_s is pointless. If it might not be, you should make use of the result, for example like so:
str = str.to_s

String#gsub! returns nil when no substitution is performed.
new_str.gsub!(/[aeiou]/) {|n| n.upcase }
returns nil when new_str doesn't contain any vowel letters. This is the case for example, if str is "replace!*", new_str is sfqmbdf!*, no vowels.

Related

is there a function to capitalize an obj accessed with str[i] in Ruby?

print str[i].upcase is not working and i have to capitalize specific letters determined using an index. Can someone help me with this?
def mumble_letters
str = nil
print "Please write a string : "
str = gets.to_str
# puts str.length
while str.length == 1
print "Please write a string : "
str = gets.to_str
end
for i in 0..str.length
print str[i].upcase!
i.times{ print str[i].capitalize}
if i != str.length - 1
print"-"
end
end
end
mumble_letters
the error I get is : undefined method `upcase' for nil:NilClass (NoMethodError)
Did you mean? case
Problem
str[i].upcase! mutates the single character in the Array value into an uppercase character. However, at least on Ruby 2.7.1, it won't actually change the contents of your original String object until you reassign the element back to the String index you want modified. For example:
str[i] = str[i].upcase
However, the approach above won't work with frozen strings, which are fairly common in certain core methods, libraries, and frameworks. As a result, you may encounter the FrozenError exception with the index-assignment approach.
Solution
There's more than one way to solve this, but one way is to:
split your String object into an Array of characters,
modify the letter at the desired indexes,
rejoin the characters into a single String, and then
re-assign the modified String to your original variable.
For example, showing some intermediate steps:
# convert String to Array of characters
str = "foobar"
chars = str.chars
# contents of your chars Array
chars
#=> ["f", "o", "o", "b", "a", "r"]
# - convert char in place at given index in Array
# - don't rely on the return value of the bang method
# to be a letter
# - safe navigation handles various nil-related errors
chars[3]&.upcase!
#=> "B"
# re-join Array of chars into String
chars.join
#=> "fooBar"
# re-assign to original variable
str = chars.join
str
#=> "fooBar"
If you want, you can perform the same operation on multiple indexes of your chars Array before re-joining the elements. That should yield the results you're looking for.
More concisely:
str = "foobar"
chars = str.chars
chars[3]&.upcase!
p str = chars.join
#=> "fooBar"
Personally, I find operating on an Array of characters more intuitive and easier to troubleshoot than making in-place changes through repeated assignments to indexes within the original String. Furthermore, it avoids exceptions raised when trying to modify indexes within a frozen String. However, your design choices may vary.
str[i].upcase returns the upcased letter, but does not modify it in place. Assign it back to the string for it to work.
str = 'abcd'
str[2] = str[2].upcase #=> "C"
str #=> "abCd"
I can see two problems with your code...
First, an empty string has a length of 0 so what you wanted to write is
while str.length == 0
Secondly, when you do...
for i in 0..str.length
You are iterating up to the string length INCLUDING the string length. If the string has five characters, it actually only has valid indexes 0 through 4 but you are iterating 0 through 5. And str[5] doesn't exist so returns nil and you cannot do upcase! on a nil.
To handle that common situation, Ruby has the tripe dot operator
for i in 0...str.length
...which will stop at the integer before the length, which is what you want.
It's also more ruby-eque to do
(0...str.length).each do |i|

Ruby `downcase!` returns `nil`

With this code:
input = gets.chomp.downcase!
puts input
if there is at least one uppercase letter in the input, the input will be put on screen, freed of its uppercases. But if the input has no uppercase letter, it will put nil, like if nothing was written.
I want my input to be fully downcased; if it is a string with no uppercase letter, it should return the same string.
I thought about something like this:
input = gets.chomp
if input.include(uppercase) then input.downcase! end
But this doesn't work. I hope someone has an idea on how I should do this.
According to the docs for String:
(emphasis is mine added)
downcase
Returns a copy of str with all uppercase letters replaced with their lowercase counterparts. The operation is locale
insensitive—only characters “A” to “Z” are affected. Note: case
replacement is effective only in ASCII region.
downcase!
Downcases the contents of str, returning nil if no changes were made. Note: case replacement is effective only in ASCII
region.
Basically it says that downcase! (with exclamation mark) will return nil if there is no uppercase letters.
To fix your program:
input = gets.chomp.downcase
puts input
Hope that helped!
This will work:
input = gets.chomp.downcase
puts input
String#downcase
Returns a modified string and leaves the original unmodified.
str = "Hello world!"
str.downcase # => "hello world!"
str # => "Hello world!"
String#downcase!
Modifies the original string, returns nil if no changes were made or returns the new string if a change was made.
str = "Hello world!"
str.downcase! # => "hello world!"
str # => "hello world!"
str.downcase! # => nil
! (bang) methods
It's common for Ruby methods with ! / non-! variants to behave in a similar manner. See this post for an in-depth explanation why.
The reason that downcase! returns nil is so you know whether or not the object was changed. If you're assigning the modified string to another variable, like you are here, you should use downcase instead (without the bang !).
If you're not familiar, the standard library bang methods typically act on the receiver directly. That means this:
foo = "Hello"
foo.downcase!
foo #=> "hello"
Versus this:
foo = "Hello"
bar = foo.downcase
foo #=> "Hello"
bar #=> "hello"

eval returns string instead of value

Can someone explain why eval is returning the string, rather than the result of the expression?
perms=["12+2","22","-2+"]
perms.each { |line|
matches=/^[\d]+[+-\/\*]{1}[\d]+$/.match(line)
s=matches.to_a
puts s
puts eval(s.to_s)
}
s = matches.to_a is an array ["12+2"], the eval(s.to_s) will return the array ["12+2"], and when you print it, you will get the output 12+2, a string representation of the array ["12+2"].
You should be evaling the element of the array, in this case, s[0] or s.first.
Fix it like this:
perms=["12+2","22","-2+"]
perms.each do |line|
matches=/^[\d]+[+-\/\*]{1}[\d]+$/.match(line)
if matches
s = matches.to_a
puts eval(s.first)
end
end
matches can be nil if there was no match. Use eval only if its not nil by checking if matches
You could further simplify the code and write something like this:
perms=["12+2","22","-2+"]
perms.each do |line|
puts eval(line) if line =~ /^[\d]+[+-\/\*]{1}[\d]+$/
end
Instead of iterating through inputs, one might directly map them to outputs:
perms.map do |p| # map inputs to outputs
eval(p) if p =~ /\A\d+[+-\/*]\d+\z/ # return eval’ed or nil
end.compact # get rid of nils
#⇒ [14]

Taking a string and returning it with vowels removed

I'm attempting to write a function that takes a string and returns it with all vowels removed. Below is my code.
def vowel(str)
result = ""
new = str.split(" ")
i = 0
while i < new.length
if new[i] == "a"
i = i + 1
elsif new[i] != "a"
result = new[i] + result
end
i = i + 1
end
return result
end
When I run the code, it returns the exact string that I entered for (str). For example, if I enter "apple", it returns "apple".
This was my original code. It had the same result.
def vowel(str)
result = ""
new = str.split(" ")
i = 0
while i < new.length
if new[i] != "a"
result = new[i] + result
end
i = i + 1
end
return result
end
I need to know what I am doing wrong using this methodology. What am I doing wrong?
Finding the bug
Let's see what's wrong with your original code by executing your method's code in IRB:
$ irb
irb(main):001:0> str = "apple"
#=> "apple"
irb(main):002:0> new = str.split(" ")
#=> ["apple"]
Bingo! ["apple"] is not the expected result. What does the documentation for String#split say?
split(pattern=$;, [limit]) → anArray
Divides str into substrings based on a delimiter, returning an array of these substrings.
If pattern is a String, then its contents are used as the delimiter when splitting str. If pattern is a single space, str is split on whitespace, with leading whitespace and runs of contiguous whitespace characters ignored.
Our pattern is a single space, so split returns an array of words. This is definitely not what we want. To get the desired result, i.e. an array of characters, we could pass an empty string as the pattern:
irb(main):003:0> new = str.split("")
#=> ["a", "p", "p", "l", "e"]
"split on empty string" feels a bit hacky and indeed there's another method that does exactly what we want: String#chars
chars → an_array
Returns an array of characters in str. This is a shorthand for str.each_char.to_a.
Let's give it a try:
irb(main):004:0> new = str.chars
#=> ["a", "p", "p", "l", "e"]
Perfect, just as advertised.
Another bug
With the new method in place, your code still doesn't return the expected result (I'm going to omit the IRB prompt from now on):
vowel("apple") #=> "elpp"
This is because
result = new[i] + result
prepends the character to the result string. To append it, we have to write
result = result + new[i]
Or even better, use the append method String#<<:
result << new[i]
Let's try it:
def vowel(str)
result = ""
new = str.chars
i = 0
while i < new.length
if new[i] != "a"
result << new[i]
end
i = i + 1
end
return result
end
vowel("apple") #=> "pple"
That looks good, "a" has been removed ("e" is still there, because you only check for "a").
Now for some refactoring.
Removing the explicit loop counter
Instead of a while loop with an explicit loop counter, it's more idiomatic to use something like Integer#times:
new.length.times do |i|
# ...
end
or Range#each:
(0...new.length).each do |i|
# ...
end
or Array#each_index:
new.each_index do |i|
# ...
end
Let's apply the latter:
def vowel(str)
result = ""
new = str.chars
new.each_index do |i|
if new[i] != "a"
result << new[i]
end
end
return result
end
Much better. We don't have to worry about initializing the loop counter (i = 0) or incrementing it (i = i + 1) any more.
Avoiding character indices
Instead of iterating over the character indices via each_index:
new.each_index do |i|
if new[i] != "a"
result << new[i]
end
end
we can iterate over the characters themselves using Array#each:
new.each do |char|
if char != "a"
result << char
end
end
Removing the character array
We don't even have to create the new character array. Remember the documentation for chars?
This is a shorthand for str.each_char.to_a.
String#each_char passes each character to the given block:
def vowel(str)
result = ""
str.each_char do |char|
if char != "a"
result << char
end
end
return result
end
The return keyword is optional. We could just write result instead of return result, because a method's return value is the last expression that was evaluated.
Removing the explicit string
Ruby even allows you to pass an object into the loop using Enumerator#with_object, thus eliminating the explicit result string:
def vowel(str)
str.each_char.with_object("") do |char, result|
if char != "a"
result << char
end
end
end
with_object passes "" into the block as result and returns it (after the characters have been appended within the block). It is also the last expression in the method, i.e. its return value.
You could also use if as a modifier, i.e.:
result << char if char != "a"
Alternatives
There are many different ways to remove characters from a string.
Another approach is to filter out the vowel characters using Enumerable#reject (it returns a new array containing the remaining characters) and then join the characters (see Nathan's answer for a version to remove all vowels):
def vowel(str)
str.each_char.reject { |char| char == "a" }.join
end
For basic operations like string manipulation however, Ruby usually already provides a method. Check out the other answers for built-in alternatives:
str.delete('aeiouAEIOU') as shown in Gagan Gami's answer
str.tr('aeiouAEIOU', '') as shown in Cary Swoveland's answer
str.gsub(/[aeiou]/i, '') as shown in Avinash Raj's answer
Naming things
Cary Swoveland pointed out that vowel is not the best name for your method. Choose the names for your methods, variables and classes carefully. It's desirable to have a short and succinct method name, but it should also communicate its intent.
vowel(str) obviously has something to do with vowels, but it's not clear what it is. Does it return a vowel or all vowels from str? Does it check whether str is a vowel or contains a vowel?
remove_vowels or delete_vowels would probably be a better choice.
Same for variables: new is an array of characters. Why not call it characters (or chars if space is an issue)?
Bottom line: read the fine manual and get to know your tools. Most of the time, an IRB session is all you need to debug your code.
I should use regex.
str.gsub(/[aeiou]/i, "")
> string= "This Is my sAmple tExt to removE vowels"
#=> "This Is my sAmple tExt to removE vowels"
> string.delete 'aeiouAEIOU'
#=> "Ths s my smpl txt t rmv vwls"
You can create a method like this:
def remove_vowel(str)
result = str.delete 'aeiouAEIOU'
return result
end
remove_vowel("Hello World, This is my sample text")
# output : "Hll Wrld, Ths s my smpl txt"
Live Demo
Assuming you're trying to learn about the basics of programming, rather than finding the quickest one-liner to do this (which would be to use a regular expression as Avinash has said), you have a number of problems with your code you need to change.
new = str.split(" ")
This line is likely the culprit, because it splits the string based on spaces. So your input string would have to be "a p p l e" to have the effect you're looking for.
new = str.split("")
You should also remove the duplicate i = i+1 once you've changed that.
As others have already identified the problems with the OP's code, I will merely suggest an alternative; namely, you could use String#tr:
"Now is the time for all good people...".tr('aeiouAEIOU', '')
#=> "Nw s th tm fr ll gd ppl..."
If regex is not allowed, you can do it this way:
def remove_vowels(string)
string.split("").delete_if { |letter| %w[a e i o u].include? letter }.join
end

Ruby: String no longer mixes in Enumerable in 1.9

So how can I still be able to write beautiful code such as:
'im a string meing!'.pop
Note: str.chop isn't sufficient answer
It is not what an enumerable string atually enumerates. Is a string a sequence of ...
lines,
characters,
codepoints or
bytes?
The answer is: all of those, any of those, either of those or neither of those, depending on the context. Therefore, you have to tell Ruby which of those you actually want.
There are several methods in the String class which return enumerators for any of the above. If you want the pre-1.9 behavior, your code sample would be
'im a string meing!'.bytes.to_a.pop
This looks kind of ugly, but there is a reason for it: a string is a sequence. You are treating it as a stack. A stack is not a sequence, in fact it pretty much is the opposite of a sequence.
That's not beautiful :)
Also #pop is not part of Enumerable, it's part of Array.
The reason why String is not enumerable is because there are no 'natural' units to enumerate, should it be on a character basis or a line basis? Because of this String does not have an #each
String instead provides the #each_char and #each_byte and #each_line methods for iteration in the way that you choose.
Since you don't like str[str.length], how about
'im a string meing!'[-1] # returns last character as a character value
or
'im a string meing!'[-1,1] # returns last character as a string
or, if you need it modified in place as well, while keeping it an easy one-liner:
class String
def pop
last = self[-1,1]
self.chop!
last
end
end
#!/usr/bin/ruby1.8
s = "I'm a string meing!"
s, last_char = s.rpartition(/./)
p [s, last_char] # => ["I'm a string meing", "!"]
String.rpartition is new for 1.9 but it's been back-ported to 1.8.7. It searches a string for a regular expression, starting at the end and working backwards. It returns the part of the string before the match, the match, and the part of the string after the match (which we discard here).
String#slice! and String#insert is going to get you much closer to what you want without converting your strings to arrays.
For example, to simulate Array#pop you can do:
text = '¡Exclamation!'
mark = text.slice! -1
mark == '!' #=> true
text #=> "¡Exclamation"
Likewise, for Array#shift:
text = "¡Exclamation!"
inverted_mark = text.slice! 0
inverted_mark == '¡' #=> true
text #=> "Exclamation!"
Naturally, to do an Array#push you just use one of the concatenation methods:
text = 'Hello'
text << '!' #=> "Hello!"
text.concat '!' #=> "Hello!!"
To simulate Array#unshift you use String#insert instead, it's a lot like the inverse of slice really:
text = 'World!'
text.insert 0, 'Hello, ' #=> "Hello, World!"
You can also grab chunks from the middle of a string in multiple ways with slice.
First you can pass a start position and length:
text = 'Something!'
thing = text.slice 4, 5
And you can also pass a Range object to grab absolute positions:
text = 'This is only a test.'
only = text.slice (8..11)
In Ruby 1.9 using String#slice like this is identical to String#[], but if you use the bang method String#slice! it will actually remove the substring you specify.
text = 'This is only a test.'
only = text.slice! (8..12)
text == 'This is a test.' #=> true
Here's a slightly more complex example where we reimplement a simple version of String#gsub! to do a search and replace:
text = 'This is only a test.'
search = 'only'
replace = 'not'
index = text =~ /#{search}/
text.slice! index, search.length
text.insert index, replace
text == 'This is not a test.' #=> true
Of course 99.999% of the time, you're going to want to use the aforementioned String.gsub! which will do the exact same thing:
text = 'This is only a test.'
text.gsub! 'only', 'not'
text == 'This is not a test.' #=> true
references:
Ruby String Documentation

Resources