Preface
Goal: A practical case to collect unique record fields using Free Pascal.
Of course there are other way to get unique value.
6: Using TStringList
This TStringList
is simple.
All we have to do is just read,
the official documentation properly,
and then use it by writing it in your code.
Delimited Text
There are already many helpful function in TStringList
,
but unfortunately there is no join
and split
method.
so we have to use what already in there,
such as delimited text
First, prepare the header. Now it is very simple: only uses and type. The skeleton is as below:
uses Classes;
var
SL : TStringList;
begin
SL := TStringList.Create;
with SL do begin ... end;
SL.Free;
end.
Then we can play with delimited text, in the program main point.
with SL do begin
// If the stringlist is not sorted,
// the Duplicates setting is ignored.
Duplicates := dupIgnore;
Sorted := true;
Delimiter := ':';
DelimitedText := 'rock:jazz:rock:pop:pop';
Delimiter := '|';
WriteLn(DelimitedText);
end;
With the result similar as below:
❯ ./09-tstringlist-a
jazz|pop|rock
Mimic Join
We can conquer the limitation, by using old tricks. The string replacement.
First, prepare the header. The skeleton is as below:
uses Classes, SysUtils;
var
SL : TStringList;
SR : String;
begin
SL := TStringList.Create;
with SL do begin ... end;
SL.Free;
end.
Then we can play with delimited text as usual.
with SL do begin
Duplicates := dupIgnore;
Sorted := true;
Delimiter := ':';
DelimitedText := 'rock:jazz:rock:pop:pop';
Delimiter := '|';
WriteLn(DelimitedText);
...
end;
And replace the string later.
with SL do begin
...
LineBreak := ', ';
WriteLn(Text);
SR := StringReplace(
DelimitedText, '|', ', ',
[rfReplaceAll, rfIgnoreCase]);
WriteLn(SR);
end;
With the result similar as below:
❯ ./09-tstringlist-b
jazz|pop|rock
jazz, pop, rock,
jazz, pop, rock
You can see the latest line result is, very similar to join function.
7: Exclude
Imperative Filter
Instead of using object oriented, we can play with oldschool algorithm.
We can learn further using recursion, to get unique tags. In order to do this, we have to create custom exclude function. This function is usually one liner in functional programming, or even lambda.
Oneliner is not a goal in Pascal. We have to make our own custom exclude function, with help of standard library.
Custom Exclude Function: Using Delete
Consider this approach: deleting elements, from an array.
As usual, prepare the header. Now it is very simple: only uses, type and const.
{$mode objfpc}{$H+}
uses SysUtils;
type TTags = array of string;
const Tags : TTags =
('rock', 'jazz', 'rock', 'pop', 'pop');
However, here below is our function.
function Exclude(
const Value: string; Elements: TTags): TTags;
var I : integer;
begin
I := High(Elements);
repeat
if Elements[I] = Value then
Delete(Elements, I, 1);
Dec(i);
until i < 0;
Result := Elements;
end;
Then we can play with custom exclude function, in the program main point.
var Xcld: TTags;
begin
Xcld := Exclude('rock', Tags);
WriteLn(string.Join(', ', Xcld));
end.
With the result similar as below:
❯ ./11-exclude-a
jazz, pop, pop
Custom Exclude Function: Using Insert
Rebuilding Array
There is another approach: by rebuilding array, except the excluded value.
There is no change, in the header. So we can ago straight examining the alternative function.
However, here below is our function.
function Exclude(
const Value: string; Elements: TTags): TTags;
var Element : string;
begin
Result := [];
for Element in Elements do
if Value <> Element then
Insert(Element, Result, High(Result)+1);
end;
There is no change, in the program main point. And the result is exactly the same as above:
❯ ./11-exclude-b
jazz, pop, pop
8: Recursive Unique
Finaly we can have our recursive function.
Getting Head and Tail.
There is also no fancy syntax, to get head and tail of and array.
But actually it is pretty easy to do it in Pascal.
Head := Elements[0];
Tail := Elements;
Delete(Tail, 0, 1);
The Complete Function
I have explain about the algorithm used in this function too many times, in other language, in other article So I won’t repeat myself.
function Unique(Elements: TTags): TTags;
var
Xcld, Tail: TTags;
Head: string;
begin
if length(Elements) <= 1 then
Result := Elements
else
begin
Head := Elements[0];
Tail := Elements;
Delete(Tail, 0, 1);
Xcld := Exclude(Head, Tail);
Result := Unique(Xcld);
Insert(Head, Result, 0);
end
end;
Then we can call our custom unique function, in the program main point.
var
Uniq: TTags;
begin
Uniq := Unique(Tags);
WriteLn(string.Join(', ', Uniq));
end.
With the result similar as below:
❯ ./12-unique-a
rock, jazz, pop
Applying to Songs Records
We can test our function above for broader situation. Do not forget to include the uses unit.
uses SysUtils, MySongs;
If you want, you can reduce the line in the function. although you will loss code readibility.
function Unique(Elements: TTags): TTags;
var Head: string;
begin
if length(Elements) <= 1 then
Result := Elements
else
begin
Head := Elements[0];
Delete(Elements, 0, 1); // tail
Result := Unique(Exclude(Head, Elements));
insert(Head, Result, 0);
end
end;
And finally apply to the songs records.
var
Song : TSong;
Tags : TTags = ();
begin
for Song in Songs do
Insert(Song.Tags, Tags, High(Tags)+1);
WriteLn(string.Join(', ', Unique(Tags)));
end.
With the result similar as below:
❯ ./12-unique-b
60s, jazz, rock, 70s, pop
What is Next 🤔?
We are going to embrace OOP approach, with a free pascal dialect, called object pascal.
Consider continue reading [ Pascal - Playing with Records - Part Four ].