How to do this without declaring 200 variables? - string

I think that there is a solution for my problem but I didn't find it, can you help me?
I want to do something like this:
var
a, b, c: string;
d: integer;
begin
a := StringGrid1.Cells[1,1];
b := StringGrid1.Cells[2,1];
c := StringGrid1.Cells[3,1];
d := StrToInt(a) + StrToInt(b) + StrToInt(c);
StringGrid1.Cells[4,1] := IntToStr(d);
end;
But now I need to declare 200 string variables like in this example. Is there anyway a 'shortcut' for this?
This is the loop that I tried:
var
x: integer;
begin
for x := 1 to 200 do
begin
Form2.StringGrid1.Cells[3,209] := IntToStr(x);
end;
end;

var
Total: Integer;
I: Integer;
begin
Total := 0;
for I := 1 to 3 do
Inc(Total, StrToInt(StringGrid1.Cells[I, 1]));
StringGrid1.Cells[4, 1] := IntToStr(Total);
end;

Related

Delphi (10.2): fast Integer conversion to string with separator

Let's say we have this Integer 1234567890, we want it converted to a string with a separator = 1.234.567.890, we could do Format('%n',[1234567890.0]); but it's very slow. I wrote a function to speed it up considerably (more than 2x faster). How could I improve it further, or can you come up with a faster routine?
function MyConvertDecToStrWithDot(Const n: UInt64): string;
Var a,b,x: Integer;
z,step: Integer;
l: SmallInt;
begin
Result := IntToStr(n);
if n < 1000 then Exit;
l := Length(Result);
a := l div 3;
b := l mod 3;
step := b+1;
z := 4;
if b <> 0 then begin
Insert('.',Result,step);
Inc(z,step);
end;
for x := 1 to (a-1) do begin
Insert('.',Result,z);
Inc(z,4);
end;
end;
procedure TForm1.Button1Click(Sender: TObject);
Var a: Integer;
s: string;
begin
PerfTimerInit;
for a := 1 to 1000000 do
s := MyConvertDecToStrWithDot(1234567890);
Memo1.lines.Add(PerfTimerStopMS.ToString);
caption := s;
end;
32-bit
Format: ~230ms
My function: ~79ms
64-bit
Format: ~440ms
My function: ~103ms
In my tests, the following is ever so slightly faster:
function ThousandsSepStringOf(Num: UInt64): string;
const
MaxChar = 30; // Probably, 26 is enough: 19 digits + 7 separators
var
Count: Integer;
Rem: UInt64;
Res: array[0..MaxChar] of Char;
WritePtr: PChar;
begin
WritePtr := #Res[MaxChar];
WritePtr^ := #0;
Count := 0;
while Num > 0 do
begin
DivMod(Num, 10, Num, Rem);
Dec(WritePtr);
WritePtr^ := Char(Byte(Rem) + Ord('0'));
Inc(Count);
if Count = 3 then
begin
Dec(WritePtr);
WritePtr^ := '.';
Count := 0;
end;
end;
if WritePtr^ = '.' then
Inc(WritePtr);
Count := MaxChar - ((NativeInt(WritePtr) - NativeInt(#Res)) shr 1);
SetLength(Result, Count);
Move(WritePtr^, PByte(Result)^, Count * SizeOf(Char));
end;
Tested with:
procedure TestHisCode;
Var
a: Integer;
s: string;
SW: TStopwatch;
begin
Writeln('His code');
SW := TStopwatch.StartNew;
for a := 1 to KLoops do
s := MyConvertDecToStrWithDot(1234567890);
Writeln(SW.ElapsedMilliseconds);
Writeln(s);
Writeln;
end;
procedure TestMyCode;
Var
a: Integer;
s: string;
SW: TStopwatch;
begin
Writeln('My code');
SW := TStopwatch.StartNew;
for a := 1 to KLoops do
s := ThousandsSepStringOf(1234567890);
Writeln(SW.ElapsedMilliseconds);
Writeln(s);
Writeln;
end;
and:
TestHisCode;
TestMyCode;
TestMyCode;
TestHisCode;
TestMyCode;
TestHisCode;
TestHisCode;
TestMyCode;
Haven't properly tested the performance of this, however it should be cross-platform and locale independent:
function Thousands(const ASource: string): string;
var
I, LLast: Integer;
begin
Result := ASource;
LLast := Length(Result);
I := LLast;
while I > 0 do
begin
if (LLast - I + 1) mod 3 = 0 then
begin
Insert(FormatSettings.ThousandSeparator, Result, I);
Dec(I, 2);
end
else
Dec(I);
end;
end;
Note: It obviously just works on integers
It's better to insert the separators directly while constructing the string instead of inserting separators later into the converted string because each insertion involves a lot of data movements and performance degradation. Besides avoid the division by 3 may improve performance a bit
This is what I get from my rusty Pascal after decades not using it
uses strutils;
function FormatNumber(n: integer): string;
var digit: integer;
count: integer;
isNegative: boolean;
begin
isNegative := (n < 0);
if isNegative then n := -n;
Result := '';
count := 3;
while n <> 0 do begin
digit := n mod 10;
n := n div 10;
if count = 0 then begin
Result := Result + '.';
count := 3;
end;
Result := Result + chr(ord('0') + digit);
dec(count);
end;
if isNegative then Result := Result + '-';
Result := reversestring(Result);
end;
See it in action: http://ideone.com/6O3e8w
It's also faster to just assign the characters directly instead of using concatenation operator/function like Victoria suggested. This is the improved version with only unsigned types
type string28 = string[28];
function FormatNumber(n: UInt64): string28;
var digit: integer;
length: integer;
count: integer;
c: char;
begin
count := 3;
length := 0;
while n <> 0 do begin
digit := n mod 10;
n := n div 10;
if count = 0 then begin
inc(length);
Result[length] := '.';
count := 3;
end;
inc(length);
Result[length] := chr(ord('0') + digit);
dec(count);
end;
for count := 1 to (length + 1) div 2 do begin
c := Result[count];
Result[count] := Result[length - count + 1];
Result[length - count + 1] := c;
end;
setlength(Result, length);
FormatNumber := Result;
end;
If the operation is done millions of times and is really a bottleneck after profiling, it's better to do in multiple threads along with SIMD

Strange result in multithreading using Delphi XE

I am trying to use multithreading in Delphi XE.
The task is following I have to create 4 threads. Each thread draw colored circle in Paintbox at predefined area, For example FIRST thread draw only red circles in first quoter of the Paintbox, the SECOND thread draw only yellow circles in the second quoter, and so on.
I have defined following class
const
NumberOfIterations = 100000;
NumberOfTreads = 4;
TCalcThread = class(TThread)
private
FIdx: Integer;
FHits: Cardinal;
V: array of Integer;
xPaintBox1: TPaintBox;
protected
procedure Execute; override;
public
constructor Create(Idx: Integer; vPaintBox: TPaintBox);
property Hits: Cardinal read FHits;
end;
In main code I do the following:
procedure TForm11.Button1Click(Sender: TObject);
var
thrarr: array[0..NumberOfTreads - 1] of TCalcThread;
hndarr: array[0..NumberOfTreads - 1] of THandle;
i, a, t: Integer;
x, y: Integer;
begin
caption := '';
PaintBox1.Canvas.Brush.Color := clWhite;
PaintBox1.Canvas.fillrect(PaintBox1.Canvas.ClipRect);
for i := 0 to NumberOfTreads - 1 do
begin
thrarr[i] := TCalcThread.Create(i, PaintBox1);
hndarr[i] := thrarr[i].Handle;
end;
WaitForMultipleObjects(NumberOfTreads, #hndarr, True, INFINITE);
for i := 0 to NumberOfTreads - 1 do
thrarr[i].Free;
end;
The thread Create and Execute methods are defined as following:
constructor TCalcThread.Create(Idx: Integer; vPaintBox: TPaintBox);
begin
FIdx := Idx;
FHits := 0;
xPaintBox1 := vPaintBox;
case FIdx of
0: xPaintBox1.Canvas.Pen.Color := clRed;
1: xPaintBox1.Canvas.Pen.Color := clYellow;
2: xPaintBox1.Canvas.Pen.Color := clBlue;
3: xPaintBox1.Canvas.Pen.Color := clMoneyGreen;
end;
xPaintBox1.Canvas.Brush.Color := xPaintBox1.Canvas.Pen.Color;
inherited Create(False);
end;
procedure TCalcThread.Execute;
var
i, start, finish: Integer;
x, y: Integer;
begin
start := (NumberOfIterations div NumberOfTreads) * FIdx;
finish := start + (NumberOfIterations div NumberOfTreads) - 1;
for i := start to finish do
begin
case FIdx of
0: begin
x := Random(200) + 1;
end;
1: begin
x := Random(200) + 201;
end;
2: begin
x := Random(200) + 401;
end;
3: begin
x := Random(200) + 601;
end;
end;
y := Random((xPaintBox1.height )) + 1;
xPaintBox1.Canvas.Ellipse(X - 5, Y - 5, X + 5, Y + 5);
end;
end;
As a result I am getting a few circles in three areas with the same color, and a lot of circles in one area (the same color). What I am doing wrong?

Best way to replace every third character in a string in delphi

What's the most efficient way to replace every third character of the same type in a string?
I have a string like this:
str := 'c1'#9'c2'#9'c3'#9'c4'#9'c5'#9'c6'#9'
I want to replace every third #9 by #13#10, so that i get:
str1 := 'c1'#9'c2'#9'c3'#13#10'c4'#9'c5'#9'c6'#13#10'
I would do this in this way:
i:=0;
newStr:='';
lastPos := Pos(str,#9);
while lastPos > 0 do begin
if i mod 3 = 2 then begin
newStr := newStr + Copy(str,1,lastPos-1) + #13#10;
end else begin
newStr := newStr + Copy(str,1,lastPos);
end;
str := Copy(str,lastPos+1,MaxInt);
i := i + 1;
lastPos := Pos(str,#9);
end;
newStr := Copy(str,1,MaxInt);
But thats a lot of copying. Is there a string manipulation function to do this?
I think the problem as stated doesn't match the code you provided. Is every third character a #9? If so, do you want to change every third appearance of #9 for #13#10?
If so, I would do it this way:
function test(str: string): string;
var
i, c, l: integer;
begin
l := Length(str);
SetLength(Result, l + l div 9);
c := 1;
for i := 1 to l do
begin
if (i mod 9 = 0) and (i > 0) then
begin
Result[c] := #13;
Inc(c);
Result[c] := #10;
end
else
Result[c] := str[i];
Inc(c);
end;
end;
I actually have no idea if this function performs well. But given that the constraints aren't clear, I guess so.
If the position of the #9 character is unknown then this solution won't work at all.
Edit: as David points out, this is not nearly equivalent to the original code posted. This seems to work, but it requires two passes on the original string. The thing is, to know if its more efficient or not we need to know more about the input and context.
function OccurrencesOfChar(const S: string; const C: char): integer;
var
i: integer;
begin
result := 0;
for i := 1 to Length(S) do
if S[i] = C then
inc(result);
end;
function Test(str: string): string;
var
len, n, C, i: integer;
begin
C := 1;
len := Length(str);
n := OccurrencesOfChar(str, #9);
SetLength(result, len + n div 3);
n := 1;
for i := 1 to len do
begin
if str[i] = #9 then
begin
if n mod 3 = 0 then
begin
result[C] := #13;
inc(C);
result[C] := #10;
end
else
result[C] := #9;
Inc(n);
end
else
result[C] := str[i];
inc(C);
end;
end;
I expect this question will be closed, but just for fun, that would be my proposal.
Function Replace(const Instr:String;Re:Char;const ReWith:String):String;
var
i,o,cnt,l:Integer;
begin
cnt:=0;
o:=0;
SetLength(Result,Length(Instr)*Length(ReWith));// just for security
for I := 1 to Length(Instr) do
begin
if Instr[i]=Re then inc(cnt);
if cnt=3 then
begin
for l := 1 to Length(ReWith) do
begin
inc(o);
Result[o] := ReWith[l];
end;
cnt := 0;
end
else
begin
inc(o);
Result[o] := Instr[i];
end;
end;
SetLength(Result,o);
end;
procedure TForm3.Button1Click(Sender: TObject);
begin
Edit2.Text := Replace(Edit1.Text,'A','xxx')
end;
I would probably do something like this (coded in the browser). It only needs one string resize and should have less movement of data around. I exit when I have made the last replacement or if it didn't need any:
procedure ReplaceXChar(var aStringToReplace: string; const aIteration:
Integer; const aChar: Char; const aReplacement: string);
var
replaceCount: Integer;
cntr: Integer;
outputCntr: Integer;
lengthToReplace: Integer;
begin
// Find the number to replace
replaceCount := 0;
for cntr := 1 to Length(aStringToReplace) do
begin
if aStringToReplace[cntr] = aChar then
Inc(replaceCount);
end;
if replaceCount >= aIteration then
begin
// Now replace them
lengthToReplace := Length(aReplacement);
cntr := Length(aStringToReplace);
SetLength(aStringToReplace, cntr +
(replaceCount div aIteration) * (lengthToReplace - 1));
outputCntr := Length(aStringToReplace);
repeat
if aStringToReplace[cntr] = aChar then
begin
if (replaceCount mod aIteration) = 0 then
begin
Dec(outputCntr, lengthToReplace);
Move(aReplacement[1], aStringToReplace[outputCntr+1],
lengthToReplace * SizeOf(Char));
end
else
begin
aStringToReplace[outputCntr] := aStringToReplace[cntr];
Dec(outputCntr);
end;
Dec(replaceCount);
end
else
begin
aStringToReplace[outputCntr] := aStringToReplace[cntr];
Dec(outputCntr);
end;
Dec(cntr);
until replaceCount = 0;
end;
end;
Usage would be like this:
var
myString: String;
begin
myString := 'c1'#9'c2'#9'c3'#9'c4'#9'c5'#9'c6'#9;
ReplaceXChar(myString, 3, #9, #13#10);
ShowMessage(myString);
end;

Delete numbers from a String

I'd like to know how I can delete numbers from a String. I try to use StringReplace and I don't know how to tell the function that I want to replace numbers.
Here's what I tried:
StringReplace(mString, [0..9], '', [rfReplaceAll, rfIgnoreCase]);
Simple but effective. Can be optimized, but should get you what you need as a start:
function RemoveNumbers(const aString: string): string;
var
C: Char;
begin
Result := '';
for C in aString do begin
if not CharInSet(C, ['0'..'9']) then
begin
Result := Result + C;
end;
end;
end;
Pretty quick inplace version.
procedure RemoveDigits(var s: string);
var
i, j: Integer;
pc: PChar;
begin
j := 0;
pc := PChar(#s[1]);
for i := 0 to Length(s) - 1 do
if pc[i] in ['0'..'9'] then
//if CharInSet(pc[i], ['0'..'9']) for Unicode version
Inc(j)
else
pc[i - j] := pc[i];
SetLength(s, Length(s) - j);
end;
This has the same output as Nick's version, but this is more than 3 times as fast with short strings. The longer the text, the bigger the difference.
function RemoveNumbers2(const aString: string): string;
var
C:Char; Index:Integer;
begin
Result := '';
SetLength(Result, Length(aString));
Index := 1;
for C in aString do
if not CharInSet(C, ['0' .. '9']) then
begin
Result[Index] := C;
Inc(Index);
end;
SetLength(Result, Index-1);
end;
Don't waste precious CPU cycles if you don't have to.
Well I was tired of looking for already build functions so I've create my own:
function RemoveNumbers(const AValue: string): string;
var
iCar : Integer;
mBuffer : string;
begin
mBuffer := AValue;
for iCar := Length(mBuffer) downto 1 do
begin
if (mBuffer[iCar] in ['0'..'9']) then
Delete(mBuffer,iCar,1);
end;
Result := mBuffer;
end;
use this
function RemoveNonAlpha(srcStr : string) : string;
const
CHARS = ['0'..'9'];
var i : integer;
begin
result:='';
for i:=0 to length(srcStr) do
if (srcstr[i] in CHARS) then
result:=result+srcStr[i];
end ;
you can call it like this
edit2.text:=RemoveNonAlpha(edit1.text);
StringReplace does not accept a set as the second argument. Maybe someone will have a more suitable approach, but this works:
StringReplace(mString, '0', '', [rfReplaceAll, rfIgnoreCase]);
StringReplace(mString, '1', '', [rfReplaceAll, rfIgnoreCase]);
StringReplace(mString, '2', '', [rfReplaceAll, rfIgnoreCase]);
etc.

CustomPage for Serial Number in Inno Setup

How to create CustomPage in Inno Setup with Edit Boxes for Serial Number?
E.g. 6x5chars or 7x5chars?
Script should check if all boxes are filled before Next button become available.
It would be also good if there could be Copy/Paste function implemented that would allow to fill up all Edit Boxes if the clipboard content matches the serial number pattern.
Here is one approach that uses the custom page where the separate edit boxes are created. You only need to specify the value for the SC_EDITCOUNT constant where the number of edit boxes is defined and the SC_CHARCOUNT what is the number of characters that can be entered into these edit boxes. If you are in the first edit box you may paste the whole serial number if it's in the format by the pattern delimited by the - char (the TryPasteSerialNumber function here). To get the serial number from the edit boxes it's enough to call GetSerialNumber where you can specify also a delimiter for the output format (if needed).
[Setup]
AppName=Serial number project
AppVersion=1.0
DefaultDirName={pf}\Serial number project
[code]
function SetFocus(hWnd: HWND): HWND;
external 'SetFocus#user32.dll stdcall';
function OpenClipboard(hWndNewOwner: HWND): BOOL;
external 'OpenClipboard#user32.dll stdcall';
function GetClipboardData(uFormat: UINT): THandle;
external 'GetClipboardData#user32.dll stdcall';
function CloseClipboard: BOOL;
external 'CloseClipboard#user32.dll stdcall';
function GlobalLock(hMem: THandle): PAnsiChar;
external 'GlobalLock#kernel32.dll stdcall';
function GlobalUnlock(hMem: THandle): BOOL;
external 'GlobalUnlock#kernel32.dll stdcall';
var
SerialPage: TWizardPage;
SerialEdits: array of TEdit;
const
CF_TEXT = 1;
VK_BACK = 8;
SC_EDITCOUNT = 6;
SC_CHARCOUNT = 5;
SC_DELIMITER = '-';
function IsValidInput: Boolean;
var
I: Integer;
begin
Result := True;
for I := 0 to GetArrayLength(SerialEdits) - 1 do
if Length(SerialEdits[I].Text) < SC_CHARCOUNT then
begin
Result := False;
Break;
end;
end;
function GetClipboardText: string;
var
Data: THandle;
begin
Result := '';
if OpenClipboard(0) then
try
Data := GetClipboardData(CF_TEXT);
if Data <> 0 then
Result := String(GlobalLock(Data));
finally
if Data <> 0 then
GlobalUnlock(Data);
CloseClipboard;
end;
end;
function GetSerialNumber(ADelimiter: Char): string;
var
I: Integer;
begin
Result := '';
for I := 0 to GetArrayLength(SerialEdits) - 1 do
Result := Result + SerialEdits[I].Text + ADelimiter;
Delete(Result, Length(Result), 1);
end;
function TrySetSerialNumber(const ASerialNumber: string; ADelimiter: Char): Boolean;
var
I: Integer;
J: Integer;
begin
Result := False;
if Length(ASerialNumber) = ((SC_EDITCOUNT * SC_CHARCOUNT) + (SC_EDITCOUNT - 1)) then
begin
for I := 1 to SC_EDITCOUNT - 1 do
if ASerialNumber[(I * SC_CHARCOUNT) + I] <> ADelimiter then
Exit;
for I := 0 to GetArrayLength(SerialEdits) - 1 do
begin
J := (I * SC_CHARCOUNT) + I + 1;
SerialEdits[I].Text := Copy(ASerialNumber, J, SC_CHARCOUNT);
end;
Result := True;
end;
end;
function TryPasteSerialNumber: Boolean;
begin
Result := TrySetSerialNumber(GetClipboardText, SC_DELIMITER);
end;
procedure OnSerialEditChange(Sender: TObject);
begin
WizardForm.NextButton.Enabled := IsValidInput;
end;
procedure OnSerialEditKeyDown(Sender: TObject; var Key: Word;
Shift: TShiftState);
var
Edit: TEdit;
EditIndex: Integer;
begin
Edit := TEdit(Sender);
EditIndex := Edit.TabOrder - SerialEdits[0].TabOrder;
if (EditIndex = 0) and (Key = Ord('V')) and (Shift = [ssCtrl]) then
begin
if TryPasteSerialNumber then
Key := 0;
end
else
if (Key >= 32) and (Key <= 255) then
begin
if Length(Edit.Text) = SC_CHARCOUNT - 1 then
begin
if EditIndex < GetArrayLength(SerialEdits) - 1 then
SetFocus(SerialEdits[EditIndex + 1].Handle)
else
SetFocus(WizardForm.NextButton.Handle);
end;
end
else
if Key = VK_BACK then
if (EditIndex > 0) and (Edit.Text = '') and (Edit.SelStart = 0) then
SetFocus(SerialEdits[EditIndex - 1].Handle);
end;
procedure CreateSerialNumberPage;
var
I: Integer;
Edit: TEdit;
DescLabel: TLabel;
EditWidth: Integer;
begin
SerialPage := CreateCustomPage(wpWelcome, 'Serial number validation',
'Enter the valid serial number');
DescLabel := TLabel.Create(SerialPage);
DescLabel.Top := 16;
DescLabel.Left := 0;
DescLabel.Parent := SerialPage.Surface;
DescLabel.Caption := 'Enter valid serial number and continue the installation...';
DescLabel.Font.Style := [fsBold];
SetArrayLength(SerialEdits, SC_EDITCOUNT);
EditWidth := (SerialPage.SurfaceWidth - ((SC_EDITCOUNT - 1) * 8)) div SC_EDITCOUNT;
for I := 0 to SC_EDITCOUNT - 1 do
begin
Edit := TEdit.Create(SerialPage);
Edit.Top := 40;
Edit.Left := I * (EditWidth + 8);
Edit.Width := EditWidth;
Edit.CharCase := ecUpperCase;
Edit.MaxLength := SC_CHARCOUNT;
Edit.Parent := SerialPage.Surface;
Edit.OnChange := #OnSerialEditChange;
Edit.OnKeyDown := #OnSerialEditKeyDown;
SerialEdits[I] := Edit;
end;
end;
procedure CurPageChanged(CurPageID: Integer);
begin
if CurPageID = SerialPage.ID then
WizardForm.NextButton.Enabled := IsValidInput;
end;
procedure InitializeWizard;
begin
CreateSerialNumberPage;
end;
And here is how it looks like:
You can make Inno prompt the user for a serial key by adding an CheckSerial() event function.
If you want more control over the page, you can use one of the stock pages (CreateInput...Page) or a custom page in the setup wizard using CreateCustomPage() and adding controls as you require.
See the codedlg.iss example included with Inno setup.
The simplest way to add a Serial key box, beneath the Name and Organisation text fields, is to add something like the following to your iss file.
[Code]
function CheckSerial(Serial: String): Boolean;
begin
// serial format is XXXX-XXXX-XXXX-XXXX
Serial := Trim(Serial);
if Length(Serial) = 19 then
result := true;
end;
This can be usefully combined with
[Setup]
DefaultUserInfoSerial={param:Serial}
which will fill in the serial if previously entered for the install.

Resources