I am using Ruby to take in a string and reverse the order of the letters (so that the last letter becomes the first, etc.)
When I use the below code, I accidentally create a palindrome by taking half of the string and repeating it:
def reverse(word)
i = 0
new_word = word
while i < word.length
new_word [i] = word [word.length - i - 1]
i += 1
end
return new_word
end
puts reverse("cat") # => "tac"
puts reverse("programming") # => "gnimmargorp"
puts reverse("bootcamp") # => "pmactoob"
However, when I use the below code, I get it right:
def reverse(word)
i = 0
new_word = ""
while i < word.length
new_word [i] = word [word.length - i - 1]
i += 1
end
return new_word
end
puts reverse("cat") # => "tac"
puts reverse("programming") # => "gnimmargorp"
puts reverse("bootcamp") # => "pmactoob"
I just changed word to "" (line 4) and it works. Why?
My suspicion is that the string (word) itself is changing with each iteration, but it isn't supposed to do that is it?
Thank you all.
My suspicion is that the string (word) itself is changing with each iteration, but it isn't supposed to do that is it?
Your suspicion is spot-on.
new_word = word
This does not create a new string. It tells new_word to refer to the same list as word. Ruby is one of a handful of languages where strings are actually mutable objects. So when you modify new_word with []=, you're also modifying word. As you've already noticed, you can start with an empty string
new_word = ""
Alternatively, if you want to start with word and modify it (there are certainly some algorithms where doing so can be beneficial), we can use the #dup method, which performs a shallow copy of the data
new_word = word.dup
You can check whether two variables refer to the exact same object (as opposed to simply looking the same) using #equal?
puts(new_word.equal? word)
Related
I want to find if the ending of a string overlaps with the beginning of separate string. For example if I have these two strings:
string_1 = 'People say nothing is impossible, but I'
string_2 = 'but I do nothing every day.'
How do I find that the "but I" part at the end of string_1 is the same as the beginning of string_2?
I could write a method to loop over the two strings, but I'm hoping for an answer that has a Ruby string method that I missed or a Ruby idiom.
Set MARKER to some string that never appears in your string_1 and string_2. There are ways to do that dynamically, but I assume you can come up with some fixed such string in your case. I assume:
MARKER = "###"
to be safe for you case. Change it depending on your use case. Then,
string_1 = 'People say nothing is impossible, but I'
string_2 = 'but I do nothing every day.'
(string_1 + MARKER + string_2).match?(/(.+)#{MARKER}\1/) # => true
string_1 = 'People say nothing is impossible, but I'
string_2 = 'but you do nothing every day.'
(string_1 + MARKER + string_2).match?(/(.+)#{MARKER}\1/) # => false
You can use a simple loop and test at the end:
a=string_1.split(/\b/)
idx=0
while (idx<=a.length) do
break if string_2.start_with?(a[idx..-1].join)
idx+=1
end
p a[idx..-1].join if idx<a.length
Since this starts at 0, the longest sub string overlap is found.
You can use the same logic in a .detect block on the same array:
> a[(0..a.length).detect { |idx| string_2.start_with?(a[idx..-1].join) }..-1].join
=> "but I"
Or, as pointed out in comments, you can use the strings vs the array
string_1[(0..string_1.length).detect { |idx| string_2.start_with?(string_1[idx..-1]) }..-1]
Here's a solution that works by comparing the end of string_1 to the start of string_2—using the greatest common length as a starting point—with at least one matching character. It returns the index (from the end of string_1 or the beginning of string_2) if any matching character(s) are found, which can be used to extract the matching portion.
class String
def oindex(other)
[length, other.length].min.downto(1).detect do |i|
end_with?(other[0, i])
end
end
end
string_1 = 'People say nothing is impossible, but I'
string_2 = 'but I do nothing every day.'
if (idx = string_1.oindex(string_2))
puts "Last #{idx} characters match: #{string_1[-idx..-1]}"
end
Here's an alternative that finds all the indexes of the first character of the other string in the string, and uses those indexes as starting points to check for matches:
class String
def each_index(other)
return enum_for(__callee__, other) unless block_given?
i = -1
yield i while i = index(other, i.succ)
end
def oindex(other)
each_index(other.chr).detect do |i|
other.start_with?(self[i..-1]) and break length - i
end
end
end
This should be more efficient than checking every index, especially on longer strings with shorter matches, but I haven't benchmarked it.
Here are a couple of ways to do that. The first converts the two strings to arrays and then compares sequences from those arrays. The second operates on the two strings directly, comparing substrings.
#1 Convert strings to arrays and compare sequences from those arrays
Here's a simple alternative that requires the strings to be converted to arrays of words. It assumes all pairs of words are separated by one space.
def begins_with_ends?(end_str, begin_str)
end_arr = end_str.split
begin_arr = begin_str.split
!!begin_arr.each_index.find { |i| begin_arr[0,i+1] == end_arr[-1-i..-1] }
end
!!obj converts obj to false when it's "falsy" (nil or false) and to true when it's "truthy" (not "falsy"). For example, !!3 #=> true and !!nil #=> false.
end_str = 'People say nothing is impossible, but I when I'
begin_str = 'but I when I do nothing every day.'
begins_with_ends?(end_str, begin_str)
#=> true
Here the match is on the second word "I" in begin_str. Often, however, the last word of end_str only matches (at most) a single word in begin_str
#2 Compare substrings
I've implemented the following algorithm.
Set start_search to 0.
Attempt to match the last word of end_str (value of target) in begin_str, beginning at offset start_search. If no match is found return false; else let idx be the index of start_str where the last character of target appears.
Return true if the string comprised of the first idx characters of begin_str equals the string comprised by the last idx characters of end_str; else set start_search = idx + 2 and repeat step 2.
def begins_with_ends?(end_str, begin_str)
target = end_str[/[[:alnum:]]+\z/]
start_idx = 0
loop do
idx = begin_str.index(/\b#{target}\b/, start_idx)
return false if idx.nil?
idx += target.size
return true if end_str[-idx..-1] == begin_str[0, idx]
start_idx = idx + 2
end
end
begins_with_ends?(end_str, begin_str)
#=> true
This approach recognizes different numbers of spaces between the same two words in both strings (in which case there is no match).
Perhaps something like this would meet your needs?
string_1.split(' ') - string_2.split(' ')
=> ["People", "say", "is", "impossible,"]
Or this is more convoluted, but would give you the exact overlap:
string_2.
chars.
each_with_index.
map { |_, i| string_1.match(string_2[0..i]) }.
select { |s| s }.
max { |x| x.length }.
to_s
=> "but I"
My purpose is to accept a paragraph of text and find the specified phrase I want to REDACT, or replace.
I made a method that accepts an argument as a string of text. I break down that string into individual characters. Those characters are compared, and if they match, I replace those characters with *.
def search_redact(text)
str = ""
print "What is the word you would like to redact?"
redacted_name = gets.chomp
puts "Desired word to be REDACTED #{redacted_name}! "
#splits name to be redacted, and the text argument into char arrays
redact = redacted_name.split("")
words = text.split("")
#takes char arrays, two loops, compares each character, if they match it
#subs that character out for an asterisks
redact.each do |x|
if words.each do |y|
x == y
y.gsub!(x, '*') # sub redact char with astericks if matches words text
end # end loop for words y
end # end if statment
end # end loop for redact x
# this adds char array to a string so more readable
words.each do |z|
str += z
end
# prints it out so we can see, and returns it to method
print str
return str
end
# calling method with test case
search_redact("thisisapassword")
#current issues stands, needs to erase only if those STRING of characters are
# together and not just anywehre in the document
If I put in a phrase that shares characters with others parts of the text, for example, if I call:
search_redact("thisisapassword")
then it will replace that text too. When it accepts input from the user, I want to get rid of only the text password. But it then looks like this:
thi*i**********
Please help.
This is a classic windowing problem used to find a substring in a string. There are many ways to solve this, some that are much more efficient than others but I'm going to give you a simple one to look at that uses as much of your original code as possible:
def search_redact(text)
str = ""
print "What is the word you would like to redact?"
redacted_name = gets.chomp
puts "Desired word to be REDACTED #{redacted_name}! "
redacted_name = "password"
#splits name to be redacted, and the text argument into char arrays
redact = redacted_name.split("")
words = text.split("")
words.each.with_index do |letter, i|
# use windowing to look for exact matches
if words[i..redact.length + i] == redact
words[i..redact.length + i].each.with_index do |_, j|
# change the letter to an astrisk
words[i + j] = "*"
end
end
end
words.join
end
# calling method with test case
search_redact("thisisapassword")
The idea here is we're taking advantage of array == which allows us to say ["a", "b", "c"] == ["a", "b", "c"]. So now we just walk the input and ask does this sub array equal this other sub array. If they do match, we know we need to change the value so we loop through each element and replace it with a *.
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
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
I tried to write a function which will be able to randomly change letters in word except first and last one.
def fun(string)
z=0
s=string.size
tab=string
a=(1...s-1).to_a.sample s-1
for i in 1...(s-1)
puts tab[i].replace(string[a[z]])
z=z+1
end
puts tab
end
fun("sample")
My output is:
p
l
a
m
sample
Anybody know how to make it my tab be correct?
it seems to change in for block, because in output was 'plamp' so it's random as I wanted but if I want to print the whole word (splampe) it doesn't working. :(
What about:
def fun(string)
first, *middle, last = string.chars
[first, middle.shuffle, last].join
end
fun("sample") #=> "smalpe"
s = 'sample'
[s[0], s[1..-2].chars.shuffle, s[-1]].join
# => "slpmae"
Here is my solution:
def fun(string)
first = string[0]
last = string[-1]
middle = string[1..-2]
puts "#{first}#{middle.split('').shuffle.join}#{last}"
end
fun('sample')
there are some problems with your function. First, when you say tab=string, tab is now a reference to string, so, when you change characters on tab you change the string characters too. I think that for clarity is better to keep the index of sample (1....n)to reference the position in the original array.
I suggest the usage of tab as a new array.
def fun(string)
if string.length <= 2
return
z=1
s=string.size
tab = []
tab[0] = string[0]
a=(1...s-1).to_a.sample(s-1)
(1...s-1).to_a.each do |i|
tab[z] = string[a[i - 1]]
z=z+1
end
tab.push string[string.size-1]
tab.join('')
end
fun("sample")
=> "spalme"
Another way, using String#gsub with a block:
def inner_jumble(str)
str.sub(/(?<=\w)\w{2,}(?=\w)/) { |s| s.chars.shuffle.join }
end
inner_jumble("pneumonoultramicroscopicsilicovolcanoconiosis") # *
#=> "poovcanaiimsllinoonroinuicclprsciscuoooomtces"
inner_jumble("what ho, fellow coders?")
#=> "waht ho, folelw coedrs?"
(?<=\w) is a ("zero-width") positive look-behind that requires the match to immediately follow a word character.
(?=\w) is a ("zero-width") positive look-ahead that requires the match to be followed immediately by a word character.
You could use \w\w+ in place of \w{2,} for matching two or more consecutive word characters.
If you only want it to apply to individual words, you can use gsub or sub.
*A lung disease caused by inhaling very fine ash and sand dust, supposedly the longest word in some English dictionaries.