Enumerate all combinations (cartesian product, abacus, odometer) - algorithm

How to implement the following algorithm in Delphi (Object Pascal) in a way that:
Each alpha numeric item will be a single object (in my case a reference to file strings).
to be possible to enumerate (to output) each pick state/combination.
imagine the columns of an abacus; all have the same size (according to its base). I need columns with different sizes. (in my case, sets of file strings with different sizes)
Last EDIT: Please, see Python intertools implementation.
Similar algorithms in other languages: c#, ruby, java, php
ALGORITHM
Consider the following sets and its members:
S1 = {a1, a2, a3, a4, a5}
S2 = {b1, b2, b3, b4}
S3 = {c1, c2, c3, c4, c5}
Pick the first member of each set (P = Pick States):
P1 = {a1, b1, c1}
Then, increment the first until its limit:
P2 = {a2, b1, c1} P3 = {a3, b1, c1} P4 = {a4, b1, c1} P5 = {a5, b1,
c1}
Then, reset the first Set, and increment 'one' the second set;
P6 = {a1, b2, c1}
Increment the first set again... and so on... reseting the first and the second set for each 'plus one' on third set.
P7 = {a2, b2, c1}
Regarding the fundamental principle of counting or principle of multiplication, this algorithm would generate 100 pick states/combinations.
P100 = {a5, b4, c5}

You are counting.
Each bead is a digit and you're working in base 5 in your example, since each bead can have one of 5 positions.
To determine what position of beads corresponds to a given integer, it is enough to write that integer in the relevant base. Here's how to do in ruby, for 17:
>> 17.to_s(5).rjust(3, '0')
=> "032"
Here I left-padded to 3 beads to be clear where each is, and I'm using a convention that beads begin in position 0, not position 1.

I've reached a solution using recursive and conditional calls. I've created a record with this structure:
TGridKey = record
ID : Integer;
Index : Integer;
Files : TStringList;
end;
TTrialSet = record
theGrid : array of TGridKey;
end;
With this name in a TForm class.
TfRandStimuliSet = class (TForm)
//...
lst1: TListBox;
dlgOpenPic: TOpenPictureDialog;
private
FTrialSet: TTrialSet;
procedure TfRandStimuliSet.SubSetsMountEngine;
//...
end;
Set Length of the Grid Array:
NumComp := 3;
SetLength(FTrialSet.theGrid, NumComp);
for I := Low(FTrialSet.theGrid) to High(FTrialSet.theGrid) do
begin
FTrialSet.theGrid[I].ID := I;
FTrialSet.theGrid[I].Index:= -1;
FTrialSet.theGrid[I].Files := TStringList.Create;
end;
Put some strings in each 'I' grid:
if dlgOpenPic.Execute then
begin
if dlgOpenPic.Files.Count > 0 then
for K := 0 to (dlgOpenPic.Files.Count - 1) do
begin
FTrialSet.theGrid[I].Files.Add(dlgOpenPic.Files.Strings[K]);
end;
dlgOpenPic.Files.Clear;
end;
Then the procedure:
procedure TfRandStimuliSet.SubSetsMountEngine;
var ID: integer; s1 : string;
procedure AddStmFromGrid(Grid, Stm : Integer);
begin
s1 := s1 + ExtractFileName(FTrialSet.theGrid[Grid].Files.Strings[Stm]) + ',';
end;
procedure AddTrialFromIndex; //each trial is the current index's
var I: Integer;
begin
for I:= Low(FTrialSet.theGrid) to High(FTrialSet.theGrid) do
AddStmFromGrid(I,FTrialSet.theGrid[I].Index);
lst1.Items.Add(s1);
s1:= '';
end;
procedure IndexReset(aGrid : Integer);
var i : Integer;
begin
for I := aGrid to (High(FTrialSet.theGrid)) do
FTrialSet.theGrid[I].Index := 0
end;
procedure IndexInc(aGrid : Integer);
begin
AddTrialFromIndex; //Save
Inc(FTrialSet.theGrid[aGrid].Index);
end;
function MoveGrid(var ID:integer): Boolean; //begin from right most, the highest grid
var IDMaxIndex, IDCurIndex, LowID, HighID: Integer;
begin
Result := True;
LowID := Low(FTrialSet.theGrid);
HighID := High(FTrialSet.theGrid);
//Application.ProcessMessages;
if (ID < LowID) then
begin
//ShowMessage('False');
AddTrialFromIndex;
Result := False;
end
else
begin
IDMaxIndex:= FTrialSet.theGrid[ID].Files.Count -1;
IDCurIndex := FTrialSet.theGrid[ID].Index;
if IDCurIndex = IDMaxIndex then
begin
ID := ID - 1;
Result:= MoveGrid(ID);//moveleft
Exit;
end;
if (ID < HighID)
and (IDCurIndex < IDMaxIndex) then
begin
IndexInc(ID); //increment/move donw
IndexReset(ID + 1); //reset everything on the right
MoveGrid(HighID); //move to the most right/next down
Exit;
end;
if (ID = (HighID))
and (IDCurIndex < IDMaxIndex) then
begin
IndexInc(ID); //increment/move down
MoveGrid(ID) //next increment/move down
end;
end;
end;
begin
ID := High(FTrialSet.theGrid);
IndexReset(Low(FTrialSet.theGrid)); //0's for everyone
MoveGrid(ID); //begin from the most right
end;

Related

TStringList.CustomSort: Compare() with variables

I am trying to custom sort a TStringList by a column in a .CSV file. My code below works (slowly, about 14 seconds for 200,000+ lines):
function Compare(List: TStringList; Index1, Index2: Integer): Integer;
function ColStr(const Ch: Char; const S: String; First, Last: Integer): String;
var
p1, p2: Integer;
function GetPos(const N: Integer; Start: Integer = 1): Integer;
var
I, Len, Count: Integer;
begin
Result := 0;
Len := Length(S);
if (Len = 0) or (Start > Len) or (N < 1) then Exit;
Count := 0;
for I := Start to Len do begin
if S[I] = Ch then begin
Inc(Count);
if Count = N then begin
Result := I;
Exit;
end;
end;
end;
end;
begin
p1 := GetPos(4, 1); // 4 should be a variable
p2 := GetPos(5, 1); // 5 should be a variable
if Last = 0 then Result := Copy(S, p1 + 1, length(S)) else Result := Copy(S, p1 + 1, p2 - p1 - 1);
end;
begin
Result := AnsiCompareStr(ColStr(',', List[Index1], 0, 1), ColStr(',', List[Index2], 0, 1));
end;
What I would want to do is not have this hard-coded but (where commented "should be a variable" depending on which column to sort). I know I can't use:
function Form1.Compare(List: TStringList; Index1, Index2: Integer): Integer;
for inserting variables, as I get the error:
Incompatible types: 'method pointer and regular procedure'.
I have searched through SO looking for instances of this error but cannot find one that fits my question. I would appreciate any pointers in the right direction.
This has to be done with Delphi 7 and Windows 11.
TStringList.CustomSort() does not let you pass in extra parameters, and it does not accept class methods or anonymous procedures. But, what it does do is pass the actual TStringList itself to the callback, so I would suggest deriving a new class from TStringList to add extra fields to it, and then you can access those fields inside the callback, eg:
type
TMyStringList = class(TStringList)
public
Count1: Integer;
Count2: Integer;
end;
function Compare(List: TStringList; Index1, Index2: Integer): Integer;
...
p1 := GetPos(TMyStringList(List).Count1, 1);
p2 := GetPos(TMyStringList(List).Count2, 1);
...
begin
...
end;
...
List := TMyStringList.Create;
// fill List ...
List.Count1 := ...;
List.Count2 := ...;
List.CustomSort(Compare);
So you are performing searching for k-th occurence of Ch and substring creation at every comparison.
You can optimize this process - before sorting make list/array of stringlists, created from every string, separated by needed character - use DelimitedText.
Inside compare function just work with this array and column numbers - sadly, you have to define them as global variables in current unit (for example, after Form1: TForm1)

Read integers from a string

I'm learning algorithms and I'm trying to make an algorithm that extracts numbers lets say n in [1..100] from a string. Hopefully I get an easier algorithm.
I tried the following :
procedure ReadQuery(var t : tab); // t is an array of Integer.
var
x,v,e : Integer;
inputs : String;
begin
//readln(inputs);
inputs:='1 2 3';
j:= 1;
// make sure that there is one space between two integers
repeat
x:= pos(' ', inputs); // position of the space
delete(inputs, x, 1)
until (x = 0);
x:= pos(' ', inputs); // position of the space
while x <> 0 do
begin
x:= pos(' ', inputs); //(1) '1_2_3' (2) '2_3'
val(copy(inputs, 1, x-1), v, e); // v = value | e = error pos
t[j]:=v;
delete(inputs, 1, x); //(1) '2_3' (2) '3'
j:=j+1; //(1) j = 2 (2) j = 3
//writeln(v);
end;
//j:=j+1; // <--- The mistake were simply here.
val(inputs, v, e);
t[j]:=v;
//writeln(v);
end;
I get this result ( resolved ) :
1
2
0
3
expected :
1
2
3
PS : I'm not very advanced, so excuse me for reducing you to basics.
Thanks for everyone who is trying to share knowledge.
Your code is rather inefficient and it also doesn't work for strings containing numbers in general.
A standard and performant approach would be like this:
type
TIntArr = array of Integer;
function GetNumbers(const S: string): TIntArr;
const
AllocStep = 1024;
Digits = ['0'..'9'];
var
i: Integer;
InNumber: Boolean;
NumStartPos: Integer;
NumCount: Integer;
procedure Add(Value: Integer);
begin
if NumCount = Length(Result) then
SetLength(Result, Length(Result) + AllocStep);
Result[NumCount] := Value;
Inc(NumCount);
end;
begin
InNumber := False;
NumCount := 0;
for i := 1 to S.Length do
if not InNumber then
begin
if S[i] in Digits then
begin
NumStartPos := i;
InNumber := True;
end;
end
else
begin
if not (S[i] in Digits) then
begin
Add(StrToInt(Copy(S, NumStartPos, i - NumStartPos)));
InNumber := False;
end;
end;
if InNumber then
Add(StrToInt(Copy(S, NumStartPos)));
SetLength(Result, NumCount);
end;
This code is intentionally written in a somewhat old-fashioned Pascal way. If you are using a modern version of Delphi, you wouldn't write it like this. (Instead, you'd use a TList<Integer> and make a few other adjustments.)
Try with the following inputs:
521 cats, 432 dogs, and 1487 rabbits
1 2 3 4 5000 star 6000
alpha1beta2gamma3delta
a1024b2048cdef32
a1b2c3
32h50s
5020
012 123!
horses
(empty string)
Make sure you fully understand the algorithm! Run it on paper a few times, line by line.

Insert values ordered from least to greatest in an Array

I need to create an array within a register that is a sequence of natural numbers, these are inserted by the user and cannot be repeated. My problem is sorting the array values as they are inserted in ascending order. I have tried this code without success. Thank you.
const
MAX = 10;
type
Natural = 0..MAXINT;
Secuencia = RECORD
valores : ARRAY [1..MAX] OF Natural;
tope : 0..MAX;
END;
TipoResultado = (Fallo, Creado, Agregado);
Resultado = RECORD
CASE quePaso : TipoResultado OF
Fallo: ();
Creado: ();
Agregado: (posicion: Natural);
END;
//Non-repeating value search function in array
Function BLineal(valor: Natural; sec: Secuencia ): boolean;
var i : integer;
begin
i := 1;
while (i <= sec.tope) and (sec.valores[i] <> valor) do
i := i + 1;
BLineal := i <= sec.tope
end;
Procedure to order values within the arrangement from smallest to largest by insertion.
//Procedure ORDER
Procedure OrdIns (var sec: Secuencia);
var
i,j: integer;
aux: Natural;
begin
for i := 2 to sec.tope do begin
j := i;
while (j >= 2) and ((sec.valores[j]) < (sec.valores[j-1])) do
begin
aux:=sec.valores[j-1];
sec.valores[j-1]:=sec.valores[j];
sec.valores[j]:=aux;
j := j - 1
end
end;
end;
Do the problem in two steps. First, create a binary tree and insert values as entered. Then when no more values are found simply walk the tree and create the array.

Swap two numbers in pascal

I'm trying to swap two values but I'm getting a Warning: Local variable "temp" does not seem to be initialized. I want to do it similar as how I've done it. I'm compiling it from the command line with fpc Main.pas. I've tried initializing the temp variable to 0, but it still says Fatal: there were 3 errors compiling module, stopping.
'Main.pas'
Program Main;
procedure Main();
var
n1, n2: Integer;
begin
n1 := 5;
n2 := 10;
Swap(#n1, #n2);
writeln('n1 = ', n1);
writeln('n2 = ', n2);
end;
BEGIN
Main();
END.
'Number.pas'
unit Number;
interface
type
IntPtr = ^Integer;
procedure Swap(n1, n2: IntPtr);
implementation
procedure Swap(n1, n2: IntPtr);
var
temp: Integer;
begin
temp = n1^;
n1^ = n2^;
n2^ = temp;
end;
end.
As you have already discovered, you mixed up the assignment (:=) and equality (=) operators. Thus,
procedure Swap(A, B: PInteger);
var
Temp: Integer;
begin
Temp := A^;
A^ := B^;
B^ := Temp;
end;
where PInteger is defined as ^Integer, does the job:
Swap(#Val1, #Val2); // swaps integers Val1 and Val2
However, I suggest you do this slightly differently:
procedure Swap(var A, B: Integer);
var
Temp: Integer;
begin
Temp := A;
A := B;
B := Temp;
end;
Using a var parameter is more idiomatic and it allows you to write simply
Swap(Val1, Val2); // swaps integers Val1 and Val2
and it also gives you a bit more type safety.

Pascal: Updating Recent Scores Array

I have a program that stores the last three recent scores of a game. However, I would like to store only the best scores. Eg. Recent Scores: Tom - 12, Sam - 14, Sue - 16. If i played the game and got a new score of 20, i would like it to store the new score of 20 (with name) and the other two scores of Sam and Sue ...thereby losing Tom. (I'm not worried about order).
Const NoOfRecentScores = 3;
TRecentScore = Record
Name : String;
Score : Integer;
End;
TRecentScores = Array[1..NoOfRecentScores] of TRecentScore;
Var
RecentScores : TRecentScores;
When i play the game, i call a procedure called UpdateRecentScores. Here it is:
Procedure UpdateRecentScores(Var RecentScores : TRecentScores; Score : Integer);
Var
PlayerName : String;
Count,count2 : Integer;
FoundSpace : Boolean;
MinMark,position: Integer;
ScorePresent:boolean = false;
Begin
PlayerName := GetPlayerName;
FoundSpace := False;
Count :=1;
While Not FoundSpace And (Count <= NoOfRecentScores)
Do If RecentScores[Count].Name = ''
Then FoundSpace := True
Else Count := Count + 1;
Here is the part i am struggling with. If no score is previously entered, then i have to accept that the first entered score is going to be the minimum:
If ScorePresent = False then
begin
MinMark:=Score;
ScorePresent:=True;
RecentScores[Count].Name := PlayerName;
RecentScores[Count].Score := Score;
writeln('Minimum Mark is: ',MinMark);
end
...the problem with the above, however, is that if the first score is a very high score, that becomes my minimum score!
Below, i am simply saying that if the Score achieved is greater than the MinMark (i.e. the minimum score) then the score should be stored in the array.
else if Score> MinMark then
begin
For count:= 1 to NoOfRecentScores do
begin
if RecentScores[count].score<Score then
position:=count;
RecentScores[position].Name := PlayerName;
RecentScores[position].Score := Score;
end;
End;
end;
As you can see, i am trying to check what the MinMark is first of all. Then, compare the score that i've just got with the MinMarker to see if it should be stored.
To clarify therefore, I want to save the best 3 scores and not recent scores.
To store n best scores, it is convenient to order them from high to low.
Let us go with an example first. Suppose you have the following four records: Tim - 14, Susan - 7, Don - 5, and Derek - 12. Then the array will look like [('Tim', 14), ('Derek', 12), ('Susan', 7), ('Don', 5)].
Now, what happens when Bert achieves a score of 9? Turns out we just want to insert a pair in a sorted array, so that it becomes [('Tim', 14), ('Derek', 12), ('Bert', 9), ('Susan', 7), ('Don', 5)]. After that, we drop the last element.
If we have to modify the array in place, we find the position pos where to insert Bert (it is position 3), then move everything in positions pos..(n-1) into positions (pos+1)..n, and after that, write Ben and his score to position pos.
Since you're using such a small array (3 records), it's relatively simple. (If your array was considerably larger, you'd want to keep it sorted and use a faster means of locating the right position for it, but your array is extremely small.)
Let's say you now have three RecentScore records, containing Tim - 14, Susan - 7, Derek - 12.
You need to find out if there's a score lower than the one the user just attained by Gemma (9) which is in the CurrentScore record (type TRecentScore), and if so replace it with Gemma's name and score.
Here's a working console project (compiled and run in XE5):
program Project1;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils;
type
TRecentScore = record
Name: string;
Score: Integer;
end;
TRecentScores = array of TRecentScore;
// Function to find lower score than the one we're trying to add.
// Returns the index of the next lower item if there is one, or
// -1 if there isn't one.
function FindLowerScore(Scores: TRecentScores; CheckScore: Integer): Integer;
var
i: Integer;
MinScore: Integer; // Lowest score found
begin
Result := -1; // No index found
MinScore := CheckScore; // Lowest score so far
for i := Low(Scores) to High(Scores) do
if Scores[i].Score < MinScore then // Lower than CheckScore?
begin
MinScore := Scores[i].Score; // Yep. Store it (new lowest)
Result := i; // and where it was found
end;
end;
// Utility procedure to display list of scores
procedure PrintScores(const Prelude: string; Scores: TRecentScores);
var
Score: TRecentScore;
begin
WriteLn(Prelude);
for Score in Scores do
Writeln(' ' + Score.Name + ' = ' + IntToStr(Score.Score));
end;
var
RecentScores: TRecentScores;
CurrentScore: TRecentScore;
i: Integer;
begin
SetLength(RecentScores, 3);
RecentScores[0].Name := 'Tim';
RecentScores[0].Score := 14;
RecentScores[1].Name := 'Susan';
RecentScores[1].Score := 7;
RecentScores[2].Name := 'Derek';
RecentScores[2].Score := 12;
// Show scores where we begin
PrintScores('Before', RecentScores);
CurrentScore.Name := 'Gemma'; CurrentScore.Score := 9;
// Check for lower score than Gemma's
i := FindLowerScore(RecentScores, CurrentScore.Score);
if i = -1 then
WriteLn('No lower score found!')
else
begin
// We have a lower score in the array. Update that one
// with our new score.
RecentScores[i].Name := CurrentScore.Name;
RecentScores[i].Score := CurrentScore.Score;
PrintScores('After', RecentScores);
end;
ReadLn;
end.
Here is what i used, following your advice...
Procedure UpdateRecentScores(Var RecentScores : TRecentScores; Score : Integer);
Var
PlayerName : String;
Count : Integer;
FoundSpace : Boolean;
Begin
PlayerName := GetPlayerName;
FoundSpace := False;
Count := 1;
While Not FoundSpace And (Count <= NoOfRecentScores)
Do If RecentScores[Count].Name = ''
Then
begin
FoundSpace := True;
RecentScores[Count].Name := PlayerName;
RecentScores[Count].Score := Score;
end
Else Count := Count + 1;
If Not FoundSpace
Then
Begin
SortRecentScores(RecentScores); // sort them into order of score
if score > RecentScores[NoOfRecentScores].Score then
begin
RecentScores[NoOfRecentScores].Name:= PlayerName;
RecentScores[NoOfRecentScores].Score:= Score;
end;
End;
End;

Resources