Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Building a training set of tags for delphi #507

Closed
ErikSchierboom opened this issue Nov 1, 2023 · 26 comments
Closed

Building a training set of tags for delphi #507

ErikSchierboom opened this issue Nov 1, 2023 · 26 comments

Comments

@ErikSchierboom
Copy link
Member

Hello lovely maintainers 👋

We've recently added "tags" to student's solutions. These express the constructs, paradigms and techniques that a solution uses. We are going to be using these tags for lots of things including filtering, pointing a student to alternative approaches, and much more.

In order to do this, we've built out a full AST-based tagger in C#, which has allowed us to do things like detect recursion or bit shifting. We've set things up so other tracks can do the same for their languages, but its a lot of work, and we've determined that actually it may be unnecessary. Instead we think that we can use machine learning to achieve tagging with good enough results. We've fine-tuned a model that can determine the correct tags for C# from the examples with a high success rate. It's also doing reasonably well in an untrained state for other languages. We think that with only a few examples per language, we can potentially get some quite good results, and that we can then refine things further as we go.

I released a new video on the Insiders page that talks through this in more detail.

We're going to be adding a fully-fledged UI in the coming weeks that allow maintainers and mentors to tag solutions and create training sets for the neural networks, but to start with, we're hoping you would be willing to manually tag 20 solutions for this track. In this post we'll add 20 comments, each with a student's solution, and the tags our model has generated. Your mission (should you choose to accept it) is to edit the tags on each issue, removing any incorrect ones, and add any that are missing. In order to build one model that performs well across languages, it's best if you stick as closely as possible to the C# tags as you can. Those are listed here. If you want to add extra tags, that's totally fine, but please don't arbitrarily reword existing tags, even if you don't like what Erik's chosen, as it'll just make it less likely that your language gets the correct tags assigned by the neural network.


To summarise - there are two paths forward for this issue:

  1. You're up for helping: Add a comment saying you're up for helping. Update the tags some time in the next few days. Add a comment when you're done. We'll then add them to our training set and move forward.
  2. You not up for helping: No problem! Just please add a comment letting us know :)

If you tell us you're not able/wanting to help or there's no comment added, we'll automatically crowd-source this in a week or so.

Finally, if you have questions or want to discuss things, it would be best done on the forum, so the knowledge can be shared across all maintainers in all tracks.

Thanks for your help! 💙


Note: Meta discussion on the forum

@ErikSchierboom
Copy link
Member Author

Exercise: two-fer

Code

unit utwofer;

interface
 function twoFer(aName: string='you'): string;

implementation

function twoFer(aName: string='you'): string;
begin
  result := 'One for ' + aName + ', one for me.';
end;

end.

Tags:

construct:add
construct:assignment
construct:class
construct:constructor
construct:method
construct:parameter
construct:string
construct:throw
construct:try
construct:unit
construct:uses
construct:visibility-modifiers
paradigm:object-oriented
technique:exceptions

@ErikSchierboom
Copy link
Member Author

Exercise: rna-transcription

Code

unit uRnaTranscription;

interface
uses Generics.Collections;

type
  complement = class
  private
    class var
      fDnaToRna: TDictionary<char, char>;
  public
    class function OfDna(nucleotide: string): string; static;
  end;

implementation
uses SysUtils;

class function complement.OfDna(nucleotide: string): string;
begin
  result := '';
  var lRNA := '';
  if not trim(nucleotide).IsEmpty then
  begin
    if not assigned(fDNAToRna) then
    begin
      fDnaToRna := TDictionary<char, char>.Create;
      with fDnatoRna do
      begin
        add('G','C');
        add('C','G');
        add('T','A');
        add('A','U');
      end;
    end;
    var elements := nucleotide.ToUpper.ToCharArray;
    var i := 0;
    while i <= high(elements) do
    begin
      var RNAelement: char;
      if fDnaToRna.TryGetValue(elements[i], RNAelement) then
      begin
        lRNA := lRNA + RNAelement;
        inc(i);
      end
      else
      begin
        lRNA := '';
        break;
      end;
    end;
  end;
  result := lRNA;
end;

end.

Tags:

construct:assignment
construct:break
construct:char
construct:class
construct:dictionary
construct:field
construct:for-loop
construct:if
construct:implicit-conversion
construct:indexer
construct:invocation
construct:lambda
construct:method
construct:number
construct:parameter
construct:string
construct:then
construct:throw
construct:using-directive
construct:variable
construct:visibility-modifiers
construct:while-loop
paradigm:functional
paradigm:imperative
paradigm:object-oriented
technique:exceptions
technique:higher-order-functions
technique:looping
uses:TDictionary<TKey,TValue>

@ErikSchierboom
Copy link
Member Author

Exercise: phone-number

Code

unit uPhoneNumber;

interface

type
  IPhoneNumber = interface
    function Clean: string;
    function Area: string;
    function Exchange: string;
    function ToString: string;
  end;

  TPhoneNumber = class(TInterfacedObject, IPhoneNumber)
  private
    FNumber: string;
  public
    constructor Create(Number: string);
    function Clean: string;
    function Area: string;
    function Exchange: string;
    function ToString: string; override;
  end;

function NewPhoneNumber(Number: string): TPhoneNumber;

implementation

uses
  System.SysUtils, System.StrUtils;

{ TPhoneNumber }

function NewPhoneNumber(Number: string): TPhoneNumber;
begin
  Result := TPhoneNumber.Create(Number);
end;

function TPhoneNumber.Clean: string;
const
  Separators: TSysCharSet = ['+', ' ', '.', '-', '(', ')'];
  NDigit: TSysCharSet = ['2'..'9'];
  XDigit: TSysCharSet = ['0'..'9'];
begin
  if FNumber.IsEmpty then
    Exit('');

  var s := FNumber;
  Result := '';
  for var c in s do
    if not CharInSet(c, Separators) then
      Result := Result + c;

  if (Length(Result) < 10) then
    Exit(''); {invalid length}

  if (Length(Result) > 11) then
    Exit(''); {invalid length}

  if Length(Result) = 11 then begin {area code present}
    if Result[1] <> '1' then
      Exit('') {invalid area code}
    else
      Result := Result.Substring(1); {remove area code}
  end;

  {Check area digits}
  if not CharInSet(Result[1], NDigit) then
    Exit('');
  if not CharInSet(Result[2], XDigit) then
    Exit('');
  if not CharInSet(Result[3], XDigit) then
    Exit('');

  {Check exchange digits}
  if not CharInSet(Result[4], NDigit) then
    Exit('');
  if not CharInSet(Result[5], XDigit) then
    Exit('');
  if not CharInSet(Result[6], XDigit) then
    Exit('');

  {Check subscriber digits}
  if not CharInSet(Result[7], XDigit) then
    Exit('');
  if not CharInSet(Result[8], XDigit) then
    Exit('');
  if not CharInSet(Result[9], XDigit) then
    Exit('');
  if not CharInSet(Result[10], XDigit) then
    Exit('');

end;

constructor TPhoneNumber.Create(Number: string);
begin
  FNumber := Number
end;

function TPhoneNumber.Area: string;
begin
  Result := Clean.Substring(0, 3);
end;

function TPhoneNumber.Exchange: string;
begin
  Result := Clean.Substring(3, 3);
end;

function TPhoneNumber.ToString: string;
begin
  Result := '(' + Area + ')' + ' ' + Exchange + '-' + Clean.Substring(6,4);
end;

end.



Tags:

No tags generated

@ErikSchierboom
Copy link
Member Author

Exercise: binary-search

Code

unit uBinarySearch;

interface

type
  BinarySearch = class
  strict private
    class function SearchHelper(input: TArray<Integer>; target: integer; minIndex: integer; maxIndex: integer): integer; static;
  public
    class function Search(input: TArray<Integer>; target: integer): integer; static;
  end;

implementation

class function BinarySearch.SearchHelper(input: TArray<Integer>; target: Integer; minIndex: Integer; maxIndex: Integer): integer;
var middleIndex: integer;
begin
  if minIndex > maxIndex then
    result := -1
  else
  begin
    middleIndex := (minIndex + maxIndex) div 2;
    if (input[middleIndex] = target) then
      result := middleIndex
    else
      if input[middleIndex] > target then
        result := SearchHelper(input, target, minIndex, middleIndex - 1)
      else
        result := SearchHelper(input, target, middleIndex + 1, maxIndex);
  end;
end;

class function BinarySearch.Search(input: TArray<Integer>; target: integer): integer;
begin
  result := SearchHelper(input, target, Low(input), High(input));
end;

end.

Tags:

construct:add
construct:class
construct:divide
construct:if
construct:implicit-conversion
construct:indexer
construct:invocation
construct:method
construct:number
construct:parameter
construct:recursion
construct:subtract
construct:then
construct:unit
construct:variable
construct:visibility-modifiers
paradigm:object-oriented
technique:recursion

@ErikSchierboom
Copy link
Member Author

Exercise: allergies

Code

unit uAllergies;

interface
uses System.Generics.Collections;

type
   IAllergies = interface(IInvokable)
   ['{0A1F6B96-4CDC-4D7C-BE50-6B466CA27EB0}']
     function GetAllergyList: TList<string>;
     function AllergicTo(allergy: string): Boolean;
     property IList: TList<string> read GetAllergyList;
   end;

   TAllergies = class(TInterfacedObject, IAllergies)
   private
     fScore: integer;
     fAvailableAllergies: TDictionary<string, integer>;
     function IsInAllergyScore(allergyvalue: integer): Boolean;
     function GetAllergyList: TList<string>;
   public
     constructor create(aScore: integer);
     destructor destroy; override;
     function AllergicTo(allergy: string): Boolean;
     property IList: TList<string> read GetAllergyList;
   end;

implementation

constructor TAllergies.create(aScore: Integer);
begin
  inherited create;
  fScore := aScore;
  fAvailableAllergies := TDictionary<string, integer>.create;
  with fAvailableAllergies do
  begin
    Add('eggs', 1);
    Add('peanuts', 2);
    Add('shellfish', 4);
    Add('strawberries', 8);
    Add('tomatoes', 16);
    Add('chocolate', 32);
    Add('pollen', 64);
    Add('cats', 128);
  end; //with
end;

destructor TAllergies.destroy;
begin
  if assigned(fAvailableAllergies) then
    fAvailableAllergies.DisposeOf;
  inherited Destroy;
end;

function TAllergies.IsInAllergyScore(allergyvalue: Integer): Boolean;
begin
  result := (fScore and allergyvalue) = allergyvalue;
end;

function TAllergies.GetAllergyList: TList<string>;
var aPair: TPair<string, integer>;
begin
   result := TList<string>.create;
   for aPair in fAvailableAllergies do
   begin
     if IsInAllergyScore(aPair.Value) then
       result.Add(aPair.Key);
   end;
end;

function TAllergies.AllergicTo(allergy: string): Boolean;
begin
  result := IsInAllergyScore(fAvailableAllergies[allergy]);
end;

end.

Tags:

construct:assignment
construct:attribute
construct:boolean
construct:class
construct:comment
construct:constructor
construct:dictionary
construct:destructor
construct:do-loop
construct:for-loop
construct:function
construct:if
construct:implicit-conversion
construct:indexer
construct:interface
construct:invocation
construct:lambda
construct:logical-and
construct:method
construct:method-overloading
construct:nested-scope
construct:override
construct:parameter
construct:property
construct:readme
construct:read-only
construct:string
construct:then
construct:throw
construct:using-directive
construct:variable
construct:visibility-modifiers
paradigm:functional
paradigm:imperative
paradigm:object-oriented
technique:boolean-logic
technique:exceptions
technique:higher-order-functions
technique:inheritance
technique:looping
uses:TDictionary<TKey,TValue>

@ErikSchierboom
Copy link
Member Author

Exercise: roman-numerals

Code

unit uRomanNumerals;

interface

uses System.Sysutils;

type
  arabicNumeral = record helper for integer
    function ToRoman : string;
  end;

implementation

{ arabicNumeral }

function arabicNumeral.ToRoman: string;

  function Calc(AInp: integer) : string;
  begin
    case AInp of
      1000..3999 : Result := 'M' + Calc(AInp - 1000);
      900 .. 999 : Result := 'CM' + Calc(AInp - 900);
      500 .. 899 : Result := 'D' + Calc(AInp - 500);
      400 .. 499 : Result := 'CD' + Calc(AInp - 400);
      100 .. 399 : Result := 'C' + Calc(AInp - 100);
       90 ..  99 : Result := 'XC' + Calc(AInp - 90);
       50 ..  89 : Result := 'L' + Calc(AInp - 50);
       40 ..  49 : Result := 'XL' + Calc(AInp - 40);
       10 ..  39 : Result := 'X' + Calc(AInp - 10);
               9 : Result := 'IX' + Calc(AInp - 9);
        5 ..   8 : Result := 'V' + Calc(AInp - 5);
               4 : Result := 'IV' + Calc(AInp - 4);
        1 ..   3 : Result := 'I' + Calc(AInp - 1);
    end;
  end;

begin
  if (self > 3999) or (Self < 1)  then
    exit ('');
  Result := Calc(Self);
end;



end.

Tags:

construct:add
construct:assignment
construct:case
construct:comment
construct:constructor
construct:exit
construct:function
construct:helper
construct:if
construct:implicit-conversion
construct:invocation
construct:logical-or
construct:method
construct:or
construct:parameter
construct:record
construct:recursive-call
construct:string
construct:subtract
construct:subtype
construct:type-conversion
construct:unit
construct:uses
construct:visibility-modifiers
paradigm:imperative
paradigm:object-oriented
technique:boolean-logic
technique:recursion

@ErikSchierboom
Copy link
Member Author

Exercise: bowling

Code

unit uBowling;

interface

type
   IBowlingGame = interface(IInvokable)
   ['{D4A292B6-BC15-48ED-AE04-2D34759017CB}']
     function Roll(aPins: integer): Boolean;
     function Score: integer;
   end;

   function NewBowlingGame: IBowlingGame;

implementation
uses System.SysUtils, System.Math, System.Generics.Collections;

type
   TBowlingGame = class(TInterfacedObject, IBowlingGame)
   private
     var
       fRolls: TList<integer>;
     const
       fNumberOfFrames = 10;
       fMaximumFrameScore = 10;
     function IsStrike(aFrameIndex: integer): Boolean;
     function IsSpare(aFrameIndex: integer): Boolean;
     function StrikeBonus(aFrameIndex: integer): integer;
     function SpareBonus(aFrameIndex: integer): integer;
     function SumOfPinsInFrame(aFrameIndex: integer): integer;
     function CorrectNumberOfRolls(aFrameIndex: integer): boolean;
     function ValidInput(aPin: integer): Boolean;
   public
     constructor create;
     function Score: integer;
     function Roll(aPins: integer): Boolean;
   end;

function NewBowlingGame: IBowlingGame;
begin
  result := TBowlingGame.create;
end;

constructor TBowlingGame.create;
begin
  fRolls := TList<integer>.Create;
end;

function TBowlingGame.Roll(aPins: Integer): Boolean;
begin
  if not ValidInput(aPins) then
    exit(false);

  fRolls.Add(aPins);
  result := true;
end;

function TBowlingGame.Score: integer;
var lFrameIndex: integer;
    i: integer;
    lScore: integer;
    lStrikeBonus: integer;
    lFrameScore: integer;
begin
  lScore := 0;
  lFrameIndex := 0;
  try
    if (fRolls.Count < 12) or (fRolls.Count > 21) then
      raise EArgumentException.Create('Not a proper game');
    for i := 1 to fNumberOfFrames do
    begin
      if fRolls.Count <= lFrameIndex then
        raise EArgumentException.Create('Not a proper game');

      if IsStrike(lFrameIndex) then
      begin
        if (fRolls.Count <= lFrameIndex + 2) then
          raise EArgumentException.Create('Not a proper game');

        lStrikeBonus := StrikeBonus(lFrameIndex);
        if (lStrikeBonus > fMaximumFrameScore) and not IsStrike(lFrameIndex + 1) or (lStrikeBonus > 20) then
          raise EArgumentException.Create('Not a proper game');

        lScore := lScore + 10 + lStrikeBonus;
        inc(lFrameIndex, ifthen(i = fNumberOfFrames, 3, 1));
      end
      else
      if IsSpare(lFrameIndex) then
      begin
        if (fRolls.Count <= lFrameIndex + 2) then
          raise EArgumentException.Create('Not a proper game');

        lScore := lScore + 10 + SpareBonus(lFrameIndex);
        inc(lFrameIndex, ifthen(i = fNumberOfFrames, 3, 2));
      end
      else
      begin
        lFrameScore := SumOfPinsInFrame(lFrameIndex);
        if (lFrameScore < 0) or (lFrameScore > 10) then
          raise EArgumentException.Create('Not a proper game');

        lScore := lScore + lFrameScore;
        inc(lFrameIndex, 2);
      end;
    end;
    result := ifthen(CorrectNumberOfRolls(lFrameIndex), lScore, -1);
  except
    result := -1;
  end;
end;

function TBowlingGame.IsStrike(aFrameIndex: Integer): Boolean;
begin
  result := fRolls[aFrameIndex] = fMaximumFrameScore;
end;

function TBowlingGame.IsSpare(aFrameIndex: Integer): Boolean;
begin
  result := fRolls[aFrameIndex] + fRolls[aFrameIndex + 1] = FMaximumFrameScore;
end;

function TBowlingGame.StrikeBonus(aFrameIndex: Integer): integer;
begin
  result := fRolls[aFrameIndex + 1] + fRolls[aFrameIndex + 2];
end;

function TBowlingGame.SpareBonus(aFrameIndex: Integer): integer;
begin
  result := fRolls[aFrameIndex + 2];
end;

function TBowlingGame.SumOfPinsInFrame(aFrameIndex: Integer): integer;
begin
  result := fRolls[aFrameIndex] + fRolls[aFrameIndex + 1];
end;

function TBowlingGame.ValidInput(aPin: integer): Boolean;
begin
  result := true;
  if (fRolls.Count >= 21) or (aPin < 0) or (aPin > 10) or
      ((fRolls.Count + 1) mod 2 = 0) and (fRolls[fRolls.Count - 1] <> 10) and ((fRolls[fRolls.Count - 1] + aPin) > 10) then
    exit(false);

  if (fRolls.Count = 20) then
  begin
    if (fRolls[18] <> 10) and (fRolls[18] + fRolls[19] <> 10) then
      exit(false);

    if (aPin = 10) and ((fRolls[18] <> 10) or (fRolls[19] <> 10) or (fRolls[19] + aPin > 10) and (fRolls[19] + aPin <> 20)) and
        (fRolls[18] + fRolls[19] <> 10) then
      exit(false);

    if (aPin <> 10) and (fRolls[19] + aPin > 10) and (fRolls[19] <> 10) then
      exit(false);
  end;
end;

function TBowlingGame.CorrectNumberOfRolls(aFrameIndex: Integer): boolean;
begin
  result := aFrameIndex = fRolls.Count;
end;

end.

Tags:

construct:add
construct:boolean
construct:class
construct:const
construct:constructor
construct:exit
construct:field
construct:for-loop
construct:function
construct:if
construct:implementation
construct:indexer
construct:integer
construct:interface
construct:interval
construct:invocation
construct:lambda
construct:list
construct:logical-and
construct:logical-or
construct:method
construct:nested-function
construct:number
construct:parameter
construct:subtract
construct:try
construct:uses
construct:variable
construct:visibility-modifiers
paradigm:functional
paradigm:object-oriented
technique:boolean-logic
technique:exceptions
technique:higher-order-functions
technique:looping
uses:List<T>

@ErikSchierboom
Copy link
Member Author

Exercise: pig-latin

Code

unit uPigLatin;

interface

type
  TPigLatin = class
  public
    class function Translate(Phrase: string): string;
  private
    class function Convert(Word: string): string;
  end;

implementation

uses
  SysUtils;

{ TPigLatin }

const
  Letter = ['a'..'z'];
  Vowel = ['a', 'e', 'i', 'o', 'u'];
  Consonant = Letter - Vowel;
  Vowelish = ['x', 'y'];

class function TPigLatin.Convert(Word: string): string;
const
  PigTail = 'ay';
var
  Int: Integer;
  Chr, PrvChr: Char;
begin
  Int := 1;
  Chr := Word[Int];
  if CharInSet(Chr, Vowel)
  or (CharInSet(Chr, Vowelish)
    and CharInSet(Word[2], Consonant))
  then
    Exit(Word + PigTail);

  Result := Word;
  while not CharInSet(Chr, Vowel)
  or (CharInSet(PrvChr, ['q']) and CharInSet(Chr, ['u']))
  do begin

    if (CharInSet(PrvChr, Consonant - ['y']) and CharInSet(Chr, ['y'])) then
      Break;

    Result := Result.Replace(Chr, '', [rfIgnoreCase]);
    Result := Result + Chr;

    PrvChr := Chr;
    Inc(Int);
    Chr := Word[Int];

  end;

  Result := Result + PigTail;

end;

class function TPigLatin.Translate(Phrase: string): string;
var
  Word: string;
  Translation: array of string;
begin
  Translation := [];
  for Word in Phrase.Split([' ']) do
    Translation := Translation + [Convert(Word)];

  Result := string.Join(' ', Translation);
end;

end.

Tags:

construct:add
construct:and
construct:assignment
construct:boolean
construct:break
construct:char
construct:class
construct:comment
construct:const
construct:constructor
construct:do
construct:exit
construct:field
construct:for-loop
construct:function
construct:if
construct:implicit-conversion
construct:indexer
construct:invocation
construct:loop
construct:method
construct:nested-loop
construct:or
construct:parameter
construct:set
construct:string
construct:subtract
construct:then
construct:throw
construct:try
construct:unit
construct:uses
construct:variable
construct:visibility-modifiers
construct:while-loop
paradigm:imperative
paradigm:object-oriented
technique:exceptions
technique:looping
technique:recursion
uses:SysUtils

@ErikSchierboom
Copy link
Member Author

Exercise: book-store

Code

unit uBookStore;

interface

type
  IBasket = interface(IInvokable)
  ['{22B4BAF3-88E6-456D-9DE5-F6BAC743A655}']
    function Total:extended;
  end;

function NewBasket(aBasket: TArray<Integer>): IBasket;

implementation
uses System.SysUtils, System.Generics.collections, System.Math;

const
  seriesBooks = '12345';
  cNumberOfBooks = 5;
  cDiscounts: array[1..cNumberOfBooks] of extended = (1.00,
                                                      0.95,
                                                      0.90,
                                                      0.80,
                                                      0.75);

type
  TBasket = class(TInterfacedObject, IBasket)
  private
    fSingleBookPrice: extended;
    fBasket: string;
    fIntList: TList<integer>;
    class function Head(inStr: string): string; static;
    class function Tail(inStr: string): string; static;
    class function ConvertIntArrayToString(const aIntArray: TArray<Integer>): string; static;
    function DiscountPercentage(inStr : string): extended;
    function GroupBasket:TArray<String>;
    function NumberOfDifferentBooks(inStr : string):integer;
  public
    function Total:extended;
    constructor Create(aBasket: TArray<Integer>);
  end;

function NewBasket(aBasket: TArray<Integer>): IBasket;
begin
  result := TBasket.Create(aBasket);
end;

class function TBasket.Head(inStr : string):string;
begin
  result := inStr.Remove(1);
end;

class function TBasket.Tail(inStr : string):string;
begin
  result := inStr.Remove(0,1);
end;

class function TBasket.ConvertIntArrayToString(const aIntArray: TArray<Integer>): string;
var arrayItem: integer;
begin
  result := '';
  if length(aIntArray) > 0 then
    for arrayItem in aIntArray do
      result := result + arrayItem.ToString;
end;

constructor TBasket.Create(aBasket: TArray<Integer>);
begin
  fSingleBookPrice := 8;
  fIntList := TList<integer>.Create;
  fIntList.AddRange(aBasket);
  fIntList.Sort;
  fBasket := ConvertIntArrayToString(aBasket);
end;

function TBasket.GroupBasket:TArray<String>;
var lStrArray: TArray<String>;
    wrkBasket: string;
    tmpStr   : string;
    thisBook : string;
    Index    : integer;
    StrCount : integer;
begin
  wrkBasket := fBasket;
  StrCount := 1;
  SetLength(lStrArray,StrCount);
  thisBook := Head(wrkBasket);
  while wrkBasket.Length > 0 do
  begin
    Index := 0;
    repeat
      tmpStr := lStrArray[Index];
      if thisBook.Length > 0 then
      begin
        if not tmpStr.Contains(thisBook) then
        begin
          tmpStr := tmpStr + thisBook;
          lStrArray[Index] := tmpStr;
          wrkBasket := Tail(wrkBasket);
          thisBook := Head(wrkBasket);
        end
        else
        if (Index = StrCount - 1) then
        begin
          inc(StrCount);
          SetLength(lStrArray,StrCount);
        end;
        inc(Index);
      end;
    until (Index = StrCount) or wrkBasket.IsEmpty;
  end;
  result := lStrArray;
end;

function TBasket.Total:extended;
var
    subBaskets    : TArray<String>;
    subResult     : array[0..1] of extended;
    lSortedBasket : TArray<integer>;

    function computeTotal: extended;
    var wrkSubBasket: string;
        totalBooks  : integer;
        subTotal    : extended;
    begin
      result := 0;
      for wrkSubBasket in subBaskets do
      begin
        totalBooks := wrkSubBasket.Length;
        subTotal := totalBooks * (fSingleBookPrice * DiscountPercentage(wrkSubBasket));
        Result := Result + subTotal;
      end;
    end;

begin
  fillchar(subResult, sizeof(extended), #0);

  subBaskets := GroupBasket;
  subResult[0] := computeTotal;

  lSortedBasket := fIntList.ToArray;
  fBasket := ConvertIntArrayToString(lSortedBasket);
  subBaskets := GroupBasket;
  subResult[1] := computeTotal;

  result := min(subResult[0], subResult[1]);
end;

function TBasket.DiscountPercentage(inStr : string):extended;
var numDiffBooks: integer;
begin
  numDiffBooks := NumberOfDifferentBooks(inStr);
  result := CDiscounts[numDiffBooks];
end;

function TBasket.NumberOfDifferentBooks(inStr : string):integer;
var Book: char;
begin
  result := 0;
  for Book in seriesBooks do
    if inStr.Contains(Book) then
      inc(result);
end;

end.

Tags:

construct:assignment
construct:attribute
construct:char
construct:class
construct:const
construct:constructor
construct:do-loop
construct:double
construct:enum
construct:field
construct:for-loop
construct:if
construct:implicit-conversion
construct:indexer
construct:interface
construct:invocation
construct:lambda
construct:local-function
construct:loop
construct:method
construct:multiply
construct:number
construct:parameter
construct:property
construct:repeat-loop
construct:setlength
construct:string
construct:subtract
construct:then
construct:throw
construct:type-conversion
construct:unit
construct:uses
construct:variable
construct:visibility-modifiers
paradigm:functional
paradigm:imperative
paradigm:object-oriented
technique:exceptions
technique:higher-order-functions
technique:inheritance
technique:looping
technique:recursion

@ErikSchierboom
Copy link
Member Author

Exercise: poker

Code

unit uPoker;

interface

uses System.Generics.Collections, System.Generics.Defaults;

type

  TPokerComparer = class(TComparer<string>)
    function Compare(const ALeft, ARight : string) : integer; override;
  end;

  TArray = class(System.Generics.Collections.TArray)
  public
    class function IndexOf<T>(const AArr: array of T; const AItem: T): Integer;
  end;

  Poker = class
  private
    class function CalcHand(AHand : TArray<string>) : single;
  public
    class function BestHands(inputHands : TList<string>) : TList<string>; static;
  end;

implementation

{ Poker }

uses System.SysUtils, System.Math;

const RangValues : TArray<string> = ['2', '3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K', 'A'];

class function Poker.CalcHand(AHand : TArray<string>) : single;
var Suites, Rangs: TDictionary<string, integer>;
    LHandList : TList<string>;
  card: string;

  procedure AddToDic(ADic : TDictionary<string, integer>; Aval : string);
  begin
    if ADic.ContainsKey(Aval) then
      ADic.Items[Aval] := ADic.Items[Aval] + 1
    else
      ADic.Add(Aval, 1);
  end;

  function DoCalc : single;
  const SuiteValues : TArray<string> = ['S', 'D', 'H', 'C'];
  var C,S, Prev, Ind, Str, PN : integer;
    r: string;
    Val, CN : single;
    AceHigh, T: Boolean;

  begin
    Result := 0;
    CN := 1;
    PN := 0;
    Prev := -1;
    Str := 0;
    AceHigh := false;
    T := false;
    for r in LHandList do
    begin
      C := Rangs.Items[r.Remove(r.Length - 1)];
      Ind := TArray.IndexOf(RangValues, r.Remove(r.Length - 1));
      Val := Ind / 100;
      case C of
        1:begin
            if (Prev = -1) or (Prev - 1 = ind) or ((prev = 12) and (ind = 3))  then
            begin
              if ((prev = 12) and (ind = 3)) then
                AceHigh := true;
              Prev := ind;
              inc(Str);
            end;

            Result := Result + Val * CN;
            CN := CN / 13;
        end;
        2:begin

          Result := Result + Val * 3 + PN div 2;
          PN := PN + 1;
        end;

        3:begin
          T := true;
          Result := Result + Val * 3 + 4;
        end;

        4: Result := Result + Val + 8;
      end;
    end;

    if Str = 5 then
    begin
      if AceHigh then
        Result := Result - 12/100;
      Result := Result + 14;
    end;

    If Suites.Items[r[r.Length]] = 5 then
      Result := Result + 15;

    if t and (PN > 0)  then
      Result := Result + 16;

    if (Str = 5) and (Suites.Items[r[r.Length]] = 5) then
      Result := Result + 4;
  end;


begin
  Rangs := TDictionary<string, integer>.Create;
  Suites := TDictionary<string, integer>.Create;

  LHandList := TList<string>.Create(TPokerComparer.Create);
  LHandList.AddRange(AHand);
  LHandList.Sort;
  LHandList.Reverse;

  for card in LHandList do
  begin
    AddToDic(Rangs, card.Remove(card.Length - 1));
    AddToDic(Suites, card[card.Length]);
  end;

  Result := DoCalc;
  Rangs.DisposeOf;
  Suites.DisposeOf
end;

class function Poker.BestHands(inputHands: TList<string>): TList<string>;
var
  H: string;
  BestHand, Act : single;
  Cards : TArray<string>;

begin
  Result := TList<string>.Create;
  BestHand := 0;
  for H in inputHands do
  begin
    Cards := H.split([' ']);
    Act := CalcHand(Cards);
    if BestHand = Act  then
      Result.Add(H);
    if BestHand < Act then
    begin
      BestHand := Act;
      Result.Clear;
      Result.Add(H);
    end;
  end;
end;

{ TArray }

class function TArray.IndexOf<T>(const AArr: array of T;
  const AItem: T): Integer;
var
  Ind: Integer;
begin
  Result := -1;
  for Ind := 0 to high(AArr) do
    if TEqualityComparer<T>.Default.Equals(AArr[Ind], AItem) then
      exit(Ind);
end;

{ TCardsComparer }

function TPokerComparer.Compare(const ALeft, ARight: string): integer;
var
  Lval, RVal : integer;

begin
  LVal := TArray.IndexOf(RangValues, ALeft.Remove(ALeft.Length - 1));
  RVal := TArray.IndexOf(RangValues, ARight.Remove(ARight.Length - 1));
  Result := Sign(Lval - RVal);
end;

end.

Tags:

construct:add
construct:and
construct:array
construct:assignment
construct:boolean
construct:case
construct:class
construct:comment
construct:const
construct:constructor
construct:divide
construct:double
construct:exit
construct:field
construct:floating-point-number
construct:for-loop
construct:function
construct:generic-type
construct:if
construct:implicit-conversion
construct:indexer
construct:initializer
construct:integral-number
construct:invocation
construct:lambda
construct:local-function
construct:method
construct:multiply
construct:nested-function
construct:number
construct:or
construct:override
construct:parameter
construct:property
construct:string
construct:subtract
construct:then
construct:throw
construct:unit
construct:uses
construct:variable
construct:visibility-modifiers
paradigm:functional
paradigm:imperative
paradigm:object-oriented
technique:boolean-logic
technique:exceptions
technique:higher-order-functions
technique:inheritance
technique:looping

@ErikSchierboom
Copy link
Member Author

Exercise: pangram

Code

unit uPangram;

interface

  function IsPangram(AInput : string) : boolean;

implementation

uses System.SysUtils, System.Generics.Collections, System.Character;

function IsPangram(AInput : string) : boolean;
var LtrCnt : TList<char>;
  c: char;
begin
  LtrCnt := TList<char>.Create;
  try
    for c in AInput.ToUpperInvariant do
      if c.IsLetter and not LtrCnt.Contains(c) then
        LtrCnt.Add(c);
    Result := LtrCnt.Count = 26;

  finally
    LtrCnt.Free;
  end;

end;

end.

Tags:

construct:assignment
construct:begin
construct:boolean
construct:char
construct:class
construct:constructor
construct:do-loop
construct:for-loop
construct:function
construct:generic-type
construct:if
construct:implicit-conversion
construct:invocation
construct:logical-and
construct:method
construct:number
construct:parameter
construct:set
construct:string
construct:try
construct:uses
construct:variable
construct:visibility-modifiers
paradigm:imperative
paradigm:object-oriented
technique:boolean-logic
technique:looping
uses:TList<T>

@ErikSchierboom
Copy link
Member Author

Exercise: robot-simulator

Code

unit uRobotSimulator;

interface

type
  TDirection = (North, East, South, West);

  TCoordinate = record
    x, y: Integer;
    constructor Create(x, y: Integer);
    class operator Implicit(Coordinate: TCoordinate): string;
  end;

  TRobotSimulator = class
    Coordinate: TCoordinate;
    Direction: TDirection;
    constructor Create(Direction: TDirection; Coordinate: TCoordinate);
    procedure Move(CommandCodes: string);
  end;

implementation

uses
  SysUtils;

{ TCoordinate }

constructor TCoordinate.Create(x, y: Integer);
begin
  Self.x := x;
  Self.y := y;
end;

class operator TCoordinate.Implicit(Coordinate: TCoordinate): string;
begin
  Result := IntToStr(Coordinate.x) + IntToStr(Coordinate.y);
end;

{ TRobotSimulator }

constructor TRobotSimulator.Create(
  Direction: TDirection;
  Coordinate: TCoordinate);
begin
  Self.Coordinate := Coordinate;
  Self.Direction := Direction;
end;

type
  Command = class
  const
    Advance: Char = 'A';
    TurnLeft: Char = 'L';
    TurnRight: Char = 'R';
  end;

procedure TRobotSimulator.Move(CommandCodes: string);

  procedure Move(CommandCode: Char);
  begin
    if CommandCode = Command.Advance then begin
      case Direction of
        North: Coordinate.y := Coordinate.y + 1;
        East: Coordinate.x := Coordinate.x + 1;
        South: Coordinate.y := Coordinate.y - 1;
        West: Coordinate.x := Coordinate.x - 1;
      end;
    end else if CommandCode = Command.TurnLeft then begin
      Direction := TDirection((Ord(Direction) + 3) mod 4);
    end else if CommandCode = Command.TurnRight then begin
      Direction := TDirection((Ord(Direction) + 1) mod 4);
    end;
  end;

begin
  for var CommandCode in CommandCodes do
    Move(CommandCode);
end;

end.

Tags:

construct:add
construct:assignment
construct:begin
construct:case
construct:char
construct:class
construct:comment
construct:const
construct:constructor
construct:for-loop
construct:if
construct:implicit-conversion
construct:integral-number
construct:invocation
construct:method
construct:method-overloading
construct:nested-routine
construct:number
construct:parameter
construct:record
construct:string
construct:subtract
construct:then
construct:type-conversion
construct:uses
construct:variable
construct:visibility-modifiers
paradigm:imperative
paradigm:object-oriented
technique:looping

@ErikSchierboom
Copy link
Member Author

Exercise: matrix

Code

unit uMatrix;

interface

uses
  System.Classes, System.Generics.Collections, System.SysUtils;

type
  TMatrix = class
    private
      FMatrix: TArray<TArray<Integer>>;
      FMatrixString: String;
      FColumns: Integer;
      FRows: Integer;
      FRow: Integer;

      procedure ExtractRows;
      procedure ExtractElements;

    public
      constructor Create(MatrixString: String);

      function Column(ColumnNo: Integer): TArray<Integer>;
      function Row(RowNo: Integer): TArray<Integer>;

  end;

implementation

{ TMatrix }

function TMatrix.Column(ColumnNo: Integer): TArray<Integer>;
var
  Row: Integer;
begin
  // Set the length of the result TArray to the number of rows
  SetLength(Result, FRows);

  // If ColumnNo is outside the range of columms in the matrix (when scaled)
  // it can't be processed, so quit.
  if (ColumnNo < 1) or (ColumnNo > Length(FMatrix[0])) then
    Exit;

  // Now, we need to cycle through the rows
  for Row := 0 to FRows - 1 do
    // adding the element in the specified column to the result.
    Result[Row] := FMatrix[Row, ColumnNo - 1];
end;

constructor TMatrix.Create(MatrixString: String);
begin
  // Start of by saving the passed string
  FMatrixString := Trim(MatrixString);

  // Then extract the elements from the string and insert them into the matrix
  ExtractRows;
end;

procedure TMatrix.ExtractElements;
var
  Elements: TList<Integer>;
  Position: Integer;
  ElementString: String;
  Column: Integer;
begin
  // Create the list of elements found
  Elements := TList<Integer>.Create;

  // Start with no columns in the row - we'll figure out the size later
  FColumns := 0;

  // We look for elements until the string is empty
  while Length(FMatrixString) > 0 do
    begin
      // Look for a space between elements
      Position := Pos(' ', FMatrixString);

      if Position = 0 then
        // There was no space, so this is the last element
        begin
          // Save it's value
          ElementString := FMatrixString;

          // and empty the string, signaling that we're done here.
          FMatrixString := '';
        end
      else
        // We found the gap between two elements
        begin
          // Save the element's value
          ElementString := Copy(FMatrixString, 1, Position - 1);

          // Remove the element from the string
          Delete(FMatrixString, 1, Position);

          // Make sute there aren't any spaces in the front of the string
          FMatrixString := Trim(FMatrixString);
        end;

        // Add the element to the string. In the real world, I'd be checking
        // to make sure the element was an integer, but I'm omitting it for
        // this exercixe.
        Elements.Add(StrToInt(ElementString));

        // Add to the column count
        Inc(FColumns);
    end;

  // Set the matrix row's size
  SetLength(FMatrix[FRow], FColumns);

  // Now it's time to put the elements into the matrix row
  for Column := 0 to FColumns - 1 do
    begin
      // Set the cell to the element value
      FMatrix[FRow, Column] := Elements[0];

      // and remove the element value from the list.
      Elements.Delete(0);
    end;

  // Clean up
  Elements.Destroy;
end;

procedure TMatrix.ExtractRows;
var
  Rows: TStringList;
  Position: Integer;
  RowString: String;
  Index: Integer;
begin
  // Create the list of rows found
  Rows := TStringList.Create;

  // Start with no rows in the matrix - we'll figure out the size later
  FRows := 0;

  // We look for rows until the string is empty
  while Length(FMatrixString) > 0 do
    begin
      // Look for the new-line sequence between rows
      Position := Pos('\', FMatrixString);

      // There was no new-line sequence, so this is the last row
      if Position = 0 then
        begin
          // Save it's value
          RowString := FMatrixString;

          // and empty the string, signaling that we're done here.
          FMatrixString := '';
        end
      else
        // There was no new-line sequence, so this is the last element
        begin
          // Save the row text for later processing
          RowString := Copy(FMatrixString, 1, Position - 1);

          // and remove it from the string.
          Delete(FMatrixString, 1, Position + 1);

          // Make sute there aren't any spaces in the front of the string
          FMatrixString := Trim(FMatrixString);
        end;

        // Add the row text to the list of rows
        Rows.Add(RowString);

        // Increment the row count
        Inc(FRows);
    end;

  // Now that we know how many rows are in the matrix, set it's length
  SetLength(FMatrix, FRows);

  // Now let's break the rows into columns, and put the cell values in the matrix
  for Index := 0 to Rows.Count - 1 do
    begin
      // Set which row is being processed
      FRow := Index;

      // Get it's string
      FMatrixString := Rows[Index];

      // and put the elements into the matrix.
      ExtractElements;
    end;

  // Clean up
  Rows.Destroy;
end;

function TMatrix.Row(RowNo: Integer): TArray<Integer>;
var
  ColumnNo: Integer;
begin
  // Set the length of the result TArray to the number of columns
  SetLength(Result, FColumns);

  // If RowNo is outside the range of rows in the matrix (when scaled)
  // it can't be processed, so quit.
  if (RowNo < 1) or (RowNo > Length(FMatrix)) then
    Exit;

  // Now, we need to cycle through the columns
  for ColumnNo := 0 to FColumns - 1 do
    // adding the element in the specified row to the result.
    Result[ColumnNo] := FMatrix[RowNo - 1, ColumnNo];
end;

end.

Tags:

construct:add
construct:assignment
construct:boolean
construct:class
construct:comment
construct:constructor
construct:exit
construct:for-loop
construct:function
construct:if
construct:indexed-access
construct:integer
construct:invocation
construct:lambda
construct:logical-or
construct:method
construct:or
construct:parameter
construct:procedure
construct:string
construct:subtract
construct:to
construct:type
construct:unit
construct:uses
construct:variable
construct:visibility-modifiers
construct:while-loop
paradigm:functional
paradigm:imperative
paradigm:object-oriented
technique:boolean-logic
technique:higher-order-functions
technique:looping

@ErikSchierboom
Copy link
Member Author

Exercise: ocr-numbers

Code

unit uOcrNumbers;

interface

uses
  System.Generics.Collections, System.SysUtils;

const
  CharacterWidth = 3;

  CharacterHeight = 4;

type
  TOcrNumbers = class
    private
      //class var FNumbers: TArray<TArray<string>>;
      class var FNumbers: array[0..9, 0..CharacterHeight - 1] of string;

    public
      class function convert(Rows: TArray<string>): String;

      class procedure Initialize;

  end;

implementation

const
  Zero_1 = ' _ ';
  Zero_2 = '| |';
  Zero_3 = '|_|';

  One_1 = '   ';
  One_2 = '  |';
  One_3 = '  |';

  Two_1 = ' _ ';
  Two_2 = ' _|';
  Two_3 = '|_ ';

  Three_1 = ' _ ';
  Three_2 = ' _|';
  Three_3 = ' _|';

  Four_1 = '   ';
  Four_2 = '|_|';
  Four_3 = '  |';

  Five_1 = ' _ ';
  Five_2 = '|_ ';
  Five_3 = ' _|';

  Six_1 = ' _ ';
  Six_2 = '|_ ';
  Six_3 = '|_|';

  Seven_1 = ' _ ';
  Seven_2 = '  |';
  Seven_3 = '  |';

  Eight_1 = ' _ ';
  Eight_2 = '|_|';
  Eight_3 = '|_|';

  Nine_1 = ' _ ';
  Nine_2 = '|_|';
  Nine_3 = ' _|';

  EmptyRow = '   ';

{ TOcrNumbers }

class function TOcrNumbers.convert(Rows: TArray<string>): String;
var
  Digit: String;
  Number: Integer;
  Position: Integer;
  Row: Integer;
  RowLength: Integer;
  RowSet: Integer;
  TestString: String;
  ValidMatch: Boolean;
begin
  // Make sure the number of rows is valid, if it isn't raise an error
  if Length(Rows) mod 4 <> 0 then
    raise EArgumentException.Create('Number of input lines is not a multiple of four');

  // Make sure the number of columns is valid, if it isn't raise an error
  if Length(Rows[0]) mod 3 <> 0 then
    raise EArgumentException.Create('Number of input columns is not a multiple of three');

  // Make sure the patterns for all 10 digits are initialized
  Initialize;

  // Start with the first, possibly only, set of rows
  RowSet := 0;

  // Scan through the sets of four rows
  while RowSet < Length(Rows) do
    begin
      // Get the length of the current rowset
      RowLength := Length(Rows[RowSet]);

      // Start with the first character of the rowset
      Position := 1;

      // Scan through the current rowset
      while Position < RowLength do
        begin
          // Default the digit to the unknown value
          Digit := '?';

          // Check the digits in the pattern array against the current pattern
          for Number := 0 to 9 do
            begin
              // Assume it's a match
              ValidMatch := True;

              // Check each row of the number's pattern against the current pattern
              for Row := 0 to CharacterHeight - 1 do
                begin
                  // Do the rows of the patterns match?
                  if (FNumbers[Number, Row] <> Copy(Rows[RowSet + Row], Position, 3)) then
                    begin
                      // No, so we can stop checking this number
                      ValidMatch := False;

                      // and leave early
                      Break;
                    end;
                end;

              // Did the patterns match?
              if ValidMatch then
                begin
                  // Yes, so no we convert the numeric value of the number to it's text equivalent
                  Digit := Char(Ord('0') + Number);

                  // and leave early
                  break;
                end;
            end;

          // Add the current digit to the result string
          Result := Result + Digit;

          // Move to the next position in this rowser
          Inc(Position, CharacterWidth);
        end;

      // Done with this rowset, so let's move to the next
      Inc(RowSet, 4);

      // If we're done with this rowset, and it's not the last rowset,
      if RowSet < Length(Rows) then
        // add a comma as a seperator
        Result := Result + ',';
    end;
end;


class procedure TOcrNumbers.Initialize;
begin
  // Initialize the patterns for all 10 digits
  FNumbers[0, 0] := Zero_1;
  FNumbers[0, 1] := Zero_2;
  FNumbers[0, 2] := Zero_3;
  FNumbers[0, 3] := EmptyRow;

  FNumbers[1, 0] := One_1;
  FNumbers[1, 1] := One_2;
  FNumbers[1, 2] := One_3;
  FNumbers[1, 3] := EmptyRow;

  FNumbers[2, 0] := Two_1;
  FNumbers[2, 1] := Two_2;
  FNumbers[2, 2] := Two_3;
  FNumbers[2, 3] := EmptyRow;

  FNumbers[3, 0] := Three_1;
  FNumbers[3, 1] := Three_2;
  FNumbers[3, 2] := Three_3;
  FNumbers[3, 3] := EmptyRow;

  FNumbers[4, 0] := Four_1;
  FNumbers[4, 1] := Four_2;
  FNumbers[4, 2] := Four_3;
  FNumbers[4, 3] := EmptyRow;

  FNumbers[5, 0] := Five_1;
  FNumbers[5, 1] := Five_2;
  FNumbers[5, 2] := Five_3;
  FNumbers[5, 3] := EmptyRow;

  FNumbers[6, 0] := Six_1;
  FNumbers[6, 1] := Six_2;
  FNumbers[6, 2] := Six_3;
  FNumbers[6, 3] := EmptyRow;

  FNumbers[7, 0] := Seven_1;
  FNumbers[7, 1] := Seven_2;
  FNumbers[7, 2] := Seven_3;
  FNumbers[7, 3] := EmptyRow;

  FNumbers[8, 0] := Eight_1;
  FNumbers[8, 1] := Eight_2;
  FNumbers[8, 2] := Eight_3;
  FNumbers[8, 3] := EmptyRow;

  FNumbers[9, 0] := Nine_1;
  FNumbers[9, 1] := Nine_2;
  FNumbers[9, 2] := Nine_3;
  FNumbers[9, 3] := EmptyRow;
end;

end.

Tags:

No tags generated

@ErikSchierboom
Copy link
Member Author

Exercise: series

Code

unit uSeries;

interface

type
  TSlice = class
  private
    FStringOfDigits:string;
  public
    /// <summary> Initial string </summary>
    property StringOfDigits:string read FStringOfDigits;
    /// <summary> constructor of the class </summary>
    constructor Create(AValue: string);
    /// <summary> extract substrings </summary>
    function slices(const ALength:integer):TArray<string>;
  end;

implementation

uses
  System.Sysutils, System.Generics.Collections;

{ TSlice }

constructor TSlice.Create(AValue: string);
begin
  inherited Create;
  FStringOfDigits := AValue;
end;

function TSlice.slices(const ALength: integer): TArray<string>;
var
  i:Integer;
  l:TList<string>;
begin
  if (FStringOfDigits = string.Empty) then
    raise EArgumentException.Create('series cannot be empty');
  if (ALength < 0) then
    raise EArgumentOutOfRangeException.Create('slice length cannot be negative');
  if (ALength = 0) then
    raise EArgumentOutOfRangeException.Create('slice length cannot be zero');
  if ALength > Length(FStringOfDigits) then
    raise EArgumentOutOfRangeException.Create('slice length cannot be greater than series length');

  l := Tlist<string>.Create;
  for i := 1 to (Length(FStringOfDigits) - ALength + 1) do
    l.Add(Copy(FStringOfDigits, i, ALength));
  Result := l.ToArray;
  l.Free;
end;

end.

Tags:

No tags generated

@ErikSchierboom
Copy link
Member Author

Exercise: atbash-cipher

Code

unit uAtbashCipher;

interface

type
  TAtbashCipher = class
  const
    ALPHABET = 'abcdefghijklmnopqrstuvwxyz';
    NUMBERS = '0123456789';
  private
    class function EncodeDecode(AValue:string):string;
    class function AddSpaces(AValue:string):string;
  public
    class function Encode(AValue:string; op:integer=0):string;
    class function Decode(AValue:string):string;
  end;


implementation

uses
  System.Sysutils, System.StrUtils;

// same proc to Encode / Decode
class function TAtbashCipher.EncodeDecode(AValue:string):string;
var
  i, pos:integer;
  ch:char;
begin
  Result := string.Empty;
  // Prepare the input
  AValue := AValue.ToLower;
  AValue := AnsiReplaceText(AValue, ' ', '');

  for i := Low(AValue) to High(AValue) do begin
    // numbers => equal
    if (AnsiPos(AValue[i], NUMBERS) <> 0) then begin
      ch := AValue[i];
      Result := Result + ch;
    end
    // Letters => Translate
    else begin
      pos := AnsiPos(AValue[i], ALPHABET);
      if (pos <> 0) then begin
        ch := ALPHABET[Length(ALPHABET) - pos + 1];
        Result := Result + ch;
      end;
    end;
  end;
end;

// Add spaces (5 positions)
class function TAtbashCipher.AddSpaces(AValue: string): string;
var
  i:Integer;
begin
  for i := 0 to (Length(AValue) - 1) do begin
    if ((i MOD 5) = 0) and (i <> 0) then
      Result := Result + ' ';
    Result := Result + AValue[i+1];
  end;
end;

class function TAtbashCipher.Decode(AValue: string): string;
begin
  Result := EncodeDecode(AValue);
end;

class function TAtbashCipher.Encode(AValue:string; op:integer=0): string;
begin
  Result := AddSpaces(EncodeDecode(AValue));
end;

end.

Tags:

construct:add
construct:and
construct:assignment
construct:begin
construct:class
construct:comment
construct:const
construct:constructor
construct:for-loop
construct:function
construct:if
construct:implicit-conversion
construct:indexer
construct:integral-number
construct:invocation
construct:method
construct:number
construct:optional-parameter
construct:parameter
construct:string
construct:subtract
construct:to
construct:unit
construct:uses
construct:variable
construct:visibility-modifiers
paradigm:imperative
paradigm:object-oriented
technique:looping

@ErikSchierboom
Copy link
Member Author

Exercise: palindrome-products

Code

unit uPalindromeProducts;

interface

uses
  SysUtils;

type
  TPalindromeResult = TArray<TArray<TArray<Integer>>>;

  TPalindromeProduct = class
    class function Largest(RangeStart, RangeEnd: Integer): TPalindromeResult;
    class function Smallest(RangeStart, RangeEnd: Integer): TPalindromeResult;
  end;

implementation

uses
  StrUtils, Generics.Collections;

type
  TGoalFunc = function(Int, GoalInt: Integer): Boolean;

function Max(Int, GoalInt: Integer): Boolean;
begin
  Result := Int > GoalInt;
end;
function Min(Int, GoalInt: Integer): Boolean;
begin
  Result := Int < GoalInt;
end;

function PalindromeProduct(
  RangeStart, RangeEnd: Integer;
  GoalFunc: TGoalFunc; InitialGoal: Integer): TPalindromeResult;

  procedure ValidateRange(RangeStart, RangeEnd: Integer);
  begin
    if RangeStart > RangeEnd then
      raise EArgumentException.Create('min must be <= max');
  end;

  function IsPalindrome(Int: Integer): Boolean;
  begin
    Result := Int.ToString = ReverseString(Int.ToString);
  end;

var
  i, j, Product: Integer;
begin
  ValidateRange(RangeStart, RangeEnd);

  for i := RangeEnd downto RangeStart do begin
    for j := i downto RangeStart do begin
      Product := i * j;
      if IsPalindrome(Product) then
        if GoalFunc(Product, InitialGoal) then begin
          InitialGoal := Product;
          Result := [[[Product]], [[j, i]]];
        end else if (Product = InitialGoal) then begin
          Result := [[[Product]], Result[1] + [[j, i]]];
        end;
    end;
  end;
end;

{ TPalindromeProduct }

class function TPalindromeProduct.Largest(
  RangeStart, RangeEnd: Integer): TPalindromeResult;
begin
  Result := PalindromeProduct(RangeStart, RangeEnd, Max, 0);
end;

class function TPalindromeProduct.Smallest(
  RangeStart, RangeEnd: Integer): TPalindromeResult;
begin
  Result := PalindromeProduct(RangeStart, RangeEnd, Min, MaxInt);
end;

end.

Tags:

construct:add
construct:assignment
construct:boolean
construct:class
construct:comment
construct:constructor
construct:for-loop
construct:function
construct:if
construct:implicit-conversion
construct:indexed-access
construct:invocation
construct:lambda
construct:method
construct:multiply
construct:nested-type
construct:overload
construct:parameter
construct:raise
construct:string
construct:then
construct:throw
construct:type-conversion
construct:unit
construct:uses-directive
construct:variable
construct:visibility-modifiers
paradigm:functional
paradigm:imperative
paradigm:object-oriented
technique:exceptions
technique:higher-order-functions
technique:looping

@ErikSchierboom
Copy link
Member Author

Exercise: say

Code

unit uSay;

interface

function Say(const Anum: Int64): String;

implementation

uses
  SysUtils, Math;

const
  NUMBER_NAMES: array[0..9] of String = (
    'zero', 'one', 'two', 'three', 'four',
    'five', 'six', 'seven', 'eight', 'nine'
  );
  GROUP_UNITS: array[0..4] of String = (
    '', 'thousand', 'million', 'billion', 'trillion'
  );

function Say(const Anum: Int64): String;
var
  Numbers: array[0..9] of String;
  Head, Groups, i: Integer;
  Tail, Divisor: Int64;
begin
  if Anum < 0 then
    Result := 'negatif ' + Say(Anum)

  else if Anum < 10 then
    Result := NUMBER_NAMES[Anum]

  else if Anum < 100 then begin
    Head := Trunc(Anum / 10);
    Tail := Anum mod 10;

    for i := 0 to 9 do
      Numbers[i] := NUMBER_NAMES[i];
    Numbers[3] := 'thir';
    Numbers[5] := 'fif';
    Numbers[8] := 'eigh';

    if Head = 1 then begin
      case Tail of
        0: Result := 'ten';
        1: Result := 'eleven';
        2: Result := 'twelve';
        else Result := Numbers[Tail] + 'teen';
      end;
    end

    else begin
      Numbers[2] := 'twen';
      Numbers[4] := 'for';

      Result := Numbers[Head] + 'ty';
      if Tail > 0 then
        Result := Result + '-' + NUMBER_NAMES[Tail];
    end;
  end

  else if Anum < 1000 then begin
    Head := Trunc(Anum / 100);
    Tail := Anum mod 100;

    Result := NUMBER_NAMES[Head] + ' hundred';
    if Tail > 0 then
      Result := Result + ' ' + Say(Tail);
  end

  else begin
    Groups := Trunc(Log10(Anum)/3);
    if Groups > High(GROUP_UNITS) then
      raise ERangeError.Create('Number is beyond scope');

    Divisor := Trunc(Power(1000, Groups));
    Head := Trunc(Anum / Divisor);
    Tail := Anum mod Divisor;

    Result := Say(Head) + ' ' + GROUP_UNITS[Groups];
    if Tail > 0 then
      Result := Result + ' ' + Say(Tail);
  end;
end;

end.

Tags:

No tags generated

@ErikSchierboom
Copy link
Member Author

Exercise: rotational-cipher

Code

unit uRotationalCipher;

interface

uses
  System.SysUtils;

type
  RotationalCipher = class
    private
      class var FDistance: Integer;

      class function rotateLetter(Letter: Char): Char;
    public
      class function rotate(Phrase: String; Distance: Integer): String;

 end;

implementation

{ RotationalCipher }

class function RotationalCipher.rotate(Phrase: String; Distance: Integer): String;
var
  Index: Integer;
begin
  // Start with an empty string
  Result := '';

  // Save the distance
  FDistance := Distance;

  // Scan the phrase, rotating each individual character
  for Index := 1 to Length(Phrase) do
    Result := Result + rotateLetter(Phrase[Index]);
end;

class function RotationalCipher.rotateLetter(Letter: Char): Char;
var
  NewLetter: Integer;
begin
  // We're only rotating letters
  if not CharInSet(Letter, ['a'..'z', 'A'..'Z']) then
    begin
      // This isn't a letter, so we return it unchanged
      Result := Letter;
      exit;
    end;

  // Calculate the new letter
  NewLetter := Ord(Letter) + FDistance;

  // Was it originally lower case?
  if CharInSet(Letter, ['a'..'z']) then
    begin
      // Yes, so we need to allow for wrapping outside the range of lower case letters
      if NewLetter < Ord('a') then
        NewLetter := NewLetter + 26;

      if NewLetter > Ord('z') then
        NewLetter := NewLetter - 26;
    end
  else
    begin
      // Otherwise,  we need to allow for wrapping outside the range of upper case letters
      if NewLetter < Ord('A') then
        NewLetter := NewLetter + 26;

      if NewLetter > Ord('Z') then
        NewLetter := NewLetter - 26;
    end;

  // Return the new letter
  Result := Char(NewLetter);
end;

end.

Tags:

construct:add
construct:assignment
construct:boolean
construct:char
construct:class
construct:comment
construct:constructor
construct:exit
construct:for-loop
construct:function
construct:if
construct:implicit-conversion
construct:indexed-access
construct:integer
construct:integral-number
construct:invocation
construct:method
construct:number
construct:parameter
construct:string
construct:subtract
construct:then
construct:to
construct:type-conversion
construct:uses
construct:variable
construct:visibility-modifiers
paradigm:imperative
paradigm:object-oriented
technique:looping

@ErikSchierboom
Copy link
Member Author

Exercise: parallel-letter-frequency

Code

unit uParallelLetterFrequency;
{$define ParallelOperation}
interface
uses
  System.Generics.Collections;

type
  TParallelLetterFrequency = class
    class function Calculate(const aInputLetters: string): TDictionary<char, integer>;
  end;

implementation
uses
  System.SysUtils,
  System.Character,
  System.Threading;

{ ParallelLetterFrequency }

{$ifdef ParallelOperation}
class function TParallelLetterFrequency.Calculate(
  const aInputLetters: string): TDictionary<char, integer>;
begin
  var LowerCased := aInputLetters.ToLowerInvariant;
  var LetterTallies := TDictionary<char, integer>.Create;
  TParallel.For(low(LowerCased), High(LowerCased), procedure(i: Int64)
  begin
    var c := LowerCased[i];
    if c.IsLetter then
    begin
      TMonitor.Enter(LetterTallies);
      try
        if not LetterTallies.ContainsKey(c) then
          LetterTallies.AddOrSetValue(c, 0);
        var x := LetterTallies[c];
        inc(x);
        LetterTallies[c] := x;
      finally
        TMonitor.Exit(LetterTallies);
      end;
    end;
  end);
  result := LetterTallies;
end;

{$else}

class function TParallelLetterFrequency.Calculate(
  const aInputLetters: string): TDictionary<char, integer>;
begin
  var LowerCased := aInputLetters.ToLowerInvariant;
  var LetterTallies := TDictionary<char, integer>.Create;
  For var i := low(LowerCased) to High(LowerCased) do
  begin
    var c := LowerCased[i];
    if c.IsLetter then
    begin
      if not LetterTallies.ContainsKey(c) then
        LetterTallies.AddOrSetValue(c, 0);
      var x := LetterTallies[c];
      inc(x);
      LetterTallies[c] := x;
    end;
  end;
  result := LetterTallies;
end;
{$endif}

end.

Tags:

construct:assignment
construct:begin-end
construct:char
construct:class
construct:comment
construct:const
construct:dictionary
construct:define-directive
construct:for-loop
construct:function
construct:if
construct:implicit-conversion
construct:indexer
construct:invocation
construct:lambda
construct:method
construct:method-overloading
construct:parameter
construct:procedure
construct:string
construct:then
construct:try-finally
construct:uses
construct:variable
construct:visibility-modifiers
paradigm:functional
paradigm:imperative
paradigm:object-oriented
technique:higher-order-functions
technique:looping
technique:parallelism
uses:TDictionary<TKey,TValue>

@rpottsoh
Copy link
Member

rpottsoh commented Nov 1, 2023

Hello lovely maintainers 👋

We've recently added "tags" to student's solutions. These express the constructs, paradigms and techniques that a solution uses. We are going to be using these tags for lots of things including filtering, pointing a student to alternative approaches, and much more.

In order to do this, we've built out a full AST-based tagger in C#, which has allowed us to do things like detect recursion or bit shifting. We've set things up so other tracks can do the same for their languages, but its a lot of work, and we've determined that actually it may be unnecessary. Instead we think that we can use machine learning to achieve tagging with good enough results. We've fine-tuned a model that can determine the correct tags for C# from the examples with a high success rate. It's also doing reasonably well in an untrained state for other languages. We think that with only a few examples per language, we can potentially get some quite good results, and that we can then refine things further as we go.

I released a new video on the Insiders page that talks through this in more detail.

We're going to be adding a fully-fledged UI in the coming weeks that allow maintainers and mentors to tag solutions and create training sets for the neural networks, but to start with, we're hoping you would be willing to manually tag 20 solutions for this track. In this post we'll add 20 comments, each with a student's solution, and the tags our model has generated. Your mission (should you choose to accept it) is to edit the tags on each issue, removing any incorrect ones, and add any that are missing. In order to build one model that performs well across languages, it's best if you stick as closely as possible to the C# tags as you can. Those are listed here. If you want to add extra tags, that's totally fine, but please don't arbitrarily reword existing tags, even if you don't like what Erik's chosen, as it'll just make it less likely that your language gets the correct tags assigned by the neural network.

To summarise - there are two paths forward for this issue:

  1. You're up for helping: Add a comment saying you're up for helping. Update the tags some time in the next few days. Add a comment when you're done. We'll then add them to our training set and move forward.
  2. You not up for helping: No problem! Just please add a comment letting us know :)

If you tell us you're not able/wanting to help or there's no comment added, we'll automatically crowd-source this in a week or so.

Finally, if you have questions or want to discuss things, it would be best done on the forum, so the knowledge can be shared across all maintainers in all tracks.

Thanks for your help! 💙

Note: Meta discussion on the forum

@ErikSchierboom not sure what to say about all this. It doesn't make any sense to me. There are many videos on the insiders page, which is the one pertaining to this subject? I don't know that I have the time to implement whatever this is as I do not know what is involved to implement this. You have posted many exercises followed with tags.... Don't know what the expectation is here. Is there a better break down of what this is all about?

@iHiD
Copy link
Member

iHiD commented Nov 1, 2023

Hi @rpottsoh. The video is the latest one on the insiders page (Top right when you look).

Is there a better break down of what this is all about?

This was the original forum post from a few months back, which gives a good overview (hopefully):

https://forum.exercism.org/t/proposal-adding-tag-detection-to-analyzers/6931

In this post we'll add 20 comments, each with a student's solution, and the tags our model has generated. Your mission is to edit the tags on each issue, removing any incorrect ones, and add any that are missing.

This is the work that we need doing. So for each exercise posted there are some tags. Those need correcting (wrong ones removing, correct ones adding, etc). Then we'll train the neural network further to correctly predict tags for Delphi, and start to crowd-source more improvements. Does that make any more sense?

@ErikSchierboom
Copy link
Member Author

This is an automated comment

Hello 👋 Next week we're going to start using the tagging work people are doing on these. If you've already completed the work, thank you! If you've not, but intend to this week, that's great! If you're not going to get round to doing it, and you've not yet posted a comment letting us know, could you please do so, so that we can find other people to do it. Thanks!

@rpottsoh
Copy link
Member

I intend to look into this at the end of the week and the following week as well.

@ErikSchierboom
Copy link
Member Author

@rpottsoh Any idea when you'll be working on this?

@ErikSchierboom
Copy link
Member Author

Seeing as there are no edits, I'm gonna close this issue. If you do still want to edit, let me know.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants