3
\$\begingroup\$

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
\$\endgroup\$
1
  • 1
    \$\begingroup\$ 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. \$\endgroup\$
    – Mast
    Aug 20 '20 at 6:18
3
\$\begingroup\$

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
\$\endgroup\$
1
  • \$\begingroup\$ Sweet! This is great, thank you! I need to learn Ruby's _index methods. \$\endgroup\$
    – whoami
    Aug 26 '20 at 21:32
1
\$\begingroup\$

Your solution does the following:

  • 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

See String#each_char, String#include? and String#join.

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

See Enumerator#with_object.

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`
\$\endgroup\$
0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.