Preface
Goal: A practical case to collect unique record fields using Free Pascal.
2: Data Structure
Meet The Songs Record
From just karaoke, we can go pro with recording studio.
From simple data, we can build a structure to solve our task.
Record Type
We can use user defined type to define a record:
Prepare the header: uses, type, and variable.
uses SysUtils;
type TSong = record
Title : ANSIString;
Tags : array of ANSIString;
end;
var Song : TSong;
Then we can define the records in main program.
begin
with Song do
begin
Title := 'Cantaloupe Island';
Tags := ['60s', 'jazz'];
end;
Write(Song.Title + ': ');
WriteLn(ANSIString.Join(', ', Song.Tags));
end.
With the result similar as below:
❯ ./02-record-a
Cantaloupe Island: 60s, jazz
Defining Record as Const
We can also use const to fill a record:
Prepare the header: uses, and type.
{$mode objfpc}{$H+}
uses SysUtils;
type TSong = record
Title : string;
Tags : array of string;
end;
Then we can fill a record in const. Just one record.
const
Song : TSong =
( Title : 'Cantaloupe Island';
Tags : ('60s', 'jazz') );
And access it later in main program.
begin
Write(Song.Title + ': ');
WriteLn(string.Join(', ', Song.Tags));
end.
With the result similar as below:
❯ ./02-record-b
Cantaloupe Island: 60s, jazz
Array of Song Records
Isn’t it nice to have all record in a row, written directly in your code?
Of course we can do it:
Prepare the header: uses, type. Nothing change.
{$mode objfpc}{$H+}
uses SysUtils;
type TSong = record
Title : string;
Tags : array of string;
end;
Then we can fill all the records in const,
as an array of TSong
.
const
Songs : array of TSong =
( ( Title : 'Cantaloupe Island';
Tags : ('60s', 'jazz') ),
( Title : 'Let It Be';
Tags : ('60s', 'rock') ),
( Title : 'Knockin'' on Heaven''s Door';
Tags : ('70s', 'rock') ),
( Title : 'Emotion';
Tags : ('70s', 'pop') ),
( Title : 'The River';
Tags : () )
);
And access it later in main program.
But beware that we required to define a Song
variable,
to get the loop variable properly.
var Song: TSong;
begin
for Song in Songs do
begin
Write(format('%-20s',
[Song.Title]) + ': ');
WriteLn(string.Join(
', ', Song.Tags));
end;
end.
With the result similar as below:
❯ ./03-songs
Cantaloupe Island : 60s, jazz
Let It Be : 60s, rock
Knockin' on Heaven's Door: 70s, rock
Emotion : 70s, pop
The River :
3: Separating Unit
Since we need to reuse the songs record multiple times, it is a good idea to separate the record from logic.
Songs Unit
The code can be shown as below:
{$mode objfpc}{$H+}
unit MySongs;
interface
type TSong = record
Title : string;
Tags : array of string;
end;
const
Songs : array of TSong = ...;
implementation
end.
Which the Songs can be witten as below:
const
Songs : array of TSong =
( ( Title : 'Cantaloupe Island';
Tags : ('60s', 'jazz') ),
( Title : 'Let It Be';
Tags : ('60s', 'rock') ),
( Title : 'Knockin'' on Heaven''s Door';
Tags : ('70s', 'rock') ),
( Title : 'Emotion';
Tags : ('70s', 'pop') ),
( Title : 'The River';
Tags : () )
);
Using Songs Unit
Now we can have a very short code.
{$mode objfpc}{$H+}
uses SysUtils, MySongs;
var Song: TSong;
begin
for Song in Songs do
begin
Write(format('%-20s',
[Song.Title]) + ': ');
WriteLn(string.Join(
', ', Song.Tags));
end;
end.
With the result exactly the same as previous code.
4: Extracting Records
Now we can easily extract all songs records, in pure free pascal.
Note that we are using imperative approach, and no functional approach at all.
Array: Insert
There is this useful insert
function,
so we can merge an array into other array.
{$mode objfpc}{$H+}
uses SysUtils, MySongs;
var
Song: TSong;
Tags: array of string = ();
begin
for Song in Songs do
System.Insert(Song.Tags, Tags, High(Tags)+1);
WriteLn(string.Join(', ', Tags));
end.
With the result similar as below:
❯ ./05-extract-a
60s, jazz, 60s, rock, 70s, rock, 70s, pop
Array of Array: Insert
But beware of the type. You might mistakenly insert array of array instead.
{$mode objfpc}{$H+}
uses SysUtils, MySongs;
type TTags = array of string;
var
Song : TSong;
Tags : TTags;
Tagss: array of TTags = ();
begin
for Song in Songs do
Insert(Song.Tags, Tagss, High(Tagss)+1);
for Tags in Tagss do
WriteLn(string.Join(', ', Tags));
end.
With the result similar as below:
❯ ./05-extract-b
60s, jazz
60s, rock
70s, rock
70s, pop
Array Flatten
But that is okay. We can still flatten with loop anyway.
Actually, loop after loop.
{$mode objfpc}{$H+}
uses SysUtils, MySongs;
type TTags = array of string;
var
Song : TSong;
Tags : TTags;
Tagss: array of TTags = ();
Flatten : TTags = ();
begin
for Song in Songs do
Insert(Song.Tags, Tagss, High(Tagss)+1);
for Tags in Tagss do
Insert(Tags, Flatten, High(Flatten)+1);
WriteLn(string.Join(', ', Flatten));
end.
With the result similar as below:
❯ ./05-extract-c
60s, jazz, 60s, rock, 70s, rock, 70s, pop
5: Finishing The Task
Unique Tags.
We can have our unique tags, by using imperative approach.
Rebuild Array
Just like I said, pascal is more low level,
than other language such as ruby
or python
.
To get the unique array, we need to iterate all the tags, and create new tags based on conditional filter.
Prepare the header: uses, type and variable definition.
{$mode objfpc}{$H+}
uses SysUtils, MySongs;
type TTags = array of string;
var
Song : TSong;
Tags : TTags = ();
Unique: TTags = ();
Tag, Uni : string;
Exist : boolean;
Then we can rebuild array in main program,
with the help of exist
variable.
begin
for Song in Songs do
insert(Song.Tags, Tags, High(Tags)+1);
for Tag in Tags do
begin
Exist := false;
for Uni in Unique do
if (Tag = Uni) then Exist := true;
if (not Exist) then
Insert(Tag, Unique, High(Unique)+1);
end;
WriteLn(string.Join(', ', Unique));
end.
With the result similar as below:
❯ ./01-tags-d
rock, jazz, rock, pop, pop.
Refactor Function
We can simplify that cryptic algorithm above, if we can just move a few code into its own function.
Prepare the header. Now it is very simple: only uses and type.
{$mode objfpc}{$H+}
uses SysUtils, MySongs;
type TTags = array of string;
Then the function:
function IsExist(
Element: string; Tags: TTags): boolean;
var Tag: string;
begin
Result := false;
for Tag in Tags do
if (Tag = Element) then Result := true;
end;
Then we can rebuild array in main program,
with the help of IsExist
function.
Prepare, we have to setup the variable definition.
var
Song : TSong;
Tags : TTags = ();
Unique: TTags = ();
Tag : string;
And then the program main point.
begin
for Song in Songs do
Insert(Song.Tags, Tags, High(Tags)+1);
for Tag in tags do
begin
if (not IsExist(Tag, Unique)) then
Insert(Tag, Unique, High(Unique)+1);
end;
WriteLn(string.Join(', ', Unique));
end.
With the result similar as below:
❯ ./01-tags-d
rock, jazz, rock, pop, pop.
Now the code looks simpler.
Getting Started Right
Please be patient, We need to get to know the pure free pascal, before go on the more advance object pascal discussion.
What is Next 🤔?
We have alternative way to extract the record structure.
Consider continue reading [ Pascal - Playing with Records - Part Three ].