Pig Latin

The Problem

Create a program that translates English into Pig Latin. In case you didn’t go through the Pig Latin phase as a kid (or in case you’ve forgotten how it works), here are the rules:

  1. If a word begins with a vowel, add “ay” to the end of the word
  2. If a word begins with a consonant, move it to the end of the word before adding “ay” to the end
  3. If a ‘q’ is followed by a ‘u’, treat them as one consonant group
  4. If a word starts with ‘y’ or ‘x’ followed by a consonant, treat them as vowels

The Test Input

Testing Rule 1

  • Given the input “apple”, the program should return “appleay”
  • Given the input “ear”, the program should return “earay”
  • Given the input “igloo”, the program should return “iglooay”
  • Given the input “object”, the program should return “objectay”
  • Given the input “under”, the program should return “underay”
  • Given the input “equal”, the program should return “equalay”

Testing Rule 2 with one starting consonant

  • Given the input “pig”, the program should return “igpay”
  • Given the input “koala”, the program should return “oalakay”
  • Given the input “yellow”, the program should return “ellowyay”
  • Given the input “xenon”, the program should return “enonxay”
  • Given the input “qat”, the program should return “atqay”
  • Given the input “pleasure”, the program should return “easureplay”
  • Given the input “stringify”, the program should return “ingifystray”
  • Given the input “zkrrkrkrkrzzzkewk”, the program should return “ewkzkrrkrkrkrzzzkay”

Testing Rule 2 with multiple starting consonants

  • Given the input “chair”, the program should return “airchay”
  • Given the input “therapy”, the program should return “erapythay”
  • Given the input “thrush”, the program should return “ushthray”
  • Given the input “school”, the program should return “oolschay”

Testing Rule 3

  • Given the input “queen”, the program should return “eenquay”
  • Given the input “square”, the program should return “aresquay”

Testing Rule 4

  • Given the input “yttria”, the program should return “yttriaay”
  • Given the input “yddria”, the program should return “yddriaay”
  • Given the input “xray”, the program should return “xrayay”
  • Given the input “xbot”, the program should return “xbotay”

Testing Phrases

  • Given the phrase “quick fast run”, the program should return “ickquay astfay unray”
  • Given the phrase “the quick brown fox jumps over the lazy dog”, the program should return “ethay ickquay ownbray oxfay umpsjay overay ethay azylay ogday”

Solving the Problem

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Split the phrase into words
For each word:
If first character is a vowel:
Add "ay" to end of word
Else
While the character we're looking at is a consonant,
look at the next character
If the next character is a 'u' and the current character is a 'q':
treat them both as if they were consonants and continue
If the next character is a consonant and the current character is a 'y' or 'x':
treat the 'y' or 'x' as if it was a vowel
If the next character is treated as a vowel:
- Split the word before the vowel,
- Move the partial string before the vowel to the end of the word
- Add "ay" to the end of the word
1
2
3
4
5
6
def translate(phrase) do
words = String.split(phrase, " ")

Enum.map(words, fn x -> piglify(x) end)
|> Enum.join(" ")
end

String.split(phrase, " ") is taking the phrase and splitting them by a single space, in order to get a list of words. We then call Enum.map on that list of words in order to make a call to piglify, which will translate those words into Pig Latin. Then we use the pipe operator |> to take the output of the map and use it as the first argument to Enum.join(" "), which turns the list of piglified words back into a string, preserving the space between words.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58

defmodule PigLatin do
@doc """
Given a `phrase`, translate it a word at a time to Pig Latin.

Words beginning with consonants should have the consonant moved to the end of
the word, followed by "ay".

Words beginning with vowels (aeiou) should have "ay" added to the end of the
word.

Some groups of letters are treated like consonants, including "ch", "qu",
"squ", "th", "thr", and "sch".

Some groups are treated like vowels, including "yt" and "xr".
"""
@spec translate(phrase :: String.t()) :: String.t()
def translate(phrase) do
words = String.split(phrase, " ", trim: true)

Enum.map(words, fn x -> piglify(x) end)
|> Enum.join(" ")
end

def piglify(word) do
first_char = String.at(word, 0)
second_char = String.at(word, 1)

split_consonants =
Regex.split(~r/([aeio]|(?<!q)u)(.*)/, word,
trim: true,
parts: 2,
include_captures: true
)

consonant = Enum.at(split_consonants, 0)
vowel = Enum.at(split_consonants, 1)

# string_after_second =
# Enum.slice(split_string, 1..-1)
# |> Enum.join()

# first_two_chars =
# Enum.slice(split_string, 0..1)
# |> Enum.join()

cond do
String.match?(first_char, ~r/[aeio]|(?<!q)u/) ->
word <> "ay"

String.match?(first_char, ~r/y|x/) && !String.match?(second_char, ~r/[aeio]|(?<!q)u/) ->
word <> "ay"

true ->
vowel <> consonant <> "ay"
end
end
end

String.at() is a fairly straightforward function. It takes a string as the first argument, an index as the second, and returns the grapheme (essentially, letter) that exists at that index of the string (or nil if the index is greater than the length of the string). So first_char and second_char are literally just the first and second letters of the word.

What is going on with this regex?!, you might ask. And it’s a fair question. Let’s break it down.

~r/([aeio]|(?<!q)u)(.*)

~r is a sigil for creating regular expressions
The way I interpreted () was as a capture group because that’s what I’m familiar with it being in other languages, though I’m not certain that’s the same thing that’s happening in Elixir.
[aeio] is to match characters who are members in that set.
| is, predictably, ‘or’
(?<!q)u is “match u but only if it isn’t following a ‘q’”
(.*) is “match everything else in the string”, since . is the wildcard to match any character and * is a quantifier to match 0 or more.

So if this is doing what I think it is, it’s taking the word and splitting it into two parts. Because the only cases we’re going to be working with split_consonants is in situations where the first part of the word is a consonant, I think that’s why the first part of the split is a consonant and the second is the vowel. I honestly need to play around with it a bit more to know for sure.

Then for the conditional, we want to have a case for if the first character in the string matches the regular expression we’ve made to determine if a character is a vowel or will be treated as a vowel and if it is, return the word concatenated with “ay” word <> "ay".

We then have a conditional for if the first character is a y or x if the second character is a consonant (which is really just putting a not in front of the String.match? for vowels). If that’s the case, we also want to return the word concatenated with “ay”.

Then, since evrerything else should follow the alternative method for building up pig latin, we can make this conditional simply be true since if the other two conditions fail, this should always pass. If the word gets to this conditional, then the vowel portion of the word should be concatenated with the consonant-leading portion of the word concatenated with “ay”, vowel <> consonant <> "ay"

Let me know if you thought this was helpful (or what you thought of it in general)!

See ya!

- Amy