Maybe it's not common to do like this, but anyway. I'm curious why it's not working.
I encountered with a strange behavior which I don't fully understand.
string = "sdsdasda asdas asdas"
words = string.split
words.map! do |word|
word.split(//).map! do |character|
character.upcase #or any other operations that change character
end.join
end
p words.join(" ")
If I instead of odd "end.join" construction this
end
word.join
end
I get an error, because |word| remains the same (string and not upcase), despite of using
.map!
with an exclamation point.
So basically my question is why map! won't change the word.
.map! is being called on the result of .split(//). It's modifying the resulting array, not the original word
You can think of the result of .split(//) being placed in a new variable:
characters = word.split(//)
characters.map! do |character|
character.upcase
end
return characters.join
# `word` is not modified
The last line of code in your block determines the value returned to the previous loop.
By adding .join to the end you are essentially saying that you want to return the result of that block of code to the previous map! call. But when you add an additional line afterwards to explicitly do word.jointhen you are saying you want to return the result of word.join to the previous map! call. As Leny Sirivong pointed out, that word is still lowercase (you can confirm by printing out word right there).
Consider this alternate syntax to make it clearer:
string = "sdsdasda asdas asdas"
words = string.split
words.map! do |word|
result = word.split(//).map! {|character|character.upcase }.join
p "word: " + word
p "result: " + result
end
p words.join(" ")
You will see here that it actually works because I'm assigning the result of the loop to a new variable result. Then, result is passed back to the original map! call instead of word.
"word: sdsdasda"
"result: SDSDASDA"
"word: asdas"
"result: ASDAS"
"word: asdas"
"result: ASDAS"
If you run this you will see in this case that the end result prints ["result: SDSDASDA", "result: ASDAS", "result: ASDAS"]. That is because the last line of my loop is p "result: " + result.
Related
I am trying to .insert a space before the Uppercase letter if it's found.
Here's what I came up with, but it seems like it's an infinite loop. I don't know why:
def solution(string)
str = string.split("")
str.each_with_index do |l, i|
if l.upcase
str.insert(l[i], " ")
end
end
str.join("")
end
please let me know what I'm missing.
Because it's often a bad idea changing the object you're looping on. You insert a space before the upcase letter you found, so the next iteration you found the upcase letter again and everything repeats.
In this case regular expression seems to fit nicely
def solution(string)
string.gsub(/[[:upper:]]/, ' \0')
end
I'm trying to reverse a string using the code:
puts("Hi now it's going to be done!")
string = gets.chomp.to_s
i = string.length
while i >= 0
puts(string[i])
i = i - 1
end
It prints the string in backward order, but each word is on a single line. How can I keep all of them on a single line?
puts adds a newline to the end of the output if one isn't already present.
print does not. So do this:
while i >=0
print string[i]
i=i-1
end
puts
The final puts is because you want any further printing to be on a new line.
Try this:
"Hi now it's going to be done!".chars.inject([]) { |s, c| s.unshift(c) }.join
Or This is a little easier to follow:
string = 'Hi now it's going to be done!'
string.reverse!
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
I'm making an auditor with ruby which started off fine this morning (using single word, user inputted content to omit) but now that I've tried to implement a wordlist, it puts the string to search through as many times as there are words in the wordlist, only censoring it once or twice. My code is as follows.
#by Nightc||ed, ©2015
puts "Enter string: "
text = gets.chomp
redact = File.read("wordlist.txt").split(" ")
words = text.split(" ")
redact.each do |beep|
words.each do |word|
if word != beep
print word + " "
else
print "[snip] "
end
end
end
sleep
I kind of understand why it doesn't work but I'm not sure how to fix it.
Any help would be appreciated.
There's an easier way than iterating through each array. The Array#include method can be easily used to see if the word is contained in your redacted list.
Here's some code that should behave how you wanted the original code to behave:
puts "Enter string: "
text = gets.chomp
redact = File.read("wordlist.txt").split(" ")
words = text.split(" ")
words.each do |word|
if redact.include? word
print "[snip] "
else
print word + " "
end
end
Scrubbing text gets very tricky. One thing you want to watch out for is word boundaries. Splitting on spaces will let a lot of beep words get through because of puctuation. Compare the first two results of the sample code below.
Next, assembling the split text back into its intended form with punction, spacing, etc., gets to be quite challenging. You may want to consider using regex for something presuambly as small as user comments. See the third result.
If you're doing this as a learning exercise, great, but if the application is sensitive where you're likely to take heat over failures to bleep words, you may want to look for an existing well-tested library.
#!/usr/bin/env ruby
# Bleeper
scifi_curses = ['friggin', 'gorram', 'fracking', 'dork']
text = "Why splitting spaces won't catch all the friggin bleeps ya gorram, fracking dork."
words = text.split(" ")
words.each do |this_word|
puts "bleep #{this_word}" if scifi_curses.include?(this_word)
end
puts
better_words = text.split(/\b/)
better_words.each do |this_word|
puts "bleep #{this_word}" if scifi_curses.include?(this_word)
end
puts
bleeped_text = text # keep copy of original if needed
scifi_curses.each do |this_curse|
bleeped_text.gsub!(this_curse, '[bleep]')
end
puts bleeped_text
You should get these results:
bleep friggin
bleep fracking
bleep friggin
bleep gorram
bleep fracking
bleep dork
Why splitting spaces won't catch all the [bleep] bleeps ya [bleep], [bleep] [bleep].
This code capitalizes the first letter of each word in a string.
Eg "this is a sentence" becomes "This Is A Sentence".
def capitalize_words(string)
words = string.split(" ")
idx = 0
while idx < words.length
word = words[idx]
word[0] = word[0].upcase
words[idx] = word #this line of code can be made redundant, but why?
idx += 1
end
return words.join(" ")
end
In the while statement, I don't understand why the third line is unnecessary. The second line sets the first letter of a word to capital:
word[0] = word[0].upcase
how does the while statement know to refer back to the previous line
word = words[idx]
to put the new capitalised-letter word back into the words array? I thought that when codes are executed, it always works in a forward fashion, please let me know if this understanding is incorrect.
It's because word variable holds reference for object - the same object that is in words array. So if you modify this object, the object in array is modified also, because it's the same.
BTW what you're trying to do here can be done much easier:
string.split(' ').map(&:capitalize).join(' ')
As Stefan suggested: Keep in mind that capitalize not only converts first character to uppercase, but also converts all remaining chars to lowercase. If this is not what you want, you can also do:
string.split(' ').map { |word| word.slice(0, 1).upcase + word.slice(1..-1) }
or use Stefan's solution with regexp:
string.gsub(/\b\w/) { |ch| ch.upcase }
Keep in mind that \b in regexp will 'split' your word not only by spaces, but by any word boudary.
If you are only using ruby then use answer as per #Marek's answer :
string.split(' ').map(&:capitalize).join(' ')
and If you are using Ruby with Rails then use this:
"this is a sentence".titleize