get multiple inputs instead of one - pascal

So, I'm new here and I'm new to programming generally. I made this program that I needed for a project (a pascal program using Lazarus) that allows me to get a kind of list generated by replacing * by numbers. What I need is to be able to give it multiple codes to process at once (maximum 10) instead of entering every code at once.
program b;
{$mode objfpc}
{$H+}
uses sysutils;
var
sourcestr: string;
resultstr: string;
n: integer;
begin
writeln('provide a string:');
readln(sourcestr);
for n := 0 to 99 do begin
resultstr := StringReplace(sourcestr, '*', IntToStr(n div 10), []);
resultstr := StringReplace(resultstr, '*', IntToStr(n mod 10), []);
resultStr := resultStr + ':password';
writeln(resultstr);
end;
end.
I hope you could help me with this and thanks in advance.

The code below shows how to replace an arbitrary number of pairs of asterisks by the two substitute characters you are generating.
for n := 0 to 9 do begin
resultstr := sourcestr;
while Pos('*', resultstr) > 0 do begin
stringReplace(resultstr, '*', IntToStr(n div 10), []);
resultstr := StringReplace(resultstr, '*', IntToStr(n mod 10), []);
end;
resultStr := resultStr + ':password';
writeln(resultstr);
end;
It uses the Pos function in a while loop to replace the asterisk pairs. Be aware that the output may not be exactly what you need, because in each generated resultstr you will get the same substitute characters replacing each pair of asterisks, i.e.
with an input of
a ** b ** c
the resultstrs generated will be like
a00b00c
a11b11c
which may not be what you need. If not, changing the code to do what you do need is left as an exercise for the reader, as they say.
Btw, it occurred to me later that maybe you are asking how to input and process several lines'-worth of user input. One way to do that would be to read the lines into a TStringList (see online help) and then process that. Something like:
var
TL : TStringList;
sourcestr : String;
begin
TL := TStringList.Create;
repeat
readln(sourcestr);
if sourcestr <> '' then
TL.Add(sourcestr);
until sourcestr = '';
for i := 0 to TL.Count - 1 do begin
sourcestr := TL[i];
// process sourcestr however you want
end;
TL.Free;
though you could, of course, simply process sourcestr as you go along, in the repeat..until loop.

Related

Wrong use of 'file of char'

Im having problems with this code, I have two file of char, one is filed with information about books, and the other is empty, i have to write in SAL some information from S and then show the total of how many books match the first 2 digits of the code and how many are R and how many are T. The code, does write the information form S to Sal, but when its supposed to show the totals it appears ERORR 100 on screen. I read about it and it says that it is a problem with 'Disk read error' and that *This error typically occurs, if you "seed" a non-existent record of a typed file and try to read/write it. *, i really dont undertand.
I've benn trying to figure it out, but I haven't been able to. I notice that if I dont put 'WHILE NOT EOF(S) DO' the error does not appear, but of course i need the while, if someone is able to point out my mistakes i would really apreciate it.
This is the code:
uses crt;
var
i : byte;
s,sal: file of char;
v,l1,l2: char;
cs,cn,cl: integer;
pn,ps,tot: integer;
BEGIN
cs:=0; cn:=0; i:=0; cl:=0;
Assign (s, 'C:\Users\te\Documents\s.txt');
{$I-}
Reset (s);
{$I+}
if IOResult <> 0 then
begin
writeln('Error');
halt(2);
end;
Assign (sal, 'C:\Users\te\Documents\sal.txt');
{$I-}
Rewrite (sal);
IOResult;
{$I+}
if IOResult <> 0 then
halt(2);
writeln('Please write the code of the book, only 2 digits');
read(L1);read(L2);
read(s,v);
while (not eof(s)) do
begin
for i:=1 to 2 do
read(s,v);
if (v = '0') then
begin
read(s,v);
if (v = '1') or (v = '2') then
begin
for i:=1 to 5 do
read(s,v);
if (v = 'R') then
begin
read(s,v);
cs:= cs + 1;
end
else
begin
if (v = 'T') then
begin
cn:= cn + 1;
read(s,v);
end;
end;
while (v <> '-') do
read(s,v);
while (v = '-') do
read(s,v);
if (v = L1) then
begin
write(sal, v);
read(s,v);
if (v = L2) then
begin
write(sal,v);
read(s,v);
cl:= cl + 1;
end;
end;
while ( v <> '/') do
begin
write(sal,v);
read(s,v);
end;
write(sal, '-');
end
else
begin
for i:= 1 to 5 do
read(s,v);
if (v = 'R') then
cs:= cs + 1
else
cn:= cn + 1;
if (v = L1) then
read(s,v);
if (v = L2) then
begin
cl:= cl + 1;
read(s,v);
end;
end;
end
else
begin
for i:= 1 to 5 do
read(s,v);
if (v = 'R') then
cs:= cs + 1
else
cn:= cn + 1;
if (v = L1) then
read(s,v);
if (v = L2) then
begin
cl:= cl + 1;
read(s,v);
end;
end;
end;
tot:= cs + cn;
ps:= (cs * 100) div tot;
pn:= (cn * 100) div tot;
writeln('TOTAL ',cl);
writeln();
writeln(ps,'% and',pn,'%');
The file S content:
02022013Rto kill a mockingbird-1301/02012014Tpeter pan-1001/02032013Thowto-2301/02012012Tmaze runner-1001/02012012Tmaze runner-1001/02012012Tmaze runner-1001/$
I really just need someone else's point of view on this code, I think maybe the algorithm is flawed.
Thanks
(After your edit, i see that your code now compiles w/o error in FPC, so I'm glad you've managed to fix the error yourself)
As this is obviously coursework, I'm not going to fix your code for you and in any case the wayEven so, I'm afraid you are going about this is completely wrong.
Basically, the main thing wrong with your code is that you are trying to control what happens as your read the source file character by character. Quite frankly, that's a hopeless way of trying to do it, because it makes the execution flow unnecessarily complicated and littered with ifs, buts and loops. It also requires you to keep mental track of what you are trying to do at any given step, and the resulting code is inherently not self-documenting - imagine if you came back to your code in six months, could you tell at a glance how it works and what it does? I certsinly couldn't personally.
You need to break the task down in a different way. Instead of analysing the problem from the bottom up ("If I read this character next, then what I need to do next is ...') do it from the top down: Although your input file is a file of char, it contains a series of strings, separated by a / character and finally terminated by a $ (but this terminator does not really matter). So what you need to do is to read these strings one-by-one; once you've got one, check whether it's the one you're looking for: if it is. process it however you need to, otherwise read the next one until you reach the end of the file.
Once you have successfully read one of the book strings, you can then split it up into the various fields it's composed of. The most useful function for doing this splitting is probably Copy, which lets you extract substrings from a string - look it up in the FPC help. I've included functions ExtractTitle and ExtractPreamble which show you what you need to do to write similar functions to extract the T/R code and the numeric code which follows the hyphen. Btw, if you need to ask a similar q in the future, it would be very helpful if you include a description of the layout and meaning of the various fields in the file.
So, what I'm going to show you is how to read the series of strings in your S.Txt by building them character-by-character. In the code below, I do this using a function GetNextBook which I hope is reasonable self-explanatory. The code uses this function in a while loop to fill the BookRecord string variable. Then, it simply writes the BookRecord to the console. What your code should do, of course, is to process the BookRecord contents to see if it is the one you are looking for and then do whether the remainder of your task is.
I hope you will agree that the code below is a lot clearer, a lot shorter and will be a lot easier to extend in future than the code in your q. They key to structuring a program this way is to break the program's task into a series of functions and procedures which each perform a single sub-task. Writing the program that way makes it easier to "re-wire" the program to change what it does, without having to rewrite the innards of the functions/procedures.
program fileofcharproject;
uses crt;
const
sContents = '02022013Rto kill a mockingbird-1301/02012014Tpeter pan-1001/02032013Thowto-2301/02012012Tmaze runner-1001/02012012Tmaze runner-1001/02012012Tmaze runner-1001/$';
InputFileName = 'C:\Users\MA\Documents\S.Txt';
OutputFileName = 'C:\Users\MA\Documents\Sal.Txt';
type
CharFile = File of Char; // this is to permit a file of char to be used
// as a parameter to a function/procedure
function GetNextBook(var S : CharFile) : String;
var
InputChar : Char;
begin
Result := '';
InputChar := Chr(0);
while not Eof(S) do begin
Read(S, InputChar);
// next, check that the char we've read is not a '/'
// if it is a '/' then exit this while loop
if (InputChar <> '/') then
Result := Result + InputChar
else
Break;
end;
end;
function ExtractBookTitle(BookRecord : String) : String;
var
p : Integer;
begin
Result := Copy(BookRecord, 10, Length(BookRecord));
p := Pos('-', Result);
if p > 0 then
Result := Copy(Result, 1, p - 1);
end;
procedure AddToOutputFile(var OutputFile : CharFile; BookRecord : String);
var
i : Integer;
begin
for i := 1 to Length(BookRecord) do
write(OutputFile, BookRecord[i]);
write(OutputFile, '/');
end;
function ExtractPreamble(BookRecord : String) : String;
begin
Result := Copy(BookRecord, 1, 8);
end;
function TitleMatches(PartialTitle, BookRecord : String) : Boolean;
begin
Result := Pos(PartialTitle, ExtractBookTitle(BookRecord)) > 0;
end;
var
i : Integer; //byte;
s,sal: file of char;
l1,l2: char;
InputChar : Char;
BookFound : Boolean;
cs,cn,cl: integer;
pn,ps,tot: integer;
Contents : String;
BookRecord : String;
PartialTitle : String;
begin
// First, create S.Txt so we don't have to make any assumptions about
// its contents
Contents := sContents;
Assign(s, InputFileName);
Rewrite(s);
for i := 1 to Length(Contents) do begin
write(s, Contents[i]); // writes the i'th character of Contents to the file
end;
Close(s);
cs:=0; cn:=0; i:=0; cl:=0;
// Open the input file
Assign (s, InputFileName);
{$I-}
Reset (s);
{$I+}
if IOResult <> 0 then
begin
writeln('Error');
halt(2);
end;
// Open the output file
Assign (sal, OutputFileName);
{$I-}
Rewrite (sal);
IOResult;
{$I+}
if IOResult <> 0 then
halt(2);
// the following reads the BookRecords one-by-one and copies
// any of them which match the partial title to sal.txt
writeln('Enter part of a book title, followed by [Enter]');
readln(PartialTitle);
while not Eof(s) do begin
BookRecord := GetNextBook(S);
writeln(BookRecord);
writeln('Preamble : ', ExtractPreamble(BookRecord));
writeln('Title : ', ExtractBookTitle(BookRecord));
if TitleMatches(PartialTitle, BookRecord) then
AddToOutputFile(sal, BookRecord);
end;
// add file '$' to sal.txt
write(sal, '$');
Close(sal);
Close(s);
writeln('Done, press any key');
readln;
end.

How to remove spaces from string with while do operator? Pascal

I have text and I need to remove spaces from beginning of text and from end of text. And I can do it only with while do operator. How can I do that? Here's program code
program RandomTeksts;
uses crt;
var
t:String;
l, x, y:Integer;
const tmin=1; tmax=30;
label
Start,
end;
begin
Start:
clrscr;
writeln('write text (from ',tmin,' to ',tmax,' chars): ');
readln(t);
l:=length(t);
if (l<tmin) or (l>tmax) then
begin
writeln('Text doesn't apply to rules!');
goto end;
end;
clrscr;
begin
randomize;
repeat
x:=random(52+1);
y:=random(80+1);
textcolor(white);
gotoxy(x,y);
writeln(t);
delay(700);
clrscr;
until keypressed;
end;
ord (readkey)<>27 then
goto Start;
end:
end.
Academic problem: Remove leading and trailing spaces from a string using a while loop.
How do we approach this problem?
Well, we certainly would like to create a function that trims a string. This way, we can simply call this function every time we need to perform such an operation. This will make the code much more readable and easier to maintain.
Clearly, this function accepts a string and returns a string. Hence its declaration should be
function Trim(const AText: string): string;
Here I follow the convention of prefixing arguments by "A". I also use the const prefix to tell the compiler I will not need to modify the argument within the function; this can improve performance (albeit very slightly).
The definition will look like this:
function Trim(const AText: string): string;
begin
// Compute the trimmed string and save it in the result variable.
end;
A first attempt
Now, let's attempt to implement this algorithm using a while loop. Our first attempt will be very slow, but fairly easy to follow.
First, let us copy the argument string AText to the result variable; when the function returns, the value of result will be its returned value:
result := AText;
Now, let us try to remove leading space characters.
while result[1] = ' ' do
Delete(result, 1, 1);
We test if the first character, result[1], is a space character and if it is, we use the Delete procedure to remove it from the string (specifically, Delete(result, 1, 1) removes 1 character from the string starting at the character with index 1). Then we do this again and again, until the first character is something other than a space.
For example, if result initially is ' Hello, World!', this will make it equal to 'Hello, World!'.
Full code, so far:
function Trim(const AText: string): string;
begin
result := AText;
while result[1] = ' ' do
Delete(result, 1, 1);
end;
Now try this with a string that consists only of space characters, such as ' ', or the empty string, ''. What happens? Why?
Think about it.
Clearly, in such a case, result will sooner or later be the empty string, and then the character result[1] doesn't exist. (Indeed, if the first character of result would exist, result would be of length at least 1, and so it wouldn't be the empty string, which consists of precisely zero characters.)
Accessing a character that doesn't exist will make the program crash.
To fix this bug, we change the loop to this:
while (Length(result) >= 1) and (result[1] = ' ') do
Delete(result, 1, 1);
Due to a technique known as 'lazy boolean evaluation' (or 'short-circuit evaluation'), the second operand of the and operator, that is, result[1] = ' ', will not even run if the first operand, in this case Length(result) >= 1, evaluates to false. Indeed, false and <anything> equals false, so we already know the value of the conjunction in this case.
In other words, result[1] = ' ' will only be evaluated if Length(result) >= 1, in which case there will be no bug. In addition, the algorithm produces the right answer, because if we eventually find that Length(result) = 0, clearly we are done and should return the empty string.
Removing trailing spaces in a similar fashion, we end up with
function Trim(const AText: string): string;
begin
result := AText;
while (Length(result) >= 1) and (result[1] = ' ') do
Delete(result, 1, 1);
while (Length(result) >= 1) and (result[Length(result)] = ' ') do
Delete(result, Length(result), 1);
end;
A tiny improvement
I don't quite like the space character literals ' ', because it is somewhat difficult to tell visually how many spaces there are. Indeed, we might even have a different whitespace character than a simple space. Hence, I would write #32 or #$20 instead. 32 (decimal), or $20 (hexadecimal), is the character code of a normal whitespace.
A (much) better solution
If you try to trim a string containing many million of characters (including a few million leading and trailing spaces) using the above algorithm, you'll notice that it is surprisingly slow. This is because we in every iteration need to reallocate memory for the string.
A much better algorithm would simply determine the number of leading and trailing spaces by reading characters in the string, and then in a single step perform a memory allocation for the new string.
In the following code, I determine the index FirstPos of the first non-space character in the string and the index LastPos of the last non-space character in the string:
function Trim2(const AText: string): string;
var
FirstPos, LastPos: integer;
begin
FirstPos := 1;
while (FirstPos <= Length(AText)) and (AText[FirstPos] = #32) do
Inc(FirstPos);
LastPos := Length(AText);
while (LastPos >= 1) and (AText[LastPos] = #32) do
Dec(LastPos);
result := Copy(AText, FirstPos, LastPos - FirstPos + 1);
end;
I'll leave it as an exercise for the reader to figure out the precise workings of the algorithm. As a bonus exercise, try to benchmark the two algorithms: how much faster is the last one? (Hint: we are talking about orders of magnitude!)
A simple benchmark
For the sake of completeness, I wrote the following very simple test:
const
N = 10000;
var
t: cardinal;
dur1, dur2: cardinal;
S: array[1..N] of string;
S1: array[1..N] of string;
S2: array[1..N] of string;
i: Integer;
begin
Randomize;
for i := 1 to N do
S[i] := StringOfChar(#32, Random(10000)) + StringOfChar('a', Random(10000)) + StringOfChar(#32, Random(10000));
t := GetTickCount;
for i := 1 to N do
S1[i] := Trim(S[i]);
dur1 := GetTickCount - t;
t := GetTickCount;
for i := 1 to N do
S2[i] := Trim2(S[i]);
dur2 := GetTickCount - t;
Writeln('trim1: ', dur1, ' ms');
Writeln('trim2: ', dur2, ' ms');
end.
I got the following output:
trim1: 159573 ms
trim2: 484 ms

Pascal - Writing Strange Characters

I am attempting to write a comment stripper in pascal. I run my code and pass it a C source code file and it strips the comments from the file and prints the result to terminal.
I am fairly new to pascal. I am getting some very strange output and I cannot figure out why. The code checks for comments line by line and prints characters one at a time. The comment stripper is printing what seems to be random characters whenever it reaches the start of a new line. I am using pascals Write(Str[i]) function to print characters and WriteLn() once the end of a line is reached.
I have no idea why im receiving weird output. I am running Linux Mint and can compile and run my code, but I receive this strange output. I also tried running my code on a Mac and received a run-time error:
Program Path: ./Assignment1
File Name: lol.c
Runtime error 2 at $00011532
$00011532
$0002F7F6
$000113FD
$00011328
$00000002
Here is my code
program Assignment1;
uses
Sysutils;
var UserFile : TextFile;
TString : String;
OLine : String;
i : integer;
isComment : boolean;
skip : boolean;
begin
{$I+}
WriteLn('Program Path: ', ParamStr(0));
WriteLn('File Name: ', ParamStr(1));
Assign(UserFile, ParamStr(1) + '.c');
Reset(UserFile);
isComment := false;
skip := true;
Repeat
Readln(UserFile, TString);
for i:= 0 to ((Length(TString) - 1)) do
begin
if(skip) then
begin
skip := false;
continue;
end;
if(isComment = false) Then
begin
if(TString[i] = '/') Then
begin
if(TString[i+1] = '/') Then
begin
break;
end
else if(TString[i+1] = '*') Then
begin
isComment := true;
skip := true;
continue;
end;
end;
Write(TString[i]);
if(i = Length(TString) - 1) Then
begin
Write(TString[i + 1]);
end;
end
else
begin
if(TString[i] = '*') Then
begin
if(TString[i + 1] = '/') Then
begin
isComment := false;
skip := true;
continue;
end;
end;
end;
end;
WriteLn();
Until Eof(UserFile);
end.
I receive random characters which range from standard keyboard symbols to unicode blocks such as the ones found here.
Does anyone have any suggestions?
As 500 - Internal Server Error says, Pascal strings are 1-based. Your references to slot zero are returning garbage. If these are 256-byte strings you're getting the length code, I don't recall the memory layout of the pointer-based strings to know what you're getting in that case. You're also losing the last character of every string because of this.
Beyond that I see a definite bug: Look at what happens with a line ending in /
I also do not understand this:
if(i = Length(TString) - 1) Then
begin
Write(TString[i + 1]);
end;
It seems to me it's writing an extra character but I'm not sure.

Quick padding of a string in Delphi

I was trying to speed up a certain routine in an application, and my profiler, AQTime, identified one method in particular as a bottleneck. The method has been with us for years, and is part of a "misc"-unit:
function cwLeftPad(aString:string; aCharCount:integer; aChar:char): string;
var
i,vLength:integer;
begin
Result := aString;
vLength := Length(aString);
for I := (vLength + 1) to aCharCount do
Result := aChar + Result;
end;
In the part of the program that I'm optimizing at the moment the method was called ~35k times, and it took a stunning 56% of the execution time!
It's easy to see that it's a horrible way to left-pad a string, so I replaced it with
function cwLeftPad(const aString:string; aCharCount:integer; aChar:char): string;
begin
Result := StringOfChar(aChar, aCharCount-length(aString))+aString;
end;
which gave a significant boost. Total running time went from 10,2 sec to 5,4 sec. Awesome! But, cwLeftPad still accounts for about 13% of the total running time. Is there an easy way to optimize this method further?
Your new function involves three strings, the input, the result from StringOfChar, and the function result. One of them gets destroyed when your function returns. You could do it in two, with nothing getting destroyed or re-allocated.
Allocate a string of the total required length.
Fill the first portion of it with your padding character.
Fill the rest of it with the input string.
Here's an example:
function cwLeftPad(const aString: AnsiString; aCharCount: Integer; aChar: AnsiChar): AnsiString;
var
PadCount: Integer;
begin
PadCount := ACharCount - Length(AString);
if PadCount > 0 then begin
SetLength(Result, ACharCount);
FillChar(Result[1], PadCount, AChar);
Move(AString[1], Result[PadCount + 1], Length(AString));
end else
Result := AString;
end;
I don't know whether Delphi 2009 and later provide a double-byte Char-based equivalent of FillChar, and if they do, I don't know what it's called, so I have changed the signature of the function to explicitly use AnsiString. If you need WideString or UnicodeString, you'll have to find the FillChar replacement that handles two-byte characters. (FillChar has a confusing name as of Delphi 2009 since it doesn't handle full-sized Char values.)
Another thing to consider is whether you really need to call that function so often in the first place. The fastest code is the code that never runs.
Another thought - if this is Delphi 2009 or 2010, disable "String format checking" in Project, Options, Delphi Compiler, Compiling, Code Generation.
StringOfChar is very fast and I doubt you can improve this code a lot. Still, try this one, maybe it's faster:
function cwLeftPad(aString:string; aCharCount:integer; aChar:char): string;
var
i,vLength:integer;
origSize: integer;
begin
Result := aString;
origSize := Length(Result);
if aCharCount <= origSize then
Exit;
SetLength(Result, aCharCount);
Move(Result[1], Result[aCharCount-origSize+1], origSize * SizeOf(char));
for i := 1 to aCharCount - origSize do
Result[i] := aChar;
end;
EDIT: I did some testing and my function is slower than your improved cwLeftPad. But I found something else - there's no way your CPU needs 5 seconds to execute 35k cwLeftPad functions except if you're running on PC XT or formatting gigabyte strings.
I tested with this simple code
for i := 1 to 35000 do begin
a := 'abcd1234';
b := cwLeftPad(a, 73, '.');
end;
and I got 255 milliseconds for your original cwLeftPad, 8 milliseconds for your improved cwLeftPad and 16 milliseconds for my version.
You call StringOfChar every time now. Of course this method checks if it has something to do and jumps out if length is small enough, but maybe the call to StringOfChar is time consuming, because internally it does another call before jumping out.
So my first idea would be to jump out by myself if there is nothing to do:
function cwLeftPad(const aString: string; aCharCount: Integer; aChar: Char;): string;
var
l_restLength: Integer;
begin
Result := aString;
l_restLength := aCharCount - Length(aString);
if (l_restLength < 1) then
exit;
Result := StringOfChar(aChar, l_restLength) + aString;
end;
You can speed up this routine even more by using lookup array.
Of course it depends on your requirements. If you don't mind wasting some memory...
I guess that the function is called 35 k times but it has not 35000 different padding lengths and many different chars.
So if you know (or you are able to estimate in some quick way) the range of paddings and the padding chars you could build an two-dimensional array which include those parameters.
For the sake of simplicity I assume that you have 10 different padding lengths and you are padding with one character - '.', so in example it will be one-dimensional array.
You implement it like this:
type
TPaddingArray = array of String;
var
PaddingArray: TPaddingArray;
TestString: String;
function cwLeftPad4(const aString:string; const aCharCount:integer; const aChar:char; var anArray: TPaddingArray ): string;
begin
Result := anArray[aCharCount-length(aString)] + aString;
end;
begin
//fill up the array
SetLength(StrArray, 10);
PaddingArray[0] := '';
PaddingArray[1] := '.';
PaddingArray[2] := '..';
PaddingArray[3] := '...';
PaddingArray[4] := '....';
PaddingArray[5] := '.....';
PaddingArray[6] := '......';
PaddingArray[7] := '.......';
PaddingArray[8] := '........';
PaddingArray[9] := '.........';
//and you call it..
TestString := cwLeftPad4('Some string', 20, '.', PaddingArray);
end;
Here are benchmark results:
Time1 - oryginal cwLeftPad : 27,0043604142394 ms.
Time2 - your modyfication cwLeftPad : 9,25971967336897 ms.
Time3 - Rob Kennedy's version : 7,64538131122457 ms.
Time4 - cwLeftPad4 : 6,6417059620664 ms.
Updated benchmarks:
Time1 - oryginal cwLeftPad : 26,8360194218451 ms.
Time2 - your modyfication cwLeftPad : 9,69653117046119 ms.
Time3 - Rob Kennedy's version : 7,71149259179622 ms.
Time4 - cwLeftPad4 : 6,58248533610693 ms.
Time5 - JosephStyons's version : 8,76641780969192 ms.
The question is: is it worth the hassle?;-)
It's possible that it may be quicker to use StringOfChar to allocate an entirely new string the length of string and padding and then use move to copy the existing text over the back of it.
My thinking is that you create two new strings above (one with FillChar and one with the plus). This requires two memory allocates and constructions of the string pseudo-object. This will be slow. It may be quicker to waste a few CPU cycles doing some redundant filling to avoid the extra memory operations.
It may be even quicker if you allocated the memory space then did a FillChar and a Move, but the extra fn call may slow that down.
These things are often trial-and-error!
You can get dramatically better performance if you pre-allocate the string.
function cwLeftPadMine
{$IFDEF VER210} //delphi 2010
(aString: ansistring; aCharCount: integer; aChar: ansichar): ansistring;
{$ELSE}
(aString: string; aCharCount: integer; aChar: char): string;
{$ENDIF}
var
i,n,padCount: integer;
begin
padCount := aCharCount - Length(aString);
if padCount > 0 then begin
//go ahead and set Result to what it's final length will be
SetLength(Result,aCharCount);
//pre-fill with our pad character
FillChar(Result[1],aCharCount,aChar);
//begin after the padding should stop, and restore the original to the end
n := 1;
for i := padCount+1 to aCharCount do begin
Result[i] := aString[n];
end;
end
else begin
Result := aString;
end;
end;
And here is a template that is useful for doing comparisons:
procedure TForm1.btnPadTestClick(Sender: TObject);
const
c_EvalCount = 5000; //how many times will we run the test?
c_PadHowMany = 1000; //how many characters will we pad
c_PadChar = 'x'; //what is our pad character?
var
startTime, endTime, freq: Int64;
i: integer;
secondsTaken: double;
padIt: string;
begin
//store the input locally
padIt := edtPadInput.Text;
//display the results on the screen for reference
//(but we aren't testing performance, yet)
edtPadOutput.Text := cwLeftPad(padIt,c_PadHowMany,c_PadChar);
//get the frequency interval of the OS timer
QueryPerformanceFrequency(freq);
//get the time before our test begins
QueryPerformanceCounter(startTime);
//repeat the test as many times as we like
for i := 0 to c_EvalCount - 1 do begin
cwLeftPad(padIt,c_PadHowMany,c_PadChar);
end;
//get the time after the tests are done
QueryPerformanceCounter(endTime);
//translate internal time to # of seconds and display evals / second
secondsTaken := (endTime - startTime) / freq;
if secondsTaken > 0 then begin
ShowMessage('Eval/sec = ' + FormatFloat('#,###,###,###,##0',
(c_EvalCount/secondsTaken)));
end
else begin
ShowMessage('No time has passed');
end;
end;
Using that benchmark template, I get the following results:
The original: 5,000 / second
Your first revision: 2.4 million / second
My version: 3.9 million / second
Rob Kennedy's version: 3.9 million / second
This is my solution. I use StringOfChar instead of FillChar because it can handle unicode strings/characters:
function PadLeft(const Str: string; Ch: Char; Count: Integer): string;
begin
if Length(Str) < Count then
begin
Result := StringOfChar(Ch, Count);
Move(Str[1], Result[Count - Length(Str) + 1], Length(Str) * SizeOf(Char));
end
else Result := Str;
end;
function PadRight(const Str: string; Ch: Char; Count: Integer): string;
begin
if Length(Str) < Count then
begin
Result := StringOfChar(Ch, Count);
Move(Str[1], Result[1], Length(Str) * SizeOf(Char));
end
else Result := Str;
end;
It's a bit faster if you store the length of the original string in a variable:
function PadLeft(const Str: string; Ch: Char; Count: Integer): string;
var
Len: Integer;
begin
Len := Length(Str);
if Len < Count then
begin
Result := StringOfChar(Ch, Count);
Move(Str[1], Result[Count - Len + 1], Len * SizeOf(Char));
end
else Result := Str;
end;
function PadRight(const Str: string; Ch: Char; Count: Integer): string;
var
Len: Integer;
begin
Len := Length(Str);
if Len < Count then
begin
Result := StringOfChar(Ch, Count);
Move(Str[1], Result[1], Len * SizeOf(Char));
end
else Result := Str;
end;

How to convert a string version value to a numerical value in Inno Setup Scripts?

I want to develop a setup package for conditionally upgrading an existing package. I want to check the existing software version against to-be-installed version. In order to do that, I have to compare the version strings.
How can I convert the string value to a numerical value in a Inno setup script?
RegQueryStringValue(HKEY_LOCAL_MACHINE, 'Software\Blah blah', 'Version', version)
version = 'V1.R2.12';
numVersion := ??string_to_numerical_value??(version);
This is a little more tricky, as you would want to handle versions like 'V1.R2.12' and 'V0.R15.42' correctly - with the simple conversion in the other answer you would get 1212 and 1542, which would not compare the way you would expect.
You need to decide how big each part of the version number can be, and multiply the parts by that value to get a correct end number. Something like this:
[Code]
function string_to_numerical_value(AString: string; AMaxVersion: LongWord): LongWord;
var
InsidePart: boolean;
NewPart: LongWord;
CharIndex: integer;
c: char;
begin
Result := 0;
InsidePart := FALSE;
// this assumes decimal version numbers !!!
for CharIndex := 1 to Length(AString) do begin
c := AString[CharIndex];
if (c >= '0') and (c <= '9') then begin
// new digit found
if not InsidePart then begin
Result := Result * AMaxVersion + NewPart;
NewPart := 0;
InsidePart := TRUE;
end;
NewPart := NewPart * 10 + Ord(c) - Ord('0');
end else
InsidePart := FALSE;
end;
// if last char was a digit the last part hasn't been added yet
if InsidePart then
Result := Result * AMaxVersion + NewPart;
end;
You can test this with the following code:
function InitializeSetup(): Boolean;
begin
if string_to_numerical_value('V1.R2.12', 1) < string_to_numerical_value('V0.R15.42', 1) then
MsgBox('Version ''V1.R2.12'' is not as recent as version ''V0.R15.42'' (false)', mbConfirmation, MB_OK);
if string_to_numerical_value('V1.R2.12', 100) > string_to_numerical_value('V0.R15.42', 100) then
MsgBox('Version ''V1.R2.12'' is more recent than version ''V0.R15.42'' (true)', mbConfirmation, MB_OK);
Result := FALSE;
end;
Whether you pass 10, 100 or 1000 for AMaxVersion depends on the number and range of your version number parts. Note that you must not overflow the LongWord result variable, which has a maximum value of 2^32 - 1.
I haven't tried that (and my Pascal knowledge is a bit rusty), but something like the following should work:
function NumericVersion(s: String): Integer;
var
i: Integer;
s1: String;
begin
s1 := '';
for i := 0 to Length(s)-1 do
if (s[i] >= '0') and (s[i] <= '9') then
s1 := s1 + s[i];
Result := StrToIntDef(s1, 0);
end;
Please not that you'll have to play with the start and end value for i as I'm not sure whether it is zero-based or not (s[0] may contain the length of the string if it is a "Pascal String").
I've implemented two version strings (actually one string and one dword value) in the registry to overcome complexity.
displayversion="v1.r1.0"
version="10100" (=1*10^4 + 1*10^2 + 0*10^0)
That's simple. Though not an answer to this question, however one might think the other way around when faced with complexity, which could be avoided in a simpler way.

Resources