Introduction
Your parents have challenged you and your sibling to a game of two-on-two basketball.
Confident they’ll win, they let you score the first couple of points, but then start taking over the game.
Needing a little boost, you start speaking in Pig Latin, which is a made-up children’s language that’s difficult for non-children to understand.
This will give you the edge to prevail over your parents!
Instructions
Instructions
Your task is to translate text from English to Pig Latin.
The translation is defined using four rules, which look at the pattern of vowels and consonants at the beginning of a word.
These rules look at each word’s use of vowels and consonants:
- vowels: the letters
a, e, i, o, and u
- consonants: the other 21 letters of the English alphabet
Rule 1
If a word begins with a vowel, or starts with "xr" or "yt", add an "ay" sound to the end of the word.
For example:
"apple" -> "appleay" (starts with vowel)
"xray" -> "xrayay" (starts with "xr")
"yttria" -> "yttriaay" (starts with "yt")
Rule 2
If a word begins with one or more consonants, first move those consonants to the end of the word and then add an "ay" sound to the end of the word.
For example:
"pig" -> "igp" -> "igpay" (starts with single consonant)
"chair" -> "airch" -> "airchay" (starts with multiple consonants)
"thrush" -> "ushthr" -> "ushthray" (starts with multiple consonants)
Rule 3
If a word starts with zero or more consonants followed by "qu", first move those consonants (if any) and the "qu" part to the end of the word, and then add an "ay" sound to the end of the word.
For example:
"quick" -> "ickqu" -> "ickquay" (starts with "qu", no preceding consonants)
"square" -> "aresqu" -> "aresquay" (starts with one consonant followed by "qu”)
Rule 4
If a word starts with one or more consonants followed by "y", first move the consonants preceding the "y"to the end of the word, and then add an "ay" sound to the end of the word.
Some examples:
"my" -> "ym" -> "ymay" (starts with single consonant followed by "y")
"rhythm" -> "ythmrh" -> "ythmrhay" (starts with multiple consonants followed by "y")
Dig Deeper
Map lookups
map lookups
// Package piglatin is a small library for translating a sentence into Pig Latin.
package piglatin
import (
"strings"
)
var vowels = map[byte]bool{'a': true, 'e': true, 'i': true, 'o': true, 'u': true}
var specials = map[string]bool{"xr": true, "yt": true}
var vowels_y = map[byte]bool{'a': true, 'e': true, 'i': true, 'o': true, 'u': true, 'y': true}
// Sentence translates a sentence into Pig Latin.
func Sentence(phrase string) string {
piggyfied := strings.Builder{}
for _, word := range strings.Split(phrase, " ") {
if piggyfied.Len() != 0 {
piggyfied.WriteByte(' ')
}
if vowels[word[0]] || specials[word[:2]] {
piggyfied.WriteString(word + "ay")
continue
}
for pos := 1; pos < len(word); pos++ {
letter := word[pos]
if vowels_y[letter] {
if letter == 'u' && word[pos-1] == 'q' {
pos++
}
piggyfied.WriteString(word[pos:] + word[:pos] + "ay")
break
}
}
}
return piggyfied.String()
}
The approach imports the strings package so it can use strings.Builder as an efficient way to build the output string.
This approach starts be defining several maps for looking up vowels and special letter combinations.
The maps have boolean values so that a key can be looked up simply as
if vowels[word[0]]
instead of
if _, ok := vowels[word[0]]; ok {
Doing so also allows having multiple checks on the same line,
since there can not be multiple if with short statements on the same line.
After instantiating a strings.Builder the sentence is Split with a space separator to iterate the words.
Unless it is the first word, a space is written to the Builder to separate the word from the previous word.
The word is checked for starting with a vowel or if the beginning slice of two characters is in the map of “specials”.
If so, the word is written to the Builder along with ay at the end.
Then the iteration is continued to the next word.
If not, each byte in the word is iterated from index 1 up to the length of the word, until finding a vowel (that now includes y).
The ways to iterate characters are by Unicode runes, or by each letter being a string, or by each letter being a byte.
The runes are from range on a string, the strings from Split(), and the bytes from indexing into the string.
Another way to iterate runes is to convert the string to a rune slice and range on it.
The difference between ranging on a rune slice vs ranging on a string is that the index returned from a string is the position of the next rune in bytes,
not which rune it is.
For example, if the first unicode character is two bytes,
then the second unicode character index will be 2 when ranging on a string and 1 when ranging on a rune slice.
As of the time of this writing we can iterate bytes, since all of the characters are single-byte ASCII characters.
If the found vowel is u, the previous consonant is checked for being q and the position is adjusted accordingly.
A slice is then taken from the position until the end of the word,
plus a slice taken from the beginning of the word and stopped before the position, plus ay, and added to the Builder.
After the iteraton of words is finished, the Builder’s String method is called to return the accumulated string from the function.
Map lookups with generics
map lookups with generics
// Package piglatin is a small library for translating a sentence into Pig Latin.
package piglatin
import (
"strings"
)
type void struct{}
var blank void
var vowels = map[byte]void{'a': blank, 'e': blank, 'i': blank, 'o': blank, 'u': blank}
var specials = map[string]void{"xr": blank, "yt": blank}
var vowels_y = map[byte]void{'a': blank, 'e': blank, 'i': blank, 'o': blank, 'u': blank, 'y': blank}
type container interface {
byte | string
}
func contains[T container](values map[T]void, value T) bool {
_, ok := values[value]
return ok
}
// Sentence translates a sentence into Pig Latin.
func Sentence(phrase string) string {
piggyfied := strings.Builder{}
for _, word := range strings.Split(phrase, " ") {
if piggyfied.Len() != 0 {
piggyfied.WriteByte(' ')
}
if contains(vowels, word[0]) || contains(specials, word[:2]) {
piggyfied.WriteString(word + "ay")
continue
}
for pos := 1; pos < len(word); pos++ {
letter := word[pos]
if contains(vowels_y, letter) {
if letter == 'u' && word[pos-1] == 'q' {
pos++
}
piggyfied.WriteString(word[pos:] + word[:pos] + "ay")
break
}
}
}
return piggyfied.String()
}
The approach imports the strings package so it can use strings.Builder as an efficient way to build the output string.
This approach starts be defining a void type that will be used in the package.
Several maps for then defined for looking up vowels and special letter combinations.
The maps have an empty struct as the type for their values.
By assigning values of an empty struct to the `map`, it essentially makes the keys what is known as a "set" in other languages.
A set is a collection of unique values.
More info on implementing a set in Go can be found [here](https://yourbasic.org/golang/implement-set/).
An interface type is then defined which will be used to constain which types are to be used for the generics:
Generics are a way of writing code that is independent of the specific types being used.
Functions and types may now be written to use any of a set of types.
Since there can not be multiple if with short statements on the same line,
the contains() function is defined which takes a generic type parameter of [T container].
T is the name of the parameter.
The name does not have to be T, but it is a common convention in Go (and other languages) to start with T,
and if a second name is needed, to use U, and so on.
The type of T is constrained to be one of the types defined in the container interface.
The arguments to the function are a map of the generic type T with void values, and a value of type T.
So, contains() can take arguments of either map[byte]void, byte or map[string]void, string.
The contains() function is defined to return a boolean, which is whether the value is a key of the map.
After instantiating a strings.Builder the sentence is Split with a space separator to iterate the words.
Unless it is the first word, a space is written to the Builder to separate the word from the previous word.
The contains() function is used to check if the word starts with a vowel or if the beginning slice of two characters is in the map of “specials”.
If so, the word is written to the Builder along with ay at the end.
Then the iteration is continued to the next word.
If not, each byte in the word is iterated from index 1 up to the length of the word, until finding a vowel (that now includes y).
The ways to iterate characters are by Unicode runes, or by each letter being a string, or by each letter being a byte.
The runes are from range on a string, the strings from Split(), and the bytes from indexing into the string.
Another way to iterate runes is to convert the string to a rune slice and range on it.
The difference between ranging on a rune slice vs ranging on a string is that the index returned from a string is the position of the next rune in bytes,
not which rune it is.
For example, if the first unicode character is two bytes,
then the second unicode character index will be 2 when ranging on a string and 1 when ranging on a rune slice.
As of the time of this writing we can iterate bytes, since all of the characters are single-byte ASCII characters.
If the found vowel is u, the previous consonant is checked for being q and the position is adjusted accordingly.
A slice is then taken from the position until the end of the word,
plus a slice taken from the beginning of the word and stopped before the position, plus ay, and added to the Builder.
After the iteraton of words is finished, the Builder’s String method is called to return the accumulated string from the function.
Source: Exercism go/pig-latin