I asked ChatGPT to “Write pascal code to manage a sorted symbol table using two tables the main table and a secondary table, when the secondary table becomes more then 10% of the main table do a merge sort into the main table”
This is something I wrote back in the late eighties, and aside from a few syntax errors which ChatGPT corrected when asked it compiled and ran perfectly
program SymbolTableManager;
uses
SysUtils;
const
MaxSize = 1000;
type
Symbol = record
Key: string[30];
Value: string[100];
end;
SymbolTable = record
Data: array[1..MaxSize] of Symbol;
Count: Integer;
end;
var
MainTable, SecondaryTable: SymbolTable;
procedure InitTable(var Table: SymbolTable);
begin
Table.Count := 0;
end;
function CompareSymbols(const A, B: Symbol): Integer;
begin
if A.Key < B.Key then
CompareSymbols := -1
else if A.Key > B.Key then
CompareSymbols := 1
else
CompareSymbols := 0;
end;
procedure AddToSecondaryTableSorted(Key, Value: string);
var
i, pos: Integer;
NewSymbol: Symbol;
begin
if SecondaryTable.Count >= MaxSize then
begin
writeln('Secondary Table is full!');
Exit;
end;
// Create the new symbol
NewSymbol.Key := Key;
NewSymbol.Value := Value;
// Find insertion position
pos := SecondaryTable.Count + 1;
for i := SecondaryTable.Count downto 1 do
begin
if CompareSymbols(SecondaryTable.Data[i], NewSymbol) > 0 then
begin
SecondaryTable.Data[i + 1] := SecondaryTable.Data[i]; // shift right
pos := i;
end
else
Break;
end;
// Insert the new symbol
SecondaryTable.Data[pos] := NewSymbol;
Inc(SecondaryTable.Count);
end;
procedure MergeTables;
var
TempTable: SymbolTable;
i, j, k: Integer;
begin
writeln('Merging tables...');
i := 1;
j := 1;
k := 0;
while (i <= MainTable.Count) and (j <= SecondaryTable.Count) do
begin
if CompareSymbols(MainTable.Data[i], SecondaryTable.Data[j]) <= 0 then
begin
Inc(k);
TempTable.Data[k] := MainTable.Data[i];
Inc(i);
end
else
begin
Inc(k);
TempTable.Data[k] := SecondaryTable.Data[j];
Inc(j);
end;
end;
while i <= MainTable.Count do
begin
Inc(k);
TempTable.Data[k] := MainTable.Data[i];
Inc(i);
end;
while j <= SecondaryTable.Count do
begin
Inc(k);
TempTable.Data[k] := SecondaryTable.Data[j];
Inc(j);
end;
TempTable.Count := k;
MainTable := TempTable;
InitTable(SecondaryTable);
end;
procedure InsertSymbol(Key, Value: string);
begin
AddToSecondaryTableSorted(Key, Value);
if MainTable.Count = 0 then
Exit;
if SecondaryTable.Count > (MainTable.Count div 10) then
MergeTables;
end;
procedure PrintTable(const Table: SymbolTable; const LabelText: string);
var
i: Integer;
begin
writeln(LabelText);
for i := 1 to Table.Count do
writeln(i:3, ': ', Table.Data[i].Key, ' -> ', Table.Data[i].Value);
end;
procedure QuickSort(var A: array of Symbol; L, R: Integer);
var
i, j: Integer;
pivot, tmp: Symbol;
begin
i := L;
j := R;
pivot := A[(L + R) div 2];
repeat
while CompareSymbols(A[i], pivot) < 0 do Inc(i);
while CompareSymbols(A[j], pivot) > 0 do Dec(j);
if i <= j then
begin
tmp := A[i];
A[i] := A[j];
A[j] := tmp;
Inc(i);
Dec(j);
end;
until i > j;
if L < j then QuickSort(A, L, j);
if i < R then QuickSort(A, i, R);
end;
procedure PreloadMainTable;
var
i: Integer;
begin
for i := 1 to 20 do
begin
Inc(MainTable.Count);
with MainTable.Data[MainTable.Count] do
begin
Key := 'Key' + Chr(64 + i); // KeyA, KeyB, ...
Value := 'Value' + IntToStr(i);
end;
end;
QuickSort(MainTable.Data, 1, MainTable.Count);
end;
begin
InitTable(MainTable);
InitTable(SecondaryTable);
PreloadMainTable;
writeln('Initial Main Table:');
PrintTable(MainTable, 'Main Table:');
// Insert symbols to Secondary Table
InsertSymbol('KeyM', 'ExtraM');
InsertSymbol('KeyZ', 'ExtraZ');
InsertSymbol('KeyA', 'ExtraA');
InsertSymbol('KeyC', 'ExtraC');
InsertSymbol('KeyY', 'ExtraY');
InsertSymbol('KeyG', 'ExtraG');
InsertSymbol('KeyX', 'ExtraX');
InsertSymbol('KeyH', 'ExtraH');
InsertSymbol('KeyW', 'ExtraW');
InsertSymbol('KeyE', 'ExtraE');
InsertSymbol('KeyT', 'ExtraT'); // Should trigger merge
writeln;
PrintTable(MainTable, 'Main Table After Merge:');
end.
After some more prompting we have a program that uses a memory mapped file and supports up to 1 million records in the symbol table.
program MMapSymbolTable;
uses
BaseUnix, SysUtils;
const
FileName = 'symbols.mmap';
MaxSymbols = 1000000;
type
Symbol = packed record
Key: string[30];
Value: string[100];
Deleted: Boolean;
end;
PSymbol = ^Symbol;
SymbolHeader = packed record
MainCount: Int64;
SecondaryCount: Int64;
end;
PSymbolHeader = ^SymbolHeader;
procedure Merge; forward;
var
fd: cint;
MapSize: cint;
FilePtr: Pointer;
Header: PSymbolHeader;
Records: PSymbol;
procedure EnsureFileSize(Filename: string; Size: cint);
var
f: file;
Dummy: Byte = 0;
begin
Assign(f, Filename);
{$I-} Reset(f, 1); {$I+}
if IOResult <> 0 then Rewrite(f, 1);
Seek(f, Size - 1);
BlockWrite(f, Dummy, 1);
Close(f);
end;
procedure MMapFile;
begin
MapSize := SizeOf(SymbolHeader) + SizeOf(Symbol) * MaxSymbols;
EnsureFileSize(FileName, MapSize);
fd := fpopen(FileName, O_RDWR);
if fd = -1 then
begin writeln('Failed to open file'); Halt(1); end;
FilePtr := fpmmap(nil, MapSize, PROT_READ or PROT_WRITE, MAP_SHARED, fd, 0);
if PtrUInt(FilePtr) = PtrUInt(MAP_FAILED) then
begin writeln('mmap failed'); Halt(2); end;
Header := PSymbolHeader(FilePtr);
Records := PSymbol(Pointer(PChar(FilePtr) + SizeOf(SymbolHeader)));
if (Header^.MainCount = 0) and (Header^.SecondaryCount = 0) then
begin
Header^.MainCount := 0;
Header^.SecondaryCount := 0;
end;
end;
procedure UnmapAndClose;
begin
fpmunmap(FilePtr, MapSize);
fpclose(fd);
end;
procedure InsertSortedIntoSecondary(Key, Value: string);
var
i, insertPos, secStart, secEnd: Int64;
totalCount: Int64;
NewSym: Symbol;
begin
totalCount := Header^.MainCount + Header^.SecondaryCount;
if totalCount >= MaxSymbols then
begin writeln('Table full'); Exit; end;
NewSym.Key := Key;
NewSym.Value := Value;
NewSym.Deleted := False;
secStart := Header^.MainCount;
secEnd := secStart + Header^.SecondaryCount;
// Find insert position in secondary
insertPos := secEnd;
for i := secStart to secEnd - 1 do
begin
if Records[i].Key > Key then
begin
insertPos := i;
Break;
end;
end;
// Shift everything from insertPos to end of secondary forward
for i := secEnd - 1 downto insertPos do
Records[i + 1] := Records[i];
// Insert new record
Records[insertPos] := NewSym;
Inc(Header^.SecondaryCount);
// Auto-merge if secondary > 5% of main
if (Header^.SecondaryCount > 1000) and (Header^.MainCount > 0) and (Header^.SecondaryCount * 100 > Header^.MainCount * 5) then
begin
writeln('Auto-merging: secondary is greater than 1000 and more than 5% of main');
Merge;
Merge;
end;
end;
procedure PrintTable;
var
i: Integer;
begin
writeln('--- Main Region (Sorted) ---');
for i := 0 to Header^.MainCount - 1 do
if not Records[i].Deleted then
writeln(i:3, ': ', Records[i].Key, ' -> ', Records[i].Value);
writeln('--- Secondary Region (Sorted) ---');
for i := Header^.MainCount to Header^.MainCount + Header^.SecondaryCount - 1 do
if not Records[i].Deleted then
writeln(i:3, ': ', Records[i].Key, ' -> ', Records[i].Value);
end;
procedure Merge;
var
All: array of Symbol;
Count, i, j: NativeInt;
temp: Symbol;
begin
Count := 0;
SetLength(All, 0);
for i := 0 to Header^.MainCount + Header^.SecondaryCount - 1 do
if not Records[i].Deleted then
begin
SetLength(All, Length(All) + 1);
All[High(All)] := Records[i];
end;
Count := Length(All);
SetLength(All, Count);
for i := 1 to Count - 1 do
begin
temp := All[i];
j := i - 1;
while (j >= 0) and (All[j].Key > temp.Key) do
begin
All[j + 1] := All[j];
Dec(j);
end;
All[j + 1] := temp;
end;
for i := 0 to Count - 1 do
Records[i] := All[i];
for i := Count to Header^.MainCount + Header^.SecondaryCount - 1 do
begin
Records[i].Key := '';
Records[i].Value := '';
Records[i].Deleted := True;
end;
Header^.MainCount := Count;
Header^.SecondaryCount := 0;
writeln('Merge complete. MainCount = ', Header^.MainCount);
end;
procedure DeleteSymbol(Key: string);
var
i, total: Int64;
begin
total := Header^.MainCount + Header^.SecondaryCount;
for i := 0 to total - 1 do
if (Records[i].Key = Key) and (not Records[i].Deleted) then
begin
Records[i].Deleted := True;
writeln('Deleted: ', Key);
Exit;
end;
writeln('Not found: ', Key);
end;
procedure SearchSymbol(Key: string);
var
L, R, M: Int64;
begin
// Search Main Region
L := 0;
R := Header^.MainCount - 1;
while L <= R do
begin
M := (L + R) div 2;
if Records[M].Deleted then begin R := M - 1; Continue; end;
if Records[M].Key = Key then
begin writeln('[MAIN] Found: ', Records[M].Key, ' -> ', Records[M].Value); Exit; end
else if Records[M].Key < Key then
L := M + 1
else
R := M - 1;
end;
// Search Secondary Region
L := Header^.MainCount;
R := Header^.MainCount + Header^.SecondaryCount - 1;
while L <= R do
begin
M := (L + R) div 2;
if Records[M].Deleted then begin R := M - 1; Continue; end;
if Records[M].Key = Key then
begin writeln('[SECONDARY] Found: ', Records[M].Key, ' -> ', Records[M].Value); Exit; end
else if Records[M].Key < Key then
L := M + 1
else
R := M - 1;
end;
writeln('Not found: ', Key);
end;
procedure Demo;
begin
InsertSortedIntoSecondary('KeyM', 'ValueM');
InsertSortedIntoSecondary('KeyB', 'ValueB');
InsertSortedIntoSecondary('KeyD', 'ValueD');
InsertSortedIntoSecondary('KeyE', 'ValueE');
InsertSortedIntoSecondary('KeyF', 'ValueF');
InsertSortedIntoSecondary('KeyG', 'ValueG');
InsertSortedIntoSecondary('KeyH', 'ValueH');
InsertSortedIntoSecondary('KeyI', 'ValueI');
InsertSortedIntoSecondary('KeyJ', 'ValueJ');
InsertSortedIntoSecondary('KeyK', 'ValueK');
InsertSortedIntoSecondary('KeyA', 'ValueA');
InsertSortedIntoSecondary('KeyZ', 'ValueZ');
InsertSortedIntoSecondary('KeyC', 'ValueC');
writeln;
PrintTable;
writeln;
Merge;
PrintTable;
writeln;
SearchSymbol('KeyM');
DeleteSymbol('KeyM');
writeln;
PrintTable;
end;
begin
MMapFile;
Demo;
UnmapAndClose;
end.