🎉 Exercism Research is now launched. Help Exercism, help science and have some fun at research.exercism.io 🎉
Avatar of dgeiger

dgeiger's solution

to Pig Latin in the Delphi Pascal Track

Published at Sep 08 2020 · 0 comments
Instructions
Test suite
Solution

Implement a program that translates from English to Pig Latin.

Pig Latin is a made-up children's language that's intended to be confusing. It obeys a few simple rules (below), but when it's spoken quickly it's really difficult for non-children (and non-native speakers) to understand.

  • Rule 1: If a word begins with a vowel sound, add an "ay" sound to the end of the word. Please note that "xr" and "yt" at the beginning of a word make vowel sounds (e.g. "xray" -> "xrayay", "yttria" -> "yttriaay").
  • Rule 2: If a word begins with a consonant sound, move it to the end of the word and then add an "ay" sound to the end of the word. Consonant sounds can be made up of multiple consonants, a.k.a. a consonant cluster (e.g. "chair" -> "airchay").
  • Rule 3: If a word starts with a consonant sound followed by "qu", move it to the end of the word, and then add an "ay" sound to the end of the word (e.g. "square" -> "aresquay").
  • Rule 4: If a word contains a "y" after a consonant cluster or as the second letter in a two letter word it makes a vowel sound (e.g. "rhythm" -> "ythmrhay", "my" -> "ymay").

There are a few more rules for edge cases, and there are regional variants too.

See http://en.wikipedia.org/wiki/Pig_latin for more details.

Testing

In order to run the tests for this track, you will need to install DUnitX. Please see the installation instructions for more information.

Loading Exercises into Delphi

If Delphi is properly installed, and *.dpr file types have been associated with Delphi, then double clicking the supplied *.dpr file will start Delphi and load the exercise/project. control + F9 is the keyboard shortcut to compile the project or pressing F9 will compile and run the project.

Alternatively you may opt to start Delphi and load your project via. the File drop down menu.

When Questions Come Up

We monitor the Pascal-Delphi support room on gitter.im to help you with any questions that might arise.

Submitting Exercises

Note that, when trying to submit an exercise, make sure the exercise file you're submitting is in the exercism/delphi/<exerciseName> directory.

For example, if you're submitting ubob.pas for the Bob exercise, the submit command would be something like exercism submit <path_to_exercism_dir>/delphi/bob/ubob.pas.

Source

The Pig Latin exercise at Test First Teaching by Ultrasaurus https://github.com/ultrasaurus/test-first-teaching/blob/master/learn_ruby/pig_latin/

Submitting Incomplete Solutions

It's possible to submit an incomplete solution so you may request help from a mentor.

uPigLatinTests.pas

unit uPigLatinTests;

interface
uses
  DUnitX.TestFramework;

const
  CanonicalVersion = '1.2.0.1';

type
  [TestFixture]
  PigLatinTest = class(TObject)
  published
//    [Ignore('Comment the "[Ignore]" statement to run the test')]
    procedure Word_beginning_with_a;

    [Ignore]
    procedure Word_beginning_with_e;

    [Ignore]
    procedure Word_beginning_with_i;

    [Ignore]
    procedure Word_beginning_with_o;

    [Ignore]
    procedure Word_beginning_with_u;

    [Ignore]
    procedure Word_beginning_with_a_vowel_and_followed_by_a_qu;

    [Ignore]
    procedure Word_beginning_with_p;

    [Ignore]
    procedure Word_beginning_with_k;

    [Ignore]
    procedure Word_beginning_with_x;

    [Ignore]
    procedure Word_beginning_with_q_without_a_following_u;

    [Ignore]
    procedure Word_beginning_with_ch;

    [Ignore]
    procedure Word_beginning_with_qu;

    [Ignore]
    procedure Word_beginning_with_qu_and_a_preceding_consonant;

    [Ignore]
    procedure Word_beginning_with_th;

    [Ignore]
    procedure Word_beginning_with_thr;

    [Ignore]
    procedure Word_beginning_with_sch;

    [Ignore]
    procedure Word_beginning_with_yt;

    [Ignore]
    procedure Word_beginning_with_xr;

    [Ignore]
    procedure y_is_treated_like_a_consonant_at_the_beginning_of_a_word;

    [Ignore]
    procedure y_is_treated_like_a_vowel_at_the_end_of_a_consonant_cluster;

    [Ignore]
    procedure y_as_second_letter_in_two_letter_word;

    [Ignore]
    procedure A_whole_phrase;
  end;

implementation
uses uPigLatin;


{ PigLatinTest }

procedure PigLatinTest.A_whole_phrase;
begin
  Assert.AreEqual('ickquay astfay unray', TPigLatin.Translate('quick fast run'));
end;

procedure PigLatinTest.Word_beginning_with_a;
begin
  Assert.AreEqual('appleay', TPigLatin.Translate('apple'));
end;

procedure PigLatinTest.Word_beginning_with_a_vowel_and_followed_by_a_qu;
begin
  Assert.AreEqual('equalay', TPigLatin.Translate('equal'));
end;

procedure PigLatinTest.Word_beginning_with_ch;
begin
  Assert.AreEqual('airchay', TPigLatin.Translate('chair'));
end;

procedure PigLatinTest.Word_beginning_with_e;
begin
  Assert.AreEqual('earay', TPigLatin.Translate('ear'));
end;

procedure PigLatinTest.Word_beginning_with_i;
begin
  Assert.AreEqual('iglooay', TPigLatin.Translate('igloo'));
end;

procedure PigLatinTest.Word_beginning_with_k;
begin
  Assert.AreEqual('oalakay', TPigLatin.Translate('koala'));
end;

procedure PigLatinTest.Word_beginning_with_o;
begin
  Assert.AreEqual('objectay', TPigLatin.Translate('object'));
end;

procedure PigLatinTest.Word_beginning_with_p;
begin
  Assert.AreEqual('igpay', TPigLatin.Translate('pig'));
end;

procedure PigLatinTest.Word_beginning_with_qu;
begin
  Assert.AreEqual('eenquay', TPigLatin.Translate('queen'));
end;

procedure PigLatinTest.Word_beginning_with_qu_and_a_preceding_consonant;
begin
  Assert.AreEqual('aresquay', TPigLatin.Translate('square'));
end;

procedure PigLatinTest.Word_beginning_with_q_without_a_following_u;
begin
  Assert.AreEqual('atqay', TPigLatin.Translate('qat'));
end;

procedure PigLatinTest.Word_beginning_with_sch;
begin
  Assert.AreEqual('oolschay', TPigLatin.Translate('school'));
end;

procedure PigLatinTest.Word_beginning_with_th;
begin
  Assert.AreEqual('erapythay', TPigLatin.Translate('therapy'));
end;

procedure PigLatinTest.Word_beginning_with_thr;
begin
  Assert.AreEqual('ushthray', TPigLatin.Translate('thrush'));
end;

procedure PigLatinTest.Word_beginning_with_u;
begin
  Assert.AreEqual('underay', TPigLatin.Translate('under'));
end;

procedure PigLatinTest.Word_beginning_with_x;
begin
  Assert.AreEqual('enonxay', TPigLatin.Translate('xenon'));
end;

procedure PigLatinTest.Word_beginning_with_xr;
begin
  Assert.AreEqual('xrayay', TPigLatin.Translate('xray'));
end;

procedure PigLatinTest.y_as_second_letter_in_two_letter_word;
begin
  Assert.AreEqual('ymay', TPigLatin.Translate('my'));
end;

procedure PigLatinTest.y_is_treated_like_a_consonant_at_the_beginning_of_a_word;
begin
  Assert.AreEqual('ellowyay', TPigLatin.Translate('yellow'));
end;

procedure PigLatinTest.y_is_treated_like_a_vowel_at_the_end_of_a_consonant_cluster;
begin
  Assert.AreEqual('ythmrhay', TPigLatin.Translate('rhythm'));
end;

procedure PigLatinTest.Word_beginning_with_yt;
begin
  Assert.AreEqual('yttriaay', TPigLatin.Translate('yttria'));
end;

initialization
  TDUnitX.RegisterTestFixture(PigLatinTest);
end.
unit uPigLatin;

interface

uses
  System.Classes, System.StrUtils, System.SysUtils;

type
  TPigLatin = class
    private
      class var FCurrentWord: string;
      class var FIndex: Integer;
      class var FWords: TStringList;
      class var FVowels: TSysCharSet;
      class var FRule1Exceptions: TStringList;
      class var FRule2Exceptions: TSysCharSet;
      class var FRule3Exceptions: TSysCharSet;
      class var FRule4Exceptions: TSysCharSet;

      class function ConvertWords: String;
      class function Rule1: Boolean;
      class function Rule2: Boolean;
      class function Rule3: Boolean;
      class function Rule4: Boolean;

      class procedure AlterWord;

    public
      class function Translate(English: String): string;

  end;

implementation

{ TPigLatin }

const
  Suffix = 'ay';

class procedure TPigLatin.AlterWord;
begin
  // We append FIndex - 1 characters  plus "ay" to the word, keeping the
  // characters starting with position FIndex.

  // If FIndex equals one,
  if FIndex = 1 then
    // there are no characters to move from the beginning to the end,
    // so we just add the suffix
    FCurrentWord := FCurrentWord + Suffix
  else
    // Otherwise, we need to move some characters to the back of the word
    // before appending the suffix
    FCurrentWord := Copy(FCurrentWord, FIndex, Length(FCurrentWord)) +
                    Copy(FCurrentWord, 1, FIndex - 1) + Suffix;
end;

class function TPigLatin.ConvertWords: String;
var
  Index: Integer;
begin
  // Scan through the words one by one
  for Index := 0 to FWords.Count - 1 do
    begin
      // Get the current word
      FCurrentWord := FWords[Index];

      // See if it matches Rule #1
      if Rule1 then
        // It does, so alter the word accordingly
        AlterWord
      else
        // It didn't match Rule #1, so let's see if it matches Rule #1
        if Rule3 then
          // It does, so alter the word accordingly
          AlterWord
        else
          // It didn't match Rule #1 or Rule #3, so let's see if it matches Rule #3
          if Rule4 then
            // It does, so alter the word accordingly
            AlterWord
          else
            // It didn't match Rule #1, Rule #3, or Rule #4,
            // so let's see if it matches Rule #2
            if Rule2 then
              // It does, so alter the word accordingly
              AlterWord
            else
              // It didn't match any of the rules, so something's wrong.
              // We'll throw an error and quit
              raise Exception.Create('word violates rules');

      // Put the pig-latin version of the word into the list of words
      FWords[Index] := FCurrentWord;
    end;

  // Start the result string as an empty string
  Result := '';

  // Scan through the altered words one at a time
  for Index := 0 to FWords.Count - 1 do
    begin
      // Add the altered word to the result
      Result := Result + FWords[Index];

      // If there are multiple words, we need to add a space between them
      if (FWords.Count > 1) and (Index < FWords.Count - 1) then
        Result := Result + ' ';
    end;
end;

class function TPigLatin.Rule1: Boolean;
var
  Index: Integer;
begin
  // Let's start by assuming the test fails
  Result := False;

  // Mark that we start copying from the first character of the word if
  // the word matches this rule
  FIndex := 1;

  // If the first letter is a vowel,
  if CharInSet(FCurrentWord[1], FVowels) then
    // it matches this rule
    Result := True
  else
    begin
      // The word doesn't start with a vowel. Now, we need to see
      // if it starts with one of the Rule #1 exceptions
      Index := FRule1Exceptions.IndexOf(Copy(FCurrentWord, 1, 2));

      // The beginning of the word is in the list of exceptions,
      if Index >= 0 then
        // so it matches the rule
        Result := True;
    end;
end;

class function TPigLatin.Rule2: Boolean;
var
  Index: Integer;
begin
  // Let's start by assuming the test fails
  Result := False;

  // Is the first character sometimes a vowel?
  if CharInSet(FCurrentWord[1], FRule2Exceptions) then
    begin
      // Yes, so we signal that it matches the rule
      Result := True;

      // Indicate that we need to just append the suffix
      FIndex := 1;

      // and leave early
      Exit;
    end;

  // The word doesn't start with a vowel, so we start looking for the first vowel
  for Index := 1 to Length(FCurrentWord) do
    begin
      // Is the current character a vowel?
      if CharInSet(FCurrentWord[Index], FVowels)  then
        begin
          // Yes, so signal that it matches the rule,
          Result := True;

          // save the position to start copying later,
          FIndex := Index;

          // and leave early
          Exit;
        end;
    end;
end;

class function TPigLatin.Rule3: Boolean;
var
  Index: Integer;
begin
  // Let's start by assuming the test fails
  Result := False;

  // Let's start looking at the letters of the current word
  for Index := 1 to Length(FCurrentWord) do
    begin
      // Is the current character a vowel (except for the exceptions to Rule #3)?
      if CharInSet(FCurrentWord[Index], FVowels - FRule3Exceptions)  then
        begin
          // Yes, so signal that the word matches this rule,
          Result := True;

          // save the postion to copy from,
          FIndex := Index;

          // and leave early.
          Exit;
        end;
    end;
end;

class function TPigLatin.Rule4: Boolean;
var
  Index: Integer;
begin
  // Let's start by assuming the test fails
  Result := False;

  // If the current word is only two characters long, and the second
  // character is one of the Rule #4 exceptions,
  if (Length(FCurrentWord) = 2) and CharInSet(FCurrentWord[2], FRule4Exceptions) then
    begin
      // mark the word as matching Rule #4,
      Result := True;

      // save the position to start copying from,
      FIndex := 2;

      // and leave early.
      Exit;
    end;

  // Either the word isn't two characters long, or it doesn't end in an
  // exception to Rule #4. So, we see if it starts with an exception followed
  // by a vowel.
  if CharInSet(FCurrentWord[1], FRule4Exceptions) and (Length(FCurrentWord) > 1) and
      CharInSet(FCurrentWord[2], FVowels) then
    begin
      // Yes, it matches this rule, so we signal it does,
      Result := True;

      // save the position to start copying,
      FIndex := 2;

      // and leave early.
      Exit;
    end;

  // We haven't matched this rule yet, so we start looking for a vowel or
  // an exception to Rule #4.
  for Index := 1 to Length(FCurrentWord) do
    begin
      // Is the current character a vowel or an exception?
      if CharInSet(FCurrentWord[Index], FVowels + FRule4Exceptions)  then
        begin
          // Signal that the word matches the rule,
          Result := True;

          // save the position to start copying,
          FIndex := Index;

          // and leave early.
          Exit;
        end;
    end;
end;

class function TPigLatin.Translate(English: String): string;
begin
  // Create a storage space for the words in the passed string
  FWords := TStringList.Create;

  // Make sure the list is empty
  FWords.Clear;

  // Set the delimiter for the list
  FWords.Delimiter := ' ';

  // Set the delmited string so that we can get the individual words
  FWords.DelimitedText := English;

  // Initialize the set of vowels
  FVowels := ['a','e','i','o','u'];

  // Initialize the list of exceptions for Rule #1
  FRule1Exceptions := TStringList.Create;
  FRule1Exceptions.Add('xr');
  FRule1Exceptions.Add('yt');

  // Initialize the set of exceptions for Rule #2
  FRule2Exceptions := ['y'];

  // Initialize the set of exceptions for Rule #2
  FRule3Exceptions := ['u'];

  // Initialize the set of exceptions for Rule #2. This is redundant and could
  // be removed by using FRule2Exceptions in the test for Rule #4.
  // However, this way each rule can be tailored for it's own needs.
  FRule4Exceptions := ['y'];

  // Get the list of converted words
  Result := ConvertWords;

  // Clean up
  FRule1Exceptions.Destroy;
  FWords.Destroy;
end;

end.

Community comments

Find this solution interesting? Ask the author a question to learn more.

What can you learn from this solution?

A huge amount can be learned from reading other people’s code. This is why we wanted to give exercism users the option of making their solutions public.

Here are some questions to help you reflect on this solution and learn the most from it.

  • What compromises have been made?
  • Are there new concepts here that you could read more about to improve your understanding?