2

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

5 Answers5

1

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"
ggorlen
  • 44,755
  • 7
  • 76
  • 106
1

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"]]]
iGian
  • 11,023
  • 3
  • 21
  • 36
  • It works if the word starts with a consonant. To get empty string when the word starts with a vowel: `word.chars.chunk { |e| !VOWELS.include? e }.first.then{ |s| s.first ? s.last : [] }.join` – iGian Jun 01 '19 at 18:21
1

Using take_while is semantic.

word.chars.take_while { |c| vowels.none? c }.join
Sagar Pandya
  • 9,323
  • 2
  • 24
  • 35
0
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"
max pleaner
  • 26,189
  • 9
  • 66
  • 118
  • Thank you for the additional solution! I accepted the other answer as it seemed even more straightforward. – newkidontheblock Jun 01 '19 at 17:51
  • @astarisborn yes, no worries. There are of course many way to do things. I do think using something like `reduce` is more idiomatic, but yes, it is _simpler_ just to use `each`. – max pleaner Jun 01 '19 at 17:52
0

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.

Cary Swoveland
  • 106,649
  • 6
  • 63
  • 100