I use this procedure to ENUM the keys into a TNTListView (UNICODE) in Delphi 7
procedure TForm1.TntButton1Click(Sender: TObject);
var
k : HKEY;
Buffer : array of widechar;
i : Integer;
iRes : Integer;
BuffSize : DWORD;
item : TTNTListItem;
WS : WideString;
begin
if RegOpenKeyExW (HKEY_CURRENT_USER, 'Software', 0, KEY_READ, K) = ERROR_SUCCESS then begin
try
i := 0;
BuffSize := 1;
while true do begin
SetLength (Buffer, BuffSize);
iRes := RegEnumKeyW(k, I, #Buffer[0], BuffSize);
if iRes = 259 then break;
if iRes = 234 then begin
inc (BuffSize);
continue;
end;
messageboxw (0, #Buffer[0], '', 0);
item := TNTListView1.Items.Add;
item.Caption := WideString (Buffer); // BREAKS IT
{ SOLUTION }
SetLength (WS, BuffSize - 1);
CopyMemory (#WS[1], #Buffer[0], (BuffSize * 2));
{ .... }
inc (i);
BuffSize := 1;
end;
finally
RegCloseKey (k);
SetLength (Buffer, 0);
end;
end;
end;
I see that most of the listviewitems are trimmed! However if I show the Buffer in the messagebox it shows the complete string in the right length. Is this a Bug of the listview or am I missing something like a NULL CHAR (or even 2)?
Thanks for help.
EDIT: I just noticed that the Buffer get's trimmed into half when I cast it to a widestring.
EDIT2: No bug in the listview. The WideString Cast breaks the string somehow and / or doesn't detect the NULL CHAR(s).
You are right - casting array of WideChar to WideString halves the string length in pre-Unicode Delphi's.
Tested on Delphi 2007:
var
A: array of WideChar;
begin
SetLength(A, 4);
ShowMessage(IntToStr(Length(WideString(A)))); // 2
end;
A quick view on the above code in debugger CPU window shows that typecasting array of WideChar-> WideString does not result in any data conversion; internal WideString format stores the string size (i.e. the number of bytes) in the same place where Delphi strings or dynarrays store length. As a result typecasting halves string length.
Related
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)
what I am trying to do, is determine, whether brackets are in correct order. For example ([][[]]<<>>) is vallid, but ][]<<(>>) is not.
I got a working version, but it has terrible efficiency and when it gets 1000+ brackets, its just crazy slow. I was hoping someone might suggest some possible improvements or another way to do it.
Here is my code:
program Codex;
const
C_FNAME = 'zavorky.in';
var TmpChar : char;
leftBrackets, rightBrackets : string;
bracketPos : integer;
i,i2,i3 : integer;
Arr, empty : array [0..10000] of String[2];
tfIn : Text;
result : boolean;
begin
leftBrackets := ' ( [ /* ($ <! << ';
rightBrackets := ' ) ] */ $) !> >> ';
i := 0;
result := true;
Assign(tfIn, C_FNAME);
Reset(tfIn);
{ load data into array }
while not eof(tfIn) do
begin
while not eoln(tfIn) do
begin
read(tfIn, TmpChar);
if (TmpChar <> ' ') then begin
if (TmpChar <> '') then begin
Arr[i] := Arr[i] + TmpChar;
end
end
else
begin
i := i + 1;
end
end;
i2 := -1;
while (i2 < 10000) do begin
i2 := i2 + 1;
{if (i2 = 0) then
writeln('STARTED LOOP!');}
if (Arr[i2] <> '') then begin
bracketPos := Pos(' ' + Arr[i2] + ' ',rightBrackets);
if (bracketPos > 0) then begin
if (i2 > 0) then begin
if(bracketPos = Pos(' ' + Arr[i2-1] + ' ',leftBrackets)) then begin
{write(Arr[i2-1] + ' and ' + Arr[i2] + ' - MATCH ');}
Arr[i2-1] := '';
Arr[i2] := '';
{ reindex our array }
for i3 := i2 to 10000 - 2 do begin
Arr[i3 - 1] := Arr[i3+1];
end;
i2 := -1;
end;
end;
end;
end;
end;
{writeln('RESULT: ');}
For i2:=0 to 10 do begin
if (Arr[i2] <> '') then begin
{write(Arr[i2]);}
result := false;
end;
{else
write('M');}
end;
if (result = true) then begin
writeln('true');
end
else begin
writeln('false');
end;
result := true;
{ move to next row in file }
Arr := empty;
i := 0;
readln(tfIn);
end;
Close(tfIn);
readln;
end.
The input data in the file zavorky.in look for example like this:
<< $) >> << >> ($ $) [ ] <! ( ) !>
( ) /* << /* [ ] */ >> <! !> */
I determine for each row whether it is valid or not. Max number of brackets on a row is 10000.
You read chars from your file. File read in byte-by-byte mode is very slow. You need to optimize the way to read the strings (buffers) instead or load the file in memory first.
Hereunder I propose the other way to process the fetched string.
First I declare the consts that will state the brackets that you might have:
const
OBr: array [1 .. 5{6}] of string = ('(', '[', '/*', '<!', '<<'{, 'begin'});
CBr: array [11 .. 15{16}] of string = (')', ']', '*/', '!>', '>>'{, 'end'});
I decided to do this as now you are not limited to the length of the brackets expression and/or number of brackets' types. Every closing and corresponding opening bracket has index difference equal to 10.
And here is the code for the function:
function ExpressionIsValid(const InputStr: string): boolean;
var
BracketsArray: array of byte;
i, Offset, CurrPos: word;
Stack: array of byte;
begin
result := false;
Setlength(BracketsArray, Length(InputStr) + 1);
for i := 0 to High(BracketsArray) do
BracketsArray[i] := 0; // initialize the pos array
for i := Low(OBr) to High(OBr) do
begin
Offset := 1;
Repeat
CurrPos := Pos(OBr[i], InputStr, Offset);
if CurrPos > 0 then
begin
BracketsArray[CurrPos] := i;
Offset := CurrPos + 1;
end;
Until CurrPos = 0;
end; // insert the positions of the opening brackets
for i := Low(CBr) to High(CBr) do
begin
Offset := 1;
Repeat
CurrPos := Pos(CBr[i], InputStr, Offset);
if CurrPos > 0 then
begin
BracketsArray[CurrPos] := i;
Offset := CurrPos + 1;
end;
Until CurrPos = 0;
end; // insert the positions of the closing brackets
Setlength(Stack, 0); // initialize the stack to push/pop the last bracket
for i := 0 to High(BracketsArray) do
case BracketsArray[i] of
Low(OBr) .. High(OBr):
begin
Setlength(Stack, Length(Stack) + 1);
Stack[High(Stack)] := BracketsArray[i];
end; // there is an opening bracket
Low(CBr) .. High(CBr):
begin
if Length(Stack) = 0 then
exit(false); // we can not begin an expression with Closing bracket
if Stack[High(Stack)] <> BracketsArray[i] - 10 then
exit(false) // here we do check if the previous bracket suits the
// closing bracket
else
Setlength(Stack, Length(Stack) - 1); // remove the last opening
// bracket from stack
end;
end;
if Length(Stack) = 0 then
result := true;
end;
Perhaps, we do an extra work by creating a byte array, but it seems that this method is i) more easy to understand and ii) is flexible as we can change the length of brackets expressions for example use and check begin/end brackets etc.
Appended
As soon as I see that the major problem is in organizing block reading of file I give here an idea of how to do it:
procedure BlckRead;
var
f: file;
pc, pline: { PChar } PAnsiChar;
Ch: { Char } AnsiChar;
LngthLine, LngthPc: word;
begin
AssignFile(f, 'b:\br.txt'); //open the file
Reset(f, 1);
GetMem(pc, FileSize(f) + 1); //initialize memory blocks
inc(pc, FileSize(f)); //null terminate the string
pc^ := #0;
dec(pc, FileSize(f)); //return the pointer to the beginning of the block
GetMem(pline, FileSize(f)); //not optimal, but here is just an idea.
pline^ := #0;//set termination => length=0
BlockRead(f, pc^, FileSize(f)); // read the whole file
//you can optimize that if you wish,
//add exception catchers etc.
LngthLine := 0; // current pointers' offsets
LngthPc := 0;
repeat
repeat
Ch := pc^;
if (Ch <> #$D) and (Ch <> #$A) and (Ch <> #$0) then
begin // if the symbol is not string-terminating then we append it to pc
pline^ := Ch;
inc(pline);
inc(pc);
inc(LngthPc);
inc(LngthLine);
end
else
begin //otherwise we terminate pc with Chr($0);
pline^ := #0;
inc(LngthPc);
if LngthPc < FileSize(f) then
inc(pc);
end;
until (Ch = Chr($D)) or (Ch = Chr($A)) or (Ch = Chr($0)) or
(LngthPc = FileSize(f));
dec(pline, LngthLine);
if LngthLine > 0 then //or do other outputs
Showmessage(pline + #13#10 + Booltostr(ExpressionIsValid(pline), true));
pline^ := #0; //actually can be skipped but you know your file structure better
LngthLine := 0;
until LngthPc = FileSize(f);
FreeMem(pline); //free the blocks and close the file
dec(pc, FileSize(f) - 1);
FreeMem(pc);
CloseFile(f);
end;
You are saving all the data into memory (even couple of times) and then you have a lot of checks. I think you are on the right track but there are much easier steps you could follow.
Make an array of integers (default = 0) with length the number of brackets you have (e.g. ' ( [ /* ($ <! << ' ==> 6)
Now to make sure that you are following the requirements. Read the file line by line and take into account only the first 10000. This could help.
Every time you find an element from the first array (e.g. leftBrackets) add +1 to the value of the coresponding index of the array of step 1. Example would be:
'[' ==> checkArray[1] += 1
Do the same for rightBrackets but this time check if the value is larger than 0. If yes then subtract 1 the same way (e.g. ']' ==> checkArray[1] -= 1) otherwise you just found invalid bracket
I hope this helps and Good luck.
I think the following should work, and will be order O(n), where n is the length of the string. First build two function.
IsLeft(bra : TBracket) can determine if a bracket is a left bracket or a right bracket, so IsLeft('<') = TRUE, IsLeft('>>') = FALSE.
IsMatchingPair(bra, ket : TBracket) can determine if two brackets are of the same 'type', so IsMatchingPair('(',')') =TRUE, but IsMatchingPair('{','>>') = FALSE.
Then build a stack TBracketStack with three functions procedure Push(bra : TBracket), and function Pop : TBracket, and function IsEmpty : boolean.
Now the following algorithm should work (with a little extra code required to ensure you don't fall off the end of the string unexpectedly):
BracketError := FALSE;
while StillBracketsToProcess(BracketString) and not BracketError do
begin
bra := GetNextBracket(BracketString);
if IsLeft(bra) then
Stack.Push(bra)
else
BracketError := Stack.IsEmpty or not IsMatchingPair(Stack.Pop,bra)
end;
I have this function called IntToStrLen which converts an integer to a string of a padded size. For example, an integer value of 42 with a size of 4 would result in a string __42 (padded with a given character, space by default).
The problem is when this function is used in bulk (for example 1,000,000 times in a loop), it adds extra weight onto the loop. The loop I'm using this in, without this function, takes about 20 seconds, but with this function, I'm still waiting as of right now for this function to complete, after about 5 minutes.
How can I speed up the following function?
function IntToStrLen(const Value: Integer; const Len: Integer;
const Fill: String = ' '): String;
var
T: String;
begin
Result:= IntToStr(Value); //convert result
if Length(Result) > Len then
Result:= Copy(Result, 1, Len) //forcefully truncate
else if Length(Result) < Len then begin
T:= '';
while Length(T) < (Len - Length(Result)) do //fill space with character
T:= T + Fill;
Result:= T + Result; //return combination
end;
end;
The absolute number one change that you can make here is to avoid heap allocations. Your code presently has multiple heap allocations. Your goal here is to write a function with zero heap allocations.
That means you need the caller to allocate and provide the buffer. Typically they will do so with a stack allocated buffer, which hence costs nothing to allocate (or deallocate). If the caller really needs a string, which is always allocated on the heap, then the caller can wrap your function with a call to SetString.
The function prototype might look like this:
procedure IntToStrLen(const Value, Len: Integer; var Buffer: array of Char;
const Fill: Char = ' ');
The first point to stress here is that Fill must be a Char. Your use of string is inefficient and allows the caller to call a fill "character" with length not equal to one. Doing so would of course break your function because it would return a value with length not equal to Len.
Do note also that the implementation must not call IntToStr because that involves heap allocation. So you need to write your own heap allocation free integer to decimal text conversion code because, astonishingly, the RTL does not offer such functionality. When I do this I use code like so:
procedure DivMod(Dividend, Divisor: Cardinal;
out Quotient, Remainder: Cardinal);
{$IFDEF CPUX86}
asm
PUSH EBX
MOV EBX,EDX
XOR EDX,EDX
DIV EBX
MOV [ECX],EAX
MOV EBX,Remainder
MOV [EBX],EDX
POP EBX
end;
{$ELSE IF Defined(CPUX64)}
asm
.NOFRAME
MOV EAX,ECX
MOV ECX,EDX
XOR EDX,EDX
DIV ECX
MOV [R8],EAX
MOV [R9],EDX
end;
{$ELSE}
{$Message Error 'Unrecognised platform.'}
{$ENDIF}
function CopyIntegerToCharBuffer(const Value: Integer;
var Buffer: array of Char): Integer;
var
i, j: Integer;
val, remainder: Cardinal;
negative: Boolean;
tmp: array [0..15] of Char;
begin
negative := Value<0;
val := abs(Value);
Result := 0;
repeat
DivMod(val, 10, val, remainder);
tmp[Result] := Chr(remainder + ord('0'));
inc(Result);
until val=0;
if negative then begin
tmp[Result] := '-';
inc(Result);
end;
Assert(Result<=Length(Buffer));
i := 0;
j := Result-1;
while i<Result do begin
Buffer[i] := tmp[j];
inc(i);
dec(j);
end;
end;
Now you can make decimal text representations of integers without touching the heap. From there it's a short way to your function.
procedure IntToStrLen(const Value, Len: Integer; var Buffer: array of Char;
const Fill: Char = ' ');
var
tmp: array [0..15] of Char;
i, N: Integer;
begin
Assert(Length(Buffer)>=Len);
N := CopyIntegerToCharBuffer(Value, tmp);
if N>=Len then begin
Move(tmp, Buffer, SizeOf(Char)*Len);
end else begin
for i := 0 to Len-N-1 do begin
Buffer[i] := Fill;
end;
Move(tmp, Buffer[Len-N], SizeOf(Char)*N);
end;
end;
At this point you'll have gained the bulk of the available performance benefits. From here on in you will be into diminishing returns. You could micro-optimise CopyIntegerToCharBuffer as is done in SysUtils._IntToStr32 for instance. Beyond that I'm sure the implementation of IntToStrLen could be optimised with judicious use of assembler. But such optimisations will yield nothing like the benefit you have gained so far from avoiding the heap.
Of course, all this assumes that you have correctly identified your performance bottleneck. It's all too easy to assume that you know where the performance bottleneck is by statically analysing the code. Unless you've actually profiled it carefully expect to find that your intuition is a poor judge of where to invest optimisation effort.
Try this variant
(and you don't treat negative numbers)
function IntToStrLen(const Value: Integer; const Len: Integer;
const Fill: Char = ' '): String;
var
T: String;
begin
Result:= IntToStr(Value);
if Length(Result) > Len then
SetLength(Result, Len) //forcefully truncate
else if Length(Result) < Len then
Result := StringOfChar(Fill, Len - Length(Result)) + Result;
end;
P.S. So strange truncation (2014=>20) - is what you really want?
It's actually my first time in here, so..
What's the problem: standard graph module is too damn slow in drawing.
I have an institute task to make a big program with modules on pascal. Program has several parts but i'm interested in graphical one. I have something like counter (number) in the left corner of the screen and i need it to update fast. But I see every pixel it fills with color, lol.
I'm using Free Pascal 2.6.4.
Asking for some ideas or other ways to draw in a command window fast.
program graphical;
uses
wincrt, graph, querystring, kbd, Timer, sysutils;
const
speedX1 = 0;
speedY1 = 0;
var
//draw part
gd, gm, error, tw, th, i: integer;
speedX2: integer = 10;
speedY2: integer = 10;
speedSize: word;
speedImage: pointer;
size: word;
//qstring part
qrec: qstr;
qtext: AnsiString;
current, ch: char;
//keyboard part
kbrd: kb;
//speedometer part
counter: word = 0;
time: word;
speed: word;
//debug part
c: string;
t: comp;
procedure draw; //screens text
begin
qtext := copy(qrec.text, qrec.qpointer, Length(qrec.text) - qrec.qpointer + 1);
outTextXY(getMaxX div 2, getMaxY div 8, qtext);
end;
begin
//graphic initialization
//gd := detect;
gd := VGA;
gm := VGAHi;
initgraph(gd, gm, '..\bgi');
//checking for errors
error := graphResult;
if (error <> grOk) then
begin
writeln('800x600x256 is not supported');
halt(1);
end;
//querystring initialization
qInit(qrec);
//keyboard initialization
initKeyboard(kbrd);
//timer initialization
TimerOn;
time := 0;
//drawing
setTextStyle(defaultFont, horizDir, 8);
draw;
drawKeyboard(kbrd);
current := getCurrent(qrec);
randomize;
speedX2 := 200;
speedY2 := 100;
repeat
//on timer events
if (isTimer) then
begin
size := ImageSize(speedX1, speedY1, speedX2, speedY2);
GetMem(speedImage, size);
GetImage(speedX1, speedY1, speedX2, speedY2, speedImage^);
PutImage(speedX1, speedY1, speedImage^, 1); FreeMem(speedImage, size);
inc(time);
speed := round(counter/time/25*60);
speed := time;
outTextXY(0, 0, IntToStr(speed));
end;
if KeyPressed then
begin
ch := readkey;
if (ch = #0) then
ch := readkey;
end;
if (UpCase(ch) = UpCase(current)) then
begin
drawKeyboard(kbrd);
draw;
current := getCurrent(qrec);
inc(counter);
end
else
if (counter > 0) then
dec(counter);
until (ch = #27) or (getLength(qrec) < 0);
closegraph;
end.
What I see from the code:
drawKeyboard is called on each iteration, which might not be necessary
for each timer event, memory is allocated and released again, which normally is a rather costly operation. The memory needed seems to be of constant size, so try to move allocation and deallocation out of the loop.
Without fixing those, you'd probably have the same problems with other graph libraries too, so give it a try.
I have written a recursive Tree Function in pascal ( or delphi ) but i had an 'Out of Memory' message when I ran it.
I need to turn the Calculate recursive function in this code to non-recursive function, can you tell me how please :
program testing(input, output);
type
ptr = ^tr;
tr = record
age: byte;
left, right: ptr;
end;
var
topper: ptr;
total, day: longint;
procedure mycreate(var t: ptr);
var
temp:ptr;
begin
new(temp);
temp^.age := 1;
temp^.left := nil;
temp^.right := nil;
t := temp;
end;
procedure gooneday(var t: ptr);
begin
if t^.age <> 5 then
begin
if t^.age = 2 then
mycreate(t^.left)
else if t^.age = 3 then
mycreate(t^.right);
t^.age := t^.age + 1;
total := total + 1;
end;
end;
procedure calculate(var crnt: ptr);
begin
if crnt <> nil then
begin
gooneday(crnt);
calculate(crnt^.left);
calculate(crnt^.right);
end;
end;
begin
total := 0;
mycreate(topper);
day := 0;
while total < 1000000000000 do
begin
total := 0;
day := day + 1;
calculate(topper);
end;
writeln(day);
writeln(total);
end.
Recursive functions use a stack to keep the state of the recursion.
When converting to a loop, you must actually create an explicit stack. You must push and pop elements off the stack within the loop.