map with recursion and mkString

Pig Latin
Pig Latin in Scala
object PigLatin {
  private val vowels = Set('a', 'e', 'i', 'o', 'u')
  private val vowels_y = Set('a', 'e', 'i', 'o', 'u', 'y')
  private val specials = Set("xr", "yt")

  def translate(phrase: String): String = {

    phrase
      .split(" ")
      .map(word =>
        if (vowels.contains(word.head) || specials.contains(word.slice(0, 2)))
          word + "ay"
        else findVowel(word, 1)
      )
      .mkString(" ")
  }

  @scala.annotation.tailrec
  private def findVowel(word: String, pos: Int): String = {
    word.charAt(pos) match {
      case ltr if vowels_y.contains(ltr) => {
        val posFix = if (word.slice(pos - 1, pos + 1) == "qu") pos + 1 else pos
        word.slice(posFix, word.length()) + word.slice(0, posFix) + "ay"
      }
      case _ => findVowel(word, pos + 1)
    }
  }
}

This approach starts by defining some Sets to to hold the vowels, vowels with y, and the special groups of letters.

The input phrase is split on a space character and the word or words are chained to the map() method. Each word is passed to the lambda which consists of an if/else expression. If the Set of vowels includes the first character of the word (also called the head), or if a slice() of the first two characters is one of the special groups of letters, then map() outputs the word appended with ay.

Otherwise, the findVowel() method is passed the word and a starting position of 1.

The findVowel() method is annotated with the @tailrec annotation to verify that the method can be compiled with tail call optimization.

A tail call is a particular form of recursion where the last call in the method is a call to the same method and nothing else.

In other words, if the last call in recurMe() is recurMe(arg1, arg2) + 1, the + 1 makes the recursion non-tail recursive.

If the last call in recurMe() is recurMe(arg1, arg2, acc + 1), then the recursion is a tail call, because only the method is being called with no other operation being peformed on it.

A match expression is used to perform pattern matching on the result of passing the position to the charAt() method.

A pattern guard is used to check if the character is a vowel (y is considered a vowel at this point.) If so, the positon is adjusted if the previous character and the current character are qu, otherwise it stays the same. The match returns a slice() from the position until the end of the word, to which is concatenated a slice() from the beginning of the word up to but not including the character at the position, to which is concatenated ay.

If the character is not a vowel, then findVowel() calls itself, passing in the same word and adding 1 to the position.

After all of the words have been mapped, they are reassembled into a string by the mkString() method.

9th Oct 2024 · Found it useful?