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.
Related
I've been working on a coding challenge to return the uncommon characters between two separate strings. However I haven't had much luck in figuring it out. I've tried a number of methods that I have found when googling, none have quite worked. I did however come across a hash mapping method but the examples only included C++, Python, C — seeing as Ruby is my first programming language it's been difficult trying to translate such a complex challenge without making mistakes.
I'm not in any rush to figure this coding challenge out and would rather appreciate getting some feedback on what everyone else thinks may be worth me reading into in order to approach this question successfully.
Here is my code currently, please don't think much of it, I know it's far off from what it should be:
# Find concatenated string
# with uncommon characters of given strings
def solve(a,b)
res = "" # result
map = {}
# store all characters of b in map
[a,b].each.map{| character, element | for characters(1..26) << element[+1]
}
# Find characters of a that are not
# present in b and append to result
# Find characters of b that are not
# present in a
return res
end
Here is an example test case of what I'm trying to do, click here for link to Kata challenge:
solve("xyab","xzca") = "ybzc"
--The first string has 'yb' which is not in the second string.
--The second string has 'zc' which is not in the first string.
I'd treat strings as arrays of characters. You can easily find differences between arrays:
def solve(a, b)
first_array = a.chars
second_array = b.chars
((first_array - second_array) + (second_array - first_array)).uniq.join
end
Another approach is to use Set:
require 'set'
def solve(a, b)
(b.chars.to_set ^ a.chars.to_set).to_a.join
end
UPD uniq was added to the first approach as #3limin4t0r suggested.
Similar to the answer of Yakov instead of adding the two differences together you could also subtract the intersection from the union between the two. (See symmetric difference)
def solve(a, b)
a = a.chars
b = b.chars
((a | b) - (a & b)).join
end
Both the union method | and intersection method & remove duplicates so there is no need to call uniq before joining.
Here is a way that does not convert the strings to arrays of characters.
def non_common_chars(str1, str2)
g = str1.each_char.with_object({}) { |c,h| h[c] = 1 }
str2.each_char.with_object(g) do |c,h|
if h.key?(c)
h.delete(c) if h[c] == 1
else
h[c] = 2
end
end.keys.join
end
non_common_chars("abc-defaa", "abcm.nopp")
#=> "-defm.nop"
If desired, the hash g could be factored out.
The steps are as follows.
str1 = "abc-defaa"
str2 = "abcm.nopp"
g = str1.each_char.with_object({}) { |c,h| h[c] = 1 }
#=> {"a"=>1, "b"=>1, "c"=>1, "-"=>1, "d"=>1, "e"=>1, "f"=>1}
The keys of g comprise the unique characters in str1
e0 = str2.each_char
#=> #<Enumerator: "abcm.nopp":each_char>
e1 = e0.with_object(g)
#=> #<Enumerator: #<Enumerator: "abcm.nopp":each_char>:with_object(
# {"a"=>1, "b"=>1, "c"=>1, "-"=>1, "d"=>1, "e"=>1, "f"=>1})>
h = e1.each do |c,h|
if h.key?(c)
h.delete(c) if h[c] == 1
else
h[c] = 2
end
end
#=> {"-"=>1, "d"=>1, "e"=>1, "f"=>1, "m"=>2, "."=>2, "n"=>2,
# "o"=>2, "p"=>2}
The keys of h are now the unique characters in str1 that are not in str2 and unique characters in str2 that are not in str1. Just two more steps:
a = h.keys
#=> ["-", "d", "e", "f", "m", ".", "n", "o", "p"]
a.join
#=>"-defm.nop"
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
I have this exercise:
Write a Title class which is initialized with a string.
It has one method -- fix -- which should return a title-cased version of the string:
Title.new("a title of a book").fix =
A Title of a Book
You'll need to use conditional logic - if and else statements - to make this work.
Make sure you read the test specification carefully so you understand the conditional logic to be implemented.
Some methods you'll want to use:
String#downcase
String#capitalize
Array#include?
Also, here is the Rspec, I should have included that:
describe "Title" do
describe "fix" do
it "capitalizes the first letter of each word" do
expect( Title.new("the great gatsby").fix ).to eq("The Great Gatsby")
end
it "works for words with mixed cases" do
expect( Title.new("liTTle reD Riding hOOD").fix ).to eq("Little Red Riding Hood")
end
it "downcases articles" do
expect( Title.new("The lord of the rings").fix ).to eq("The Lord of the Rings")
expect( Title.new("The sword And The stone").fix ).to eq("The Sword and the Stone")
expect( Title.new("the portrait of a lady").fix ).to eq("The Portrait of a Lady")
end
it "works for strings with all uppercase characters" do
expect( Title.new("THE SWORD AND THE STONE").fix ).to eq("The Sword and the Stone")
end
end
end
Thank you #simone, I incorporated your suggestions:
class Title
attr_accessor :string
def initialize(string)
#string = string
end
IGNORE = %w(the of a and)
def fix
s = string.split(' ')
s.map do |word|
words = word.downcase
if IGNORE.include?(word)
words
else
words.capitalize
end
end
s.join(' ')
end
end
Although I'm still running into errors when running the code:
expected: "The Great Gatsby"
got: "the great gatsby"
(compared using ==)
exercise_spec.rb:6:in `block (3 levels) in <top (required)>'
From my beginner's perspective, I cannot see what I'm doing wrong?
Final edit: I just wanted to say thanks for all the effort every one put in in assisting me earlier. I'll show the final working code I was able to produce:
class Title
attr_accessor :string
def initialize(string)
#string = string
end
def fix
word_list = %w{a of and the}
a = string.downcase.split(' ')
b = []
a.each_with_index do |word, index|
if index == 0 || !word_list.include?(word)
b << word.capitalize
else
b << word
end
end
b.join(' ')
end
end
Here's a possible solution.
class Title
attr_accessor :string
IGNORES = %w( the of a and )
def initialize(string)
#string = string
end
def fix
tokens = string.split(' ')
tokens.map do |token|
token = token.downcase
if IGNORES.include?(token)
token
else
token.capitalize
end
end.join(" ")
end
end
Title.new("a title of a book").fix
Your starting point was good. Here's a few improvements:
The comparison is always lower-case. This will simplify the if-condition
The list of ignored items is into an array. This will simplify the if-condition because you don't need an if for each ignored string (they could be hundreds)
I use a map to replace the tokens. It's a common Ruby pattern to use blocks with enumerations to loop over items
There are two ways you can approach this problem:
break the string into words, possibly modify each word and join the words back together; or
use a regular expression.
I will say something about the latter, but I believe your exercise concerns the former--which is the approach you've taken--so I will concentrate on that.
Split string into words
You use String#split(' ') to split the string into words:
str = "a title of a\t book"
a = str.split(' ')
#=> ["a", "title", "of", "a", "book"]
That's fine, even when there's extra whitespace, but one normally writes that:
str.split
#=> ["a", "title", "of", "a", "book"]
Both ways are the same as
str.split(/\s+/)
#=> ["a", "title", "of", "a", "book"]
Notice that I've used the variable a to signify that an array is return. Some may feel that is not sufficiently descriptive, but I believe it's better than s, which is a little confusing. :-)
Create enumerators
Next you send the method Enumerable#each_with_index to create an enumerator:
enum0 = a.each_with_index
# => #<Enumerator: ["a", "title", "of", "a", "book"]:each_with_index>
To see the contents of the enumerator, convert enum0 to an array:
enum0.to_a
#=> [["a", 0], ["title", 1], ["of", 2], ["a", 3], ["book", 4]]
You've used each_with_index because the first word--the one with index 0-- is to be treated differently than the others. That's fine.
So far, so good, but at this point you need to use Enumerable#map to convert each element of enum0 to an appropriate value. For example, the first value, ["a", 0] is to be converted to "A", the next is to be converted to "Title" and the third to "of".
Therefore, you need to send the method Enumerable#map to enum0:
enum1 = enum.map
#=> #<Enumerator: #<Enumerator: ["a", "title", "of", "a",
"book"]:each_with_index>:map>
enum1.to_a
#=> [["a", 0], ["title", 1], ["of", 2], ["a", 3], ["book", 4]]
As you see, this creates a new enumerator, which could think of as a "compound" enumerator.
The elements of enum1 will be passed into the block by Array#each.
Invoke the enumerator and join
You want to a capitalize the first word and all other words other than those that begin with an article. We therefore must define some articles:
articles = %w{a of it} # and more
#=> ["a", "of", "it"]
b = enum1.each do |w,i|
case i
when 0 then w.capitalize
else articles.include?(w) ? w.downcase : w.capitalize
end
end
#=> ["A", "Title", "of", "a", "Book"]
and lastly we join the array with one space between each word:
b.join(' ')
=> "A Title of a Book"
Review details of calculation
Let's go back to the calculation of b. The first element of enum1 is passed into the block and assigned to the block variables:
w, i = ["a", 0] #=> ["a", 0]
w #=> "a"
i #=> 0
so we execute:
case 0
when 0 then "a".capitalize
else articles.include?("a") ? "a".downcase : "a".capitalize
end
which returns "a".capitalize => "A". Similarly, when the next element of enum1 is passed to the block:
w, i = ["title", 1] #=> ["title", 1]
w #=> "title"
i #=> 1
case 1
when 0 then "title".capitalize
else articles.include?("title") ? "title".downcase : "title".capitalize
end
which returns "Title" since articles.include?("title") => false. Next:
w, i = ["of", 2] #=> ["of", 2]
w #=> "of"
i #=> 2
case 2
when 0 then "of".capitalize
else articles.include?("of") ? "of".downcase : "of".capitalize
end
which returns "of" since articles.include?("of") => true.
Chaining operations
Putting this together, we have:
str.split.each_with_index.map do |w,i|
case i
when 0 then w.capitalize
else articles.include?(w) ? w.downcase : w.capitalize
end
end
#=> ["A", "Title", "of", "a", "Book"]
Alternative calculation
Another way to do this, without using each_with_index, is like this:
first_word, *remaining_words = str.split
first_word
#=> "a"
remaining_words
#=> ["title", "of", "a", "book"]
"#{first_word.capitalize} #{ remaining_words.map { |w|
articles.include?(w) ? w.downcase : w.capitalize }.join(' ') }"
#=> "A Title of a Book"
Using a regular expression
str = "a title of a book"
str.gsub(/(^\w+)|(\w+)/) do
$1 ? $1.capitalize :
articles.include?($2) ? $2 : $2.capitalize
end
#=> "A Title of a Book"
The regular expression "captures" [(...)] a word at the beginning of the string [(^\w+)] or [|] a word that is not necessarily at the beginning of string [(\w+)]. The contents of the two capture groups are assigned to the global variables $1 and $2, respectively.
Therefore, stepping through the words of the string, the first word, "a", is captured by capture group #1, so (\w+) is not evaluated. Each subsequent word is not captured by capture group #1 (so $1 => nil), but is captured by capture group #2. Hence, if $1 is not nil, we capitalize the (first) word (of the sentence); else we capitalize $2 if the word is not an article and leave it unchanged if it is an article.
def fix
string.downcase.split(/(\s)/).map.with_index{ |x,i|
( i==0 || x.match(/^(?:a|is|of|the|and)$/).nil? ) ? x.capitalize : x
}.join
end
Meets all conditions:
a, is, of, the, and all lowercase
capitalizes all other words
all first words are capitalized
Explanation
string.downcase calls one operation to make the string you're working with all lower case
.split(/(\s)/) takes the lower case string and splits it on white-space (space, tab, newline, etc) into an array, making each word an element of the array; surrounding the \s (the delimiter) in the parentheses also retains it in the array that's returned, so we don't lose that white-space character when rejoining
.map.with_index{ |x,i| iterates over that returned array, where x is the value and i is the index number; each iteration returns an element of a new array; when the loop is complete you will have a new array
( i==0 || x.match(/^(?:a|is|of|the|and)$/).nil? ) if it's the first element in the array (index of 0), or the word matches a,is,of,the, or and -- that is, the match is not nil -- then x.capitalize (capitalize the word), otherwise (it did match the ignore words) so just return the word/value, x
.join take our new array and combine all the words into one string again
Additional
Ordinarily, what is inside parentheses in regex is considered a capture group, meaning that if the pattern inside is matched, a special variable will retain the value after the regex operations have finished. In some cases, such as the \s we wanted to capture that value, because we reuse it, in other cases like our ignore words, we need to match, but do not need to capture them. To avoid capturing a match you can pace ?: at the beginning of the capture group to tell the regex engine not to retain the value. There are many benefits of this that fall outside the scope of this answer.
Here is another possible solution to the problem
class Title
attr_accessor :str
def initialize(str)
#str = str
end
def fix
s = str.downcase.split(" ") #convert all the strings to downcase and it will be stored in an array
words_cap = []
ignore = %w( of a and the ) # List of words to be ignored
s.each do |item|
if ignore.include?(item) # check whether word in an array is one of the words in ignore list.If it is yes, don't capitalize.
words_cap << item
else
words_cap << item.capitalize
end
end
sentence = words_cap.join(" ") # convert an array of strings to sentence
new_sentence =sentence.slice(0,1).capitalize + sentence.slice(1..-1) #Capitalize first word of the sentence. Incase it is not capitalized while checking the ignore list.
end
end
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