I'm working on beginner Ruby tutorials. I'm trying to write a method that will advance vowels to the next vowel. 'a' will become 'e', 'e' will become 'i', 'u' will become 'a', etc etc. I've been trying various ideas for quite a while, to no avail.
I think I'm on the right track in that I need to create an array of the vowels, and then use an index to advance them to the next array in the vowel. I just can't seem to create the right method to do so.
I know this isn't workable code, but my outline is along these lines. Where I run into issues is getting my code to recognize each vowel, and advance it to the next vowel:
def vowel_adv(str)
vowels = ["a", "e", "i", "o", "u"]
str = str.split('')
**str_new = str.map do |letter|
if str_new.include?(letter)
str_new = str_new[+1]
end**
# The ** section is what I know I need to find working code with, but keep hitting a wall.
str_new.join
end
Any help would be greatly appreciated.
Because we have only a few vowels, I would first define a hash containing vowel keys and vowel values:
vowels_hash = {
'a' => 'e',
'A' => 'E',
'e' => 'i',
'E' => 'I',
'i' => 'o',
'I' => 'O',
'o' => 'u',
'O' => 'U',
'u' => 'a',
'U' => 'A'
}
Then I would iterate over the alphabets present in each word / sentence like so:
word.split(//).map do |character|
vowels_hash[character] || character
end.join
Update:
BTW instead of splitting the word, you could also use gsub with regex + hash arguments like so:
word.gsub(/[aeiouAEIOU]/, vowels_hash)
Or like so if you want to be Mr. / Ms. Fancy Pants:
regex = /[#{vowels_hash.keys.join}]/
word.gsub(regex, vowels_hash)
Here's your code with the fewest corrections necessary to make it work:
def vowel_adv(str)
vowels = ["a", "e", "i", "o", "u"]
str = str.split('')
str_new = str.map do |char|
if vowels.include?(char)
vowels.rotate(1)[vowels.index(char)]
else
char
end
end
str_new.join
end
vowel_adv "aeiou"
=> "eioua"
Things that I changed include
addition of a block variable to the map block
returning the thing you're mapping to from the map block
include? is called on the Array, not on the possible element
finding the next vowel by looking in the array of vowels, not by incrementing the character, which is what I think you were trying to do.
Here's an improved version:
VOWELS = %w(a e i o u)
ROTATED_VOWELS = VOWELS.rotate 1
def vowel_adv(str)
str.
chars.
map do |char|
index = VOWELS.index char
if index
ROTATED_VOWELS[index]
else
char
end
end.
join
end
static Arrays in constants
nicer array-of-string syntax
String#chars instead of split
use the index to test for inclusion instead of include?
no assignment to parameters, which is a little confusing
no temporary variables, which some people like and some people don't but I've done here to show that it's possible
And, just because Ruby is fun, here's a different version which copies the string and modifies the copy:
VOWELS = %w(a e i o u)
ROTATED_VOWELS = VOWELS.rotate 1
def vowel_adv(str)
new_str = str.dup
new_str.each_char.with_index do |char, i|
index = VOWELS.index char
if index
new_str[i] = ROTATED_VOWELS[index]
end
end
new_str
end
The string class has a nice method for this. Demo:
p "Oh, just a test".tr("aeiouAEIOU", "uaeioUAEIO") # => "Ih, jost u tast"
To riff of of steenslag's answer a little.
VOWELS = %w{a e i o u A E I O U}
SHIFTED_VOWELS = VOWELS.rotate 1
def vowel_shifter input_string
input_string.tr!(VOWELS.join, SHIFTED_VOWELS.join)
end
and just for fun, consonants:
CONSONANTS = ('a'..'z').to_a + ('A'..'Z').to_a - VOWELS
SHIFTED_CONSONANTS = CONSONANTS.rotate 1
def consonant_shifter input_string
input_string.tr!(CONSONANTS.join, SHIFTED_CONSONANTS.join)
end
Related
I would like to extract all consonants in a string before the first vowel, ideally without using regex. If I have the word truck for example I would like to extract the "tr", for "street" I would like to extract "str".
I tried the following but received the error wrong number of arguments (given 0, expected 1) for the execution of the block in vowels.
Can someone explain where the error is or suggest an easier way to do this?
vowels = ["a", "e", "i", "o", "u"]
def vowels(words)
letters = words.split("")
consonants = []
letters.each do |letter|
consonants << letter until vowels.include?(letter)
end
consonants
end
Notice your function and array have the same name, vowels. That's causing confusion--you may think you're calling include? on the global vowels array, but actually, your code attempts to call your identically-named function recursively without parameters, hence the error. Since Ruby permits function calls without parentheses, Ruby treats this line:
vowels.include?(letter)
as
vowels().include?(letter) # <- wrong number of arguments!
Changing your function name to something other than "vowels" causes this error, which makes more sense:
undefined local variable or method `vowels' for main:Object
(repl):7:in `block in function_formerly_known_as_vowels'
(repl):6:in `each'
(repl):6:in `function_formerly_known_as_vowels'
(repl):12:in `<main>'
This leads to the underlying cause: breaking encapsulation. The vowels function attempts to access state in the global scope. This is poor practice (and won't work in this code since the vowels array isn't actually global--it'd need a $ prefix on the variable name or another way of making it visible inside the function scope--see this answer for details). Instead, pass the array of vowels into the function as a parameter (and probably generalize the function in the process), or hardcode it inside the function itself since we can assume vowels won't change.
Having resolved the scope issue, the next problem is that until is a loop. As soon as letter is a consonant, it'll be pushed repeatedly onto the consonants array until the program runs out of memory. You probably meant unless. Even here, you'll need to break or return to exit the loop upon finding a vowel.
Lastly, a couple semantic suggestions: vowels is not a very accurate function name. It returns consonants up to the first vowel, so title it as such! The parameter "words" is potentially misleading because it suggests an array or a series of words, when your function appears to operate on just one word (or string, generically).
Here's a re-write:
def front_consonants(word)
vowels = "aeiou"
consonants = []
word.each_char do |letter|
break if vowels.include? letter
consonants << letter
end
consonants.join
end
p front_consonants "stack" # => "st"
Consider using Enumerable#chunk:
VOWELS = ["a", "e", "i", "o", "u"]
word = "truck"
word.chars.chunk { |e| VOWELS.include? e }.first.last.join
#=> "tr"
The first part returns
word.chars.chunk { |e| VOWELS.include? e }.to_a #=> [[false, ["t", "r"]], [true, ["u"]], [false, ["c", "k"]]]
Using take_while is semantic.
word.chars.take_while { |c| vowels.none? c }.join
consonants << letter until vowels.include?(letter)
will just end up pushing the same consonant over and over again (an infinite loop).
How I would do this, is reduce using `break.
I recommend reading up on these if you're unfamiliar
https://apidock.com/ruby/Enumerable/reduce
How to break out from a ruby block?
# better to make this a constant
Vowels = ["a", "e", "i", "o", "u"]
def letters_before_first_vowel(string)
string.chars.reduce([]) do |result, char|
break result if Vowels.include?(char)
result + [char]
end.join
end
puts letters_before_first_vowel("truck")
# => "tr"
You said "ideally without using regex". Sorry, but I think most Rubyists would agree that a regex is the tool of choice here:
"whatchamacallit"[/\A[^aeiou]*/] #=> "wh"
"apple"[/\A[^aeiou]*/] #=> ""
The regex reads, "match the beginning of the string (\A) followed by zero or more (*) characters that are not (^) vowels", [^aeiou] being a character class.
I'm working on this function:
It's supposed to take in an array and match it with a given word to see if that word can be formed with the given array of strings.
I added the two commented lines because I wanted to see how the for-loop works.
def canformword(arr,word)
arrword = word.chars
arrleft = arr
flag = true
for i in 0...arrword.size
ch = arrword[i]
# puts arrword[i]
if !arrleft.include?(ch)
flag = false
break
else
ind = arrleft.index(ch)
# puts ind
arrleft.delete_at(ind)
end
end
if flag
puts 'can form word'
else
puts 'can not form word'
end
end
canformword(['y','b','z','e','a','u','t'], 'beauty')
canformword(['r','o','u','g','h'], 'tough')
When I uncomment those two lines, the following is the output:
Why does the output print out the index 2 repeatedly? I would think that it would print out the index of each letter in my arrleft array rather than repeatedly spitting out 2!
I understand the 1 it prints out, because that's the index of b, but the rest is weird to me.
b
1
e
2
a
2
u
2
t
2
y
0
can form word
t
can not form word
hear a better implementation that
def can_form_word?(chars_array, word)
(word.chars - chars_array).empty?
end
that's all.
here another implementation the Ruby way. Because your code is like C. I've been writing Ruby code for more than three years now, and I never used for loops.
def canformword(chars,word)
word.each_char do |char|
puts char
if !chars.include?(char)
return false # or puts "Can't form word"
end
end
true # or puts "Can form word"
end
this is because you are deleting the character at position ind(arrleft.delete_at(ind)); so each time array characters are shifting one cell left.
Now as all your letters 'e','a','u','t','y' are placed ordered way so it is showing 2,2,2,2 continuously.
Now look at 'y'; it is at position 0 ; so 0 is printed at end.
So the issue is because you are deleting the characters at position 'ind'.
So, to achieve this you can just do one thing ; do not delete the characters when found rather replace it by some numeric value like '0'.
You obtain 2 several times because you are deleting elements from the array. In that case you delete the second element every time so the next character, in the next iteration, take the index 2 again.
Problem
If you want do delete index 2 and 3 from an array, you need to delete them in decreasing order, becausing deleting index 2 would modify index of 3:
array = %w(a b c d e)
array.delete_at(3)
array.delete_at(2)
p array
#=> ["a", "b", "e"]
or
array = %w(a b c d e)
array.delete_at(2)
array.delete_at(2)
p array
#=> ["a", "b", "e"]
Solution
For your code, you just need to replace
arrleft.delete_at(ind)
with
arrleft[ind] = nil
Alternative
Since you take the numbers of characters into account, here's a modified version of a previous answer :
class Array
def count_by
each_with_object(Hash.new(0)) { |e, h| h[e] += 1 }
end
def subset_of?(superset)
superset_counts = superset.count_by
count_by.all? { |k, count| superset_counts[k] >= count }
end
end
def can_form_word?(chars, word)
word.chars.subset_of?(chars)
end
p can_form_word?(['y','b','z','e','a','u','t'], 'beauty')
#=> true
p can_form_word?(['y','b','z','e','u','t'], 'beauty')
#=> false
p can_form_word?(['a', 'c', 'e', 'p', 't', 'b', 'l'], 'acceptable')
#=> false
p ('acceptable'.chars - ['a', 'c', 'e', 'p', 't', 'b', 'l']).empty?
#=> true
def count_vowels(string)
vowels = ["a", "e", "i", "o", "u"]
i = 0
j = 0
count = 0
while i < string.length do
while j < vowels.length do
if string[i] == vowels[j]
count += 1
break
end
j += 1
end
i += 1
end
puts count
end
I'm having trouble spotting where this goes wrong. If this program encounters a consonant, it stops. Also, how would the same problem be solved using the ".each" method?
The problem is that you never reset j to zero.
The first time your outer while loop runs, which is to compare the first character of string to each vowel, j is incremented from 0 (for "a") to 4 (for "u"). The second time the outer loop runs, however, j is already 4, which means it then gets incremented to 5, 6, 7 and on and on. vowels[5], vowels[6], etc. all evaluate to nil, so characters after the first are never counted as vowels.
If you move the j = 0 line inside the outer while loop, your method works correctly.
Your second question, about .each, shows that you're already thinking along the right lines. while is rarely seen in Ruby and .each would definitely be an improvement. As it turns out, you can't call .each on a String (because the String class doesn't include Enumerable), so you have to turn it into an Array of characters first with the String#chars method. With that, your code would look like this:
def count_vowels(string)
chars = string.chars
vowels = ["a", "e", "i", "o", "u"]
count = 0
chars.each do |char|
vowels.each do |vowel|
if char == vowel
count += 1
break
end
end
end
puts count
end
In Ruby, though, we have much better ways to do this sort of thing. One that fits particularly well here is Array#count. It takes a block and evaluates it for each item in the array, then returns the number of items for which the block returned true. Using it we could write a method like this:
def count_vowels(string)
chars = string.chars
vowels = ["a", "e", "i", "o", "u"]
count = chars.count do |char|
is_vowel = false
vowels.each do |vowel|
if char == vowel
is_vowel = true
break
end
end
is_vowel
end
puts count
end
That's not much shorter, though. Another great method we can use is Enumerable#any?. It evaluates the given block for each item in the array and returns true upon finding any item for which the block returns true. Using it makes our code super short, but still readable:
def count_vowels(string)
chars = string.chars
vowels = %w[ a e i o u ]
count = chars.count do |char|
vowels.any? {|vowel| char == vowel }
end
puts count
end
(Here you'll see I threw in another common Ruby idiom, the "percent literal" notation for creating an array: %w[ a e i o u ]. It's a common way to create an array of strings without all of those quotation marks and commas. You can read more about it here.)
Another way to do the same thing would be to use Enumerable#include?, which returns true if the array contains the given item:
def count_vowels(string)
vowels = %w[ a e i o u ]
puts string.chars.count {|char| vowels.include?(char) }
end
...but as it turns out, String has an include? method, too, so we can do this instead:
def count_vowels(string)
puts string.chars.count {|char| "aeiou".include?(char) }
end
Not bad! But I've saved the best for last. Ruby has a great method called String#count:
def count_vowels(string)
puts string.count("aeiou")
end
The ordered_vowel_words method and ordered_vowel_word? helper method accept a word and return the word back if the vowels of the word are in the order of (a,e,i,o,u).
I'm having trouble understanding the logic. Particularly how the last block (0...(vowels_arr.length - 1)).all? do... in the helper method works.
Can someone please explain how this works? I don't understand how all? is being called on a range.
def ordered_vowel_words(str)
words = str.split(" ")
ordered_vowel_words = words.select do |word|
ordered_vowel_word?(word)
end
ordered_vowel_words.join(" ")
end
def ordered_vowel_word?(word)
vowels = ["a", "e", "i", "o", "u"]
letters_arr = word.split("")
vowels_arr = letters_arr.select { |l| vowels.include?(l) }
(0...(vowels_arr.length - 1)).all? do |i|
vowels_arr[i] <= vowels_arr[i + 1]
end
end
I've added some comments :)
def ordered_vowel_words(str)
# words is a string with words separated by a whitespace.
# split generates an array of words from a string
words = str.split(" ")
# select only the ordered vowel words from the previous array
ordered_vowel_words = words.select do |word|
ordered_vowel_word?(word)
end
# join the ordered vowel words in a single string
ordered_vowel_words.join(" ")
end
def ordered_vowel_word?(word)
# THESE ARE THE VOWELS YOU FOOL
vowels = ["a", "e", "i", "o", "u"]
# transform the word in an array of characters
letters_arr = word.split("")
# select only the vowels in this array
vowels_arr = letters_arr.select { |l| vowels.include?(l) }
# generate a range from 0 to the length of the vowels array minus 2:
# there is this weird range because we want to iterate from the first vowel
# to the second to last; all? when called on a range returns true if...
(0...(vowels_arr.length - 1)).all? do |i|
# for each number in the range, the current vowel is smaller that the next vowel
vowels_arr[i] <= vowels_arr[i + 1]
end
end
Hope this helped!
EDIT I might add that the last block doesn't feel very Ruby-ish. I may suggest this alternative implementation:
def ordered_vowel_word?(word)
vowels = ["a", "e", "i", "o", "u"]
# transform the word in an array of characters
letters_arr = word.split("")
# select only the vowels in this array
vowels_arr = letters_arr.select { |l| vowels.include?(l) }
# from this array generate each possible consecutive couple of characters
vowels_arr.each_cons(2).all? do |first, second|
first <= second
end
end
require 'rspec/autorun'
describe "#ordered_vowel_word?" do
it "tells if word is ordered" do
expect(ordered_vowel_word?("aero")).to be_true
end
it "or not" do
expect(ordered_vowel_word?("rolling")).to be_false
end
end
The all? block is essentially iterating over the vowels_arr array, comparing each value with it's next one. If all the comparisons return true then all? will also return true, which means the array is ordered. If one of the iterations returned false, the return value of all? would also be false, which would mean that the collection is unordered.
You can call all? on a Rangehttp://www.ruby-doc.org/core-1.9.3/Range.html object because Range mixes in the Enumerablehttp://www.ruby-doc.org/core-1.9.3/Enumerable.html module, which is the one that defines all?.
You can verify this by trying the following in irb:
Range.included_modules # => => [Enumerable, Kernel]
The first part (0...(vowels_arr.length - 1)) creates a range from
0 to how many vowels are in the word.
all? iterates over that range and returns true if all for all
elements of the range some condition is true false otherwise.
do |i| introduces a block with i as the variable representing
each element of the range created in step 1.
Finally, the condition is for each index in the range, now represented by i, it checks if vowels_arr[i] <= vowels_arr[i+1] is true.
This is my solution to this problem:
def ordered_vowel_words(str)
vowels_s = str.scan(/[aeiou]/)
vowels_sort = str.scan(/[aeiou]/).sort
vowels_s === vowels_sort ? str : ""
end
I'm working on the following exercise below:
Write a method, ovd(str) that takes a string of lowercase words and returns a string with just the words containing all their vowels (excluding "y") in alphabetical order. Vowels may be repeated ("afoot" is an ordered vowel word). The method does not return the word if it is not in alphabetical order.
Example output is:
ovd("this is a test of the vowel ordering system") #output=> "this is a test of the system"
ovd("complicated") #output=> ""
Below is code I wrote that will do the job but I am looking to see if there is a shorter more clever way to do this. My solution seems too lengthy.Thanks in advance for helping.
def ovd?(str)
u=[]
k=str.split("")
v=["a","e","i","o","u"]
w=k.each_index.select{|i| v.include? k[i]}
r={}
for i in 0..v.length-1
r[v[i]]=i+1
end
w.each do |s|
u<<r[k[s]]
end
if u.sort==u
true
else
false
end
end
def ovd(phrase)
l=[]
b=phrase.split(" ")
b.each do |d|
if ovd?(d)==true
l<<d
end
end
p l.join(" ")
end
def ovd(str)
str.split.select { |word| "aeiou".match(/#{word.gsub(/[^aeiou]/, "").chars.uniq.join(".*")}/) }.join(" ")
end
ovd("this is a test of the vowel ordering system") # => "this is a test of the system"
ovd("complicated") # => ""
ovd("alooft") # => "alooft"
ovd("this is testing words having something in them") # => "this is testing words having in them"
EDIT
As requested by the OP, explanation
String#gsub word.gsub(/[^aeiou]/, "") removes the non-vowel characters e.g
"afloot".gsub(/[^aeiou]/, "") # => "aoo"
String#chars converts the new word to an array of characters
"afloot".gsub(/[^aeiou]/, "").chars # => ["a", "o", "o"]
Array#uniq converts returns only unique elements from the array e.g
"afloot".gsub(/[^aeiou]/, "").chars.uniq # => ["a", "o"]
Array#join converts an array to a string merging it with the supplied parameter e.g
"afloot".gsub(/[^aeiou]/, "").chars.uniq.join(".*") # => "a.*o"
#{} is simply String interpolation and // converts the interpolated string into a Regular Expression
/#{"afloot".gsub(/[^aeiou]/, "").chars.uniq.join(".*")}/ # => /a.*o/
A non-regex solution:
V = %w[a e i o u] # => ["a", "e", "i", "o", "u"]
def ovd(str)
str.split.select{|w| (x = w.downcase.chars.select \
{|c| V.include?(c)}) == x.sort}.join(' ')
end
ovd("this is a test of the vowel ordering system")
# => "this is a test of the system"
ovd("") # => ""
ovd("camper") # => "camper"
ovd("Try singleton") # => "Try"
ovd("I like leisure time") # => "I"
ovd("The one and only answer is '42'") # => "The and only answer is '42'"
ovd("Oil and water don't mix") # => "and water don't mix"
Edit to add an alternative:
NV = (0..127).map(&:chr) - %w(a e i o u) # All ASCII chars except lc vowels
def ovd(str)
str.split.select{|w| (x = w.downcase.chars - NV) == x.sort}.join(' ')
end
Note x = w.downcase.chars & V does not work. While it spears out the vowels from w, and preserves their order, it removes duplicates.