# Reverse the words, move the vowels and downcase everything

I'm brand new to Ruby.

The function takes in a string of any number of words, and reverses the order of the words. Also, for each word, it takes the vowels and moves it to the end of the word. It also downcases everything. Thus, Hello World! would become wrld!o hlleo.

I am trying to use some Ruby features, hence why it is a one-liner so to speak. Basically I am just looking for style suggestions. Is it appropriate to do such a thing in this manner (one line?). I'm sure there are functions that could accomplish the task quicker, so I am open to those suggestions too, since my code is very long and convoluted. Also I should mention I wanted to write this with only base Ruby, no extra packages/gems.

Someone suggested Rubocop and the Style Guide so I will check those out.

  def funky_words(s)
s.strip.gsub(/\s+/, " ").split(" ").reverse.instance_eval{map{|elt| elt.gsub(/([aeiou])/i,"")}}.
zip(s.strip.split(" ").reverse.map{|elt| elt.scan(/([aeiou])/i).flatten}.instance_eval{map{|elt| elt.join}}).
map(&:join).join(" ").downcase
#first "line" reverses word order removes vowels, second "line" captures vowels, last "line" joins vowels and all words
end

• To those voting to close: please notify me if you still think it's unacceptable. This question was getting closed due to poor phrasing, it's not outside our scope IMO.
– Mast
Aug 20 '20 at 6:18

One-liners are good fun, but the world doesn't really need more of them. That said, they don't have to be so unreadable. What will your future brain say a year from now if you have to maintain that function?

Here's an approach illustrating a scalable technique to make even long "one-liners" readable by (1) using lines generously, (2) indenting code in the manner of a pretty-printed data structure to convey the logic's hierarchy (code is data, after all), and (3) including comments to assist the reader with the logic and intent.

def funky_words(s)
(
# Split into words.
s
.split
.reverse_each
.map { |word|
# Within each word, push vowels to the end, while preserving
# original order within consonants and vowels.
word
.each_char
.sort_by.with_index { |c, i| "aeiouAEIOU".include?(c) ? [1, i] : [0, i] }
.join
}
# Rejoin the new words.
.join(" ")
)
end

• Sweet! This is great, thank you! I need to learn Ruby's _index methods. Aug 26 '20 at 21:32

• downcase the string
• convert the resulting string to an array of words
• reverse the array of words
• convert each word in the array so that the vowels are at the end and order is preserved for both the vowels and non-vowels
• join the words in the resulting array to form a string

Let's begin with the penultimate operation. To make the code more readable and speed testing let's make that a separate method.

VOWELS = 'aeiou'

def shove_vowels_to_end(word)
vowels = ''
non_vowels = ''
word.each_char do |char|
if VOWELS.include?(char)
vowels << char
else
non_vowels << char
end
end
[non_vowels, vowels].join
end


Aside: I could have written word.chars do |char|... instead of word.each_char do |char|..., but the former has the disadvantage that word.chars returns an intermediate array, whereas the latter returns an enumerator, thereby consuming less memory.

Let's try it:

shove_vowels_to_end("atlastdisgonehurray!")
#=> "tlstdsgnhrry!aaioeua"


If desired we could make VOWELS a set (to employ Set#include?, which may speed calculations a litte:

require 'set'

VOWELS = 'aeiou'.each_char.to_set
#<Set: {"a", "e", "i", "o", "u"}>


We can now write the rest of the method around shove_vowels_to_end:

def funky_words(str)
str.downcase.split.map { |word| shove_vowels_to_end(word) }.join(' ')
end


I will discuss the code but first let's try it:

str = "Little Miss Muffett sat on her tuffet"

funky_words str
#=> "lttlie mssi mffttue sta no hre tfftue"


Depending on what is known about str we may need to change str.split to str.strip.split. str.split is the same as str.split(/\s+/), which is probably appropriate. See String#split.

The intermediate calculation is:

str.downcase.split.map { |word| shove_vowels_to_end(word) }
#=> ["lttlie", "mssi", "mffttue", "sta", "no", "hre", "tfftue"]


which is why we need .join(' ') at the end.

Notice that extra spaces are not preserved:

funky_words "some       spaces"
#=> "smoe spcsae"


Here is a more Ruby-like way of writing shove_vowels_to_end:

def shove_vowels_to_end(word)
word.each_char.with_object(['', '']) do |char, (non_vowels, vowels)|
if VOWELS.include?(char)
vowels << char
else
non_vowels << char
end
end.join
end


Notice how I used array decomposition to advantage when writing the block variables:

|char, (non_vowels, vowels)|


Here's another way to write funky_words. I modify each sequence of non-spaces with String#gsub.

require 'set'

VOWELS = %w|a e i o u|.to_set
#=> #<Set: {"a", "e", "i", "o", "u"}>

def funky_words(str)
str.downcase.gsub(/[^ ]+/) do |word|
vowels = ''
others = ''
word.each_char do |char|
if VOWELS.include?(char)
vowels.prepend(char)
else
others.prepend(char)
end
end
others + vowels
end.reverse
end

str = "Little Miss Muffett sat on her tuffet"
funky_words(str)
#=> "tfftue hre no sta mffttue mssi lttlie"


Consider modifying the word 'muffett'. It is to become 'mffttue'. However, since I reverse the string at the end I need to convert 'muffett' to 'muffett'.reverse #=> 'euttffm'. That is obtained in the following steps:

muffett
vowels = ''
others = 'm'

uffett
vowels = 'u'
others = 'm'

ffett
vowels = 'u'
others = 'fm'

fett
vowels = 'u'
others = 'ffm'

ett
vowels = 'eu'
others = 'ffm

tt
vowels = 'eu'
others = 'tffm'

t
vowels = 'eu'
others = 'ttffm'

vowels + others
#=> euttffm