Search code examples
rubycapitalizationpunctuation

Why does my ruby pig latin translator not capitalize the first word of a string properly?


I am trying to write a program that translates a string with some capitalized words and punctuation into Pig Latin. Here are the conditions:

1) words beginning with a vowel should just tack on "ay".

2) words beginning with a single phoneme like "sch" or "qu" or "squ" or "ch" should move all of those characters to the end, not just the first letter, and then tack on "ay".

3) the regular pig latin rules for a word beginning with one consonant (i.e., "Well," => 'Ellway,").

4) capitalization and punctuation should be preserved, but the initial letter would change if the letter doesn't begin with a vowel. So "Well," would become "Ellway,".

Everything works, except for the first word of my string. The fourth condition is never met with the first word of a string. So, for example, "Well," becomes "ellWay,". So punctuation works, but the capitalization isn't working properly.

Edit: I have realized that this issue occurs only when the word does NOT begin with a vowel. So, "Actually," becomes "Actuallyay," (which it should), but "Quaint," becomes "aintQuay,", when it should be "Aintquay,". So, here is the code where I actually pass the pig latin into the array named pig_latin:

    string = string.split(' ')
    pig_latin = []
      string.each do |word|
      if vowels.include?(word[0])
        pig_latin << word + "ay"
      elsif (consonants.include?(word[0]) && consonants.include?(word[1]) && consonants.include?(word[2])) || word[1..2].include?('qu')
        pig_latin << (word[3..-1] + word[0..2] + "ay")
      elsif (consonants.include?(word[0]) && consonants.include?(word[1])) || word[0..1].include?('qu')
        pig_latin << (word[2..-1] + word[0..1] + "ay")
      else
        pig_latin << (word[1..-1] + word[0] + "ay")
      end
    end

Here is the part of my code that handles the capitalization and punctuation. To clarify, pig_latin is the array with the pig-latinized phrase passed into it. uppercase_alphabet is an array i created to include all uppercase letters:

    idx1 = 0
    while idx1 < pig_latin.count
      word = pig_latin[idx1]
      idx2 = 0
      while idx2 < word.length
        if uppercase_alphabet.include?(word[idx2])
            word[idx2] = word[idx2].downcase
            word[0] = word[0].upcase
        end

        if punctuation.include?(word[idx2])
            word[word.length], word[idx2] = word[idx2], ''
        end

        idx2 += 1
      end

      idx1 += 1
    end

pig_latin.join(' ')

Edit: Here is the code outlining the various arrays I'm using:

vowels = ['a', 'e', 'i', 'o', 'u', 'A', 'E', 'I', 'O', 'U']
lowercase_alphabet = ('a'..'z').to_a
uppercase_alphabet = ('A'..'Z').to_a
alphabet = lowercase_alphabet + uppercase_alphabet
punctuation = ['.', ',', ';', '?', '!', ':']
consonants = []
alphabet.each do |letter|
    consonants << letter unless vowels.include?(letter)
end

And, here are the errors I'm getting when I run the test with the following string: "Well, I have, not even. seen that movie." (I understand the punctuation makes no sense).

    1) #translate retains punctuation from the original phrase
    Failure/Error: s.should == "Ellway, Iay avehay, otnay evenay.  eensay atthay oviemay."

        expected: "Ellway, Iay avehay, otnay evenay. eensay atthay oviemay."

        got: "ellWay, Iay avehay, otnay evenay. eensay atthay oviemay." (using ==)

        # ./spec/04_pig_latin_spec.rb:83:in `block (2 levels) in <top (required)>

Solution

  • It is hard to debug imaginary code. You are asking people why your code doesn't work without providing the values of some key variables.

    Her are some tips:

    1) A String works just like an Array, so you don't have to create an Array of individual letters, which forces you to type all the commas and quote marks:

    vowels = 'aeiou'
    
    vowels.include?('a')  #=>true
    

    2) You don't have to include caps in your arrays of consonants and vowels, instead you can downcase before calling include?():

     ch = 'A'
     vowels.include?(ch.downcase)  #=> true
    

    3) When you are debugging, puts and p(for Arrays, Hashes) are your friend. You can find out which elsif branches are executing by adding puts/p statements:

      if vowels.include?(word[0])
        puts 'X'
        pig_latin << word + "ay"
      elsif (consonants.include?(word[0]) && consonants.include?(word[1]) && consonants.include?(word[2])) || word[1..2].include?('qu')
        puts 'A'
        pig_latin << (word[3..-1] + word[0..2] + "ay")
      elsif (consonants.include?(word[0]) && consonants.include?(word[1])) || word[0..1].include?('qu')
        puts 'B'
        pig_latin << (word[2..-1] + word[0..1] + "ay")
      else
        puts 'C'
        pig_latin << (word[1..-1] + word[0] + "ay")
      end
    end
    

    4) When you are comparing strings, you can use ==. Instead of this:

    word[0..1].include?('qu')
    

    ...you can write:

    if word[0..1].downcase == 'qu'
    

    It's more efficient to use == when you can.

    5) Your if conditionals are too complex. If you know what regexes are, you can simplify things by extracting the consonants at the beginning of a word, and then using if statements to test what the consonants are:

    words = %w{
      schlepp
      quail
      squall
      checkers
    }
    
    consonants = ('a'..'z').to_a.join.tr('aeiou', '')
    
    words.each do |word|
      md = word.match(/
            \A                 #match start of string, followed by...
            [#{consonants}]+   #a consonant, 1 or more times
      /x)
    
      if md
        starting_consonants = md[0]
    
        #Test for starting_consonants here, e.g.
        #if starting_consonants == 'q' and word[1] == 'u'
        #   do something
      else  #then word starts with a vowel
        ...
      end
    end
    
    --output:--
    schl
    q
    sq
    ch
    

    You can limit the number of consonants extracted to three like this:

    [#{consonants}]{1,3}
    

    6) I would handle capitalization at the same time you change the words--then you won't have to search through all the letters in every word. First thing, check for capitalization of first letter (then set a flag variable, e.g. capitalized = true). Then downcase the first letter. Then after you change the word, if there was a capital, upcase the first letter(you can also call capitalize(), but the result can be different than just calling upcase() on the first letter). That way you don't have to search through the whole word in your complicated nested loop. Be sure to set the flag variable back to false.

    7) In ruby, you rarely use while loops and increment a counter:

    while idx2 < word.length
      char = word[idx2]
      ...
      ...
      idx2 += 1
    end
    

    Instead, you use each() loops:

       word.each_char do |char|
         #do something with char
    
       end