I need a wildcard that would match only numbers. I tried FileExistsWildcard function from How to test using wildcards whether a file exists in Inno Setup:
FileExistsWildcard(ExpandConstant('{app}\sav[1-9]'))
But Pascal Script obviously doesn't work that way. Is there such a wildcard or should I write a custom function or something?
P.S. Is there a wildcard matching list for Inno Setup at all?
The #TLama's FileExistsWildcard function internally uses Inno Setup FindFirst function, which in turn internally uses Windows FindFirstFile function.
And Windows supports only * and ? in its wildcards. The range/set pattern [a-z] is *nix thing only.
So it's not a Pascal (Script) limitation. It's a Windows limitation.
Implementing a generic matching function that supports all of ?, * and [a-z] is not easy.
I've tried to implement a matching function that is compatible with Windows matching (FindFirstFile) but supports a set pattern (including range set).
I was not identify exact rules how Windows treat . in the mask and the filename. So my matching function does not behave exactly the same in that respect. Otherwise, I believe, it is identical. And it supports [abc] set pattern as well as range set pattern [a-z], or any combination like [_0-9a-z].
function MatchesMaskEx(Mask: string; FileName: string): Boolean;
var
MaskI: Integer;
MaskC: Char;
FileNameI: Integer;
FileNameI2: Integer;
P: Integer;
Mask2: string;
EOSMatched: Boolean;
begin
Mask := LowerCase(Mask);
FileName := LowerCase(FileName);
MaskI := 1;
FileNameI := 1;
Result := True;
EOSMatched := False;
while (MaskI <= Length(Mask)) and Result do
begin
MaskC := Mask[MaskI];
if MaskC = '?' then
begin
{ noop, ? matches anything, even beyond end-of-string }
Inc(FileNameI);
end
else
if MaskC = '[' then
begin
if FileNameI > Length(FileName) then
begin
Result := False;
end
else
begin
P := Pos(']', Copy(Mask, MaskI + 1, Length(Mask) - MaskI));
if P = 0 then
begin
{ unclosed set - no match }
Result := False;
end
else
begin
Result := False;
P := P + MaskI;
Inc(MaskI);
while (MaskI < P) and (not Result) do
begin
MaskC := Mask[MaskI];
{ is it range (A-Z) ? }
if (MaskI + 2 < P) and (Mask[MaskI + 1] = '-') then
begin
MaskI := MaskI + 2;
end;
{ matching the range (or pseudo range A-A) }
if (MaskC <= FileName[FileNameI]) and
(FileName[FileNameI] <= Mask[MaskI]) then
begin
Inc(FileNameI);
Result := True;
MaskI := P - 1;
end;
Inc(MaskI);
end;
end;
end;
end
else
if MaskC = '*' then
begin
Mask2 := Copy(Mask, MaskI + 1, Length(Mask) - MaskI);
Result := False;
{ Find if the rest of the mask can match any remaining part }
{ of the filename => recursion }
for FileNameI2 := FileNameI to Length(FileName) + 1 do
begin
if MatchesMaskEx(
Mask2, Copy(FileName, FileNameI2, Length(FileName) - FileNameI2 + 1)) then
begin
Result := True;
MaskI := Length(Mask);
FileNameI := Length(FileName) + 1;
break;
end;
end;
end
else
begin
if (FileNameI <= Length(FileName)) and (FileName[FileNameI] = MaskC) then
begin
Inc(FileNameI);
end
else
begin
{ The dot can match EOS too, but only once }
if (MaskC = '.') and (FileNameI > Length(FileName)) and (not EOSMatched) then
begin
EOSMatched := True;
end
else
begin
Result := False;
end;
end;
end;
Inc(MaskI);
end;
if Result and (FileNameI <= Length(FileName)) then
begin
Result := False;
end;
end;
Use it like:
function FileExistsEx(Path: string): Boolean;
var
FindRec: TFindRec;
Mask: string;
begin
if FindFirst(AddBackslash(ExtractFilePath(Path)) + '*', FindRec) then
begin
Mask := ExtractFileName(Path);
try
repeat
if (FindRec.Attributes and FILE_ATTRIBUTE_DIRECTORY = 0) and
MatchesMaskEx(Mask, FindRec.Name) then
begin
Result := True;
Exit;
end;
until not FindNext(FindRec);
finally
FindClose(FindRec);
end;
end;
Result := False;
end;
For your specific needs, you can also use a simple ad-hoc function like:
function SpecialFileExists(Path: string): Boolean;
var
FindRec: TFindRec;
begin
if FindFirst(AddBackslash(Path) + '*', FindRec) then
begin
try
repeat
if (Length(FindRec.Name) = 4) and
(Copy(FindRec.Name, 1, 3) = 'sav') and
(FindRec.Name[4] >= '0') and (FindRec.Name[4] <= '9') then
begin
Result := True;
Exit;
end;
until not FindNext(FindRec);
finally
FindClose(FindRec);
end;
end;
Result := False;
end;
Use it like:
SpecialFileExists(ExpandConstant('{app}'))
Related
I have these three functions that successfully remove all non-numeric characters from a given string:
The first function loops through the characters of the input string, and if the current character is a number, it adds it to a new string that is returned as the result of the function.
function RemoveNonNumericChars(const s: string): string;
begin
Result := '';
for var i := 1 to Length(s) do
begin
if s[i] in ['0'..'9'] then
Result := Result + s[i];
end;
end;
The second function loops through the characters of the input string from right to left, and if the current character is not a number, it uses the Delete function to remove it from the string
function RemoveNonNumericChars(const s: string): string;
begin
Result := s;
for var i := Length(Result) downto 1 do
begin
if not(Result[i] in ['0'..'9']) then
Delete(Result, i, 1);
end;
end;
The third function uses a regular expression to replace all non-numeric characters with nothing, thus removing them. TRegEx is from the System.RegularExpressions unit.
function RemoveNonNumericChars(const s: string): string;
begin
var RegEx := TRegEx.Create('[^0-9]');
Result := RegEx.Replace(s, '');
end;
All three of them do what I need, but I want to know if there is maybe a built-in function in Delphi for this... Or maybe even a better way to do it than the way I'm doing it. What's the best and/or fastest way to remove non-numeric characters from a string in Delphi?
Both your approaches are slow because you constantly change the length of the string. Also, they only recognise Arabic digits.
To solve the performance issue, preallocate the maximum result length:
function RemoveNonDigits(const S: string): string;
begin
SetLength(Result, S.Length);
var LActualLength := 0;
for var i := 1 to S.Length do
if CharInSet(S[i], ['0'..'9']) then
begin
Inc(LActualLength);
Result[LActualLength] := S[i];
end;
SetLength(Result, LActualLength);
end;
To support non-Arabic digits, use the TCharacter.IsDigit function:
function RemoveNonDigits(const S: string): string;
begin
SetLength(Result, S.Length);
var LActualLength := 0;
for var i := 1 to S.Length do
if S[i].IsDigit then
begin
Inc(LActualLength);
Result[LActualLength] := S[i];
end;
SetLength(Result, LActualLength);
end;
To optimise even further, as suggested by Stefan Glienke, you can bypass the RTL's string handling machinery and write each character directly with some loss of code readability:
function RemoveNonDigits(const S: string): string;
begin
SetLength(Result, S.Length);
var ResChr := PChar(Result);
var LActualLength := 0;
for var i := 1 to S.Length do
if CharInSet(S[i], ['0'..'9']) then
begin
Inc(LActualLength);
ResChr^ := S[i];
Inc(ResChr);
end;
SetLength(Result, LActualLength);
end;
Benchmark
Just for fun I did a very primitive benchmark on random input strings with length < 100 and about 24% chance of a char being a digit:
program Benchmark;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils, System.RegularExpressions, Windows;
function OP1(const s: string): string;
begin
Result := '';
for var i := 1 to Length(s) do
begin
if s[i] in ['0'..'9'] then
Result := Result + s[i];
end;
end;
function OP2(const s: string): string;
begin
Result := s;
for var i := Length(Result) downto 1 do
begin
if not(Result[i] in ['0'..'9']) then
Delete(Result, i, 1);
end;
end;
function OP3(const s: string): string;
begin
var RegEx := TRegEx.Create('[^0-9]');
Result := RegEx.Replace(s, '');
end;
function AR1(const S: string): string;
begin
SetLength(Result, S.Length);
var LActualLength := 0;
for var i := 1 to S.Length do
if CharInSet(S[i], ['0'..'9']) then
begin
Inc(LActualLength);
Result[LActualLength] := S[i];
end;
SetLength(Result, LActualLength);
end;
function AR2(const S: string): string;
begin
SetLength(Result, S.Length);
var ResChr := PChar(Result);
var LActualLength := 0;
for var i := 1 to S.Length do
if CharInSet(S[i], ['0'..'9']) then
begin
Inc(LActualLength);
ResChr^ := S[i];
Inc(ResChr);
end;
SetLength(Result, LActualLength);
end;
function AR3(const S: string): string;
begin
SetLength(Result, S.Length);
var ResChr := PChar(Result);
for var i := 1 to S.Length do
if CharInSet(S[i], ['0'..'9']) then
begin
ResChr^ := S[i];
Inc(ResChr);
end;
SetLength(Result, ResChr - PChar(Result));
end;
function RandomInputString: string;
begin
SetLength(Result, Random(100));
for var i := 1 to Result.Length do
Result[i] := Chr(Ord('0') + Random(42));
end;
begin
Randomize;
const N = 1000000;
var Data := TArray<string>(nil);
SetLength(Data, N);
for var i := 0 to N - 1 do
Data[i] := RandomInputString;
var f, c0, cOP1, cOP2, cOP3, cAR1, cAR2, cAR3: Int64;
QueryPerformanceFrequency(f);
QueryPerformanceCounter(c0);
for var i := 0 to High(Data) do
OP1(Data[i]);
QueryPerformanceCounter(cOP1);
Dec(cOP1, c0);
QueryPerformanceCounter(c0);
for var i := 0 to High(Data) do
OP2(Data[i]);
QueryPerformanceCounter(cOP2);
Dec(cOP2, c0);
QueryPerformanceCounter(c0);
for var i := 0 to High(Data) do
OP3(Data[i]);
QueryPerformanceCounter(cOP3);
Dec(cOP3, c0);
QueryPerformanceCounter(c0);
for var i := 0 to High(Data) do
AR1(Data[i]);
QueryPerformanceCounter(cAR1);
Dec(cAR1, c0);
QueryPerformanceCounter(c0);
for var i := 0 to High(Data) do
AR2(Data[i]);
QueryPerformanceCounter(cAR2);
Dec(cAR2, c0);
QueryPerformanceCounter(c0);
for var i := 0 to High(Data) do
AR3(Data[i]);
QueryPerformanceCounter(cAR3);
Dec(cAR3, c0);
Writeln('Computations per second:');
Writeln('OP1: ', Round(N / (cOP1 / f)));
Writeln('OP2: ', Round(N / (cOP2 / f)));
Writeln('OP3: ', Round(N / (cOP3 / f)));
Writeln('AR1: ', Round(N / (cAR1 / f)));
Writeln('AR2: ', Round(N / (cAR2 / f)));
Writeln('AR3: ', Round(N / (cAR3 / f)));
Readln;
end.
Result:
Computations per second:
OP1: 1398134
OP2: 875116
OP3: 39162
AR1: 3406172
AR2: 4063260
AR3: 4032343
As you can see, in this test at least, regular expressions are by far the slowest approach. And preallocating makes a major difference, while avoiding the _UniqueStringU issue appears to make only a relatively minor improvement.
But even with the very slow RegEx approach, you can do 40 000 calls per second. On my 13-year-old computer.
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;
How can I split a string in Inno Setup?
Is there any special function in Inno Setup to split the string?
I want to get the following from the string '11.2.0.16':
tokens: array of string = ('11', '0', '2', '16');
Thanks in advance!
For anyone who prefers the function format, I have modified #cezarlamann's answer:
function StrSplit(Text: String; Separator: String): TArrayOfString;
var
i, p: Integer;
Dest: TArrayOfString;
begin
i := 0;
repeat
SetArrayLength(Dest, i+1);
p := Pos(Separator,Text);
if p > 0 then begin
Dest[i] := Copy(Text, 1, p-1);
Text := Copy(Text, p + Length(Separator), Length(Text));
i := i + 1;
end else begin
Dest[i] := Text;
Text := '';
end;
until Length(Text)=0;
Result := Dest
end;
I've been looking for the same thing today...
This one works just fine on Inno Setup Scripts. Paste this excerpt inside your script before the procedure/function which will call this "split" procedure.
You can also modify this onto a function, if you desire...
procedure Explode(var Dest: TArrayOfString; Text: String; Separator: String);
var
i, p: Integer;
begin
i := 0;
repeat
SetArrayLength(Dest, i+1);
p := Pos(Separator,Text);
if p > 0 then begin
Dest[i] := Copy(Text, 1, p-1);
Text := Copy(Text, p + Length(Separator), Length(Text));
i := i + 1;
end else begin
Dest[i] := Text;
Text := '';
end;
until Length(Text)=0;
end;
procedure Whatever();
var
str: String;
strArray: TArrayOfString;
i: Integer;
begin
Explode(strArray,str,'.');
for i:=0 to GetArrayLength(strArray)-1 do begin
{ do something }
end;
end;
Taken from here
Here's what I use:
procedure SplitString(S, Delim: string; var Dest: TArrayOfString);
var
Temp: string;
I, P: Integer;
begin
Temp := S;
I := StringChangeEx(Temp, Delim, '', true);
SetArrayLength(Dest, I + 1);
for I := 0 to GetArrayLength(Dest) - 1 do
begin
P := Pos(Delim, S);
if P > 0 then
begin
Dest[I] := Copy(S, 1, P - 1);
Delete(S, 1, P + Length(Delim) - 1);
end
else
Dest[I] := S;
end;
end;
This version avoids repeated array resizing by counting the delimiters using StringChangeEx and setting the array size only once. Since we then know the array size, we can just use a for loop. I also opted for Delete rather than Copy, which (IMO) makes the code easier to read. (This version also fixes the bug where the split does not occur correctly if the delimiter is longer than 1 character.)
If there is a possibility that the delimiter could also be right at the end of the string, then this is what I used (modified from #timyha's answer)
function StrSplit(Text: String; Separator: String): TArrayOfString;
var
i, p: Integer;
Dest: TArrayOfString;
begin
i := 0;
repeat
SetArrayLength(Dest, i+1);
p := Pos(Separator,Text);
if p > 0 then begin
Dest[i] := Copy(Text, 1, p-1);
Text := Copy(Text, p + Length(Separator), Length(Text));
i := i + 1;
//add an empty string if delim was at the end
if Text = '' then begin
Dest[i]:='';
i := i + 1;
end;
end else begin
Dest[i] := Text;
Text := '';
end;
until Length(Text)=0;
Result := Dest
end;
Is there a way to exclude blank strings from the dynamic array resulting of the SplitString function (Delphi XE, StrUtils), without having to iterate through the array?
If not, can anyone suggest the most efficient way to do it? Right now I'm doing it like this:
function SplitStringNoEmpty(myString : string; myDelimiters : string):TStringDynArray;
var
words_array_pre : TStringDynArray;
words_array_pos : TStringDynArray;
array_length : Integer;
actual_length : Integer;
current_word : string;
procedure AddElement(const Str: string);
begin
words_array_pos[actual_length]:= Str;
inc(actual_length);
end;
begin
words_array_pre:= SplitString(myString, whitespaceNewLineCharacterSet + punctuationCharacterSet);
array_length:= Length(words_array_pre);
if (array_length >0) then
begin
actual_length:= 0;
SetLength(words_array_pos, array_length);
for current_word in words_array_pre do
begin
if (current_word <> '') then
AddElement(current_word);
end;
SetLength(words_array_pos, actual_length);
result:= words_array_pos;
end
else
result:= words_array_pre;
end;
You can write your own implementation of the SplitString function ignoring the empty strings.
Check this sample
function SplitString2(const S, Delimiters: string): TStringDynArray;
var
LIndex, SIndex, FIndex, LMax, LPos: Integer;
foo : string;
begin
Result := nil;
if S <> '' then
begin
LPos := 0;
LMax := 0;
SIndex := 1;
for LIndex := 1 to Length(S) do
if IsDelimiter(Delimiters, S, LIndex) then Inc(LMax);
SetLength(Result, LMax + 1);
repeat
FIndex := FindDelimiter(Delimiters, S, SIndex);
if FIndex <> 0 then
begin
foo:= Copy(S, SIndex, FIndex - SIndex);
if foo<>'' then
begin
Result[LPos] := foo;
Inc(LPos);
end;
SIndex := FIndex + 1;
end;
until (LPos = LMax) or (FIndex=0);
if LPos<LMax then
SetLength(Result, LPos + 1);
foo:=Copy(S, SIndex, Length(S) - SIndex + 1);
if foo<>'' then
Result[LMax] := foo
else
SetLength(Result, LPos);
end;
end;
It's impossible to remove certain elements of an array without iterating over the array — how else would you know which elements to remove? The improvements to make to your code are to remove the need to allocate an extra array. You can cull the original array in-place:
function SplitStringNoEmpty(const s, delimiters: string): TStringDynArray;
var
Src, Dest: Integer;
begin
Result := SplitString(s, delimiters);
if Length(Result) <> 0 then begin
// Push all non-empty values to front of array
Dest := 0;
for Src := 0 to High(Result) do
if Result[Src] <> '' then begin
if Src <> Dest then
Result[Dest] := Result[Src];
Inc(Dest);
end;
// Remove excess from end of array
SetLength(Result, Dest);
end;
end;
I have a variety of strings which I need to work with, these contain both letters and numbers , I am trying to extract the numbers (which is the part I need) from the string, the strings would have a similar format to -
the cat can count 123 567 so can the dog"
The length and position of the numbers can vary from
12 34
123 456
1234 5678
11111 11111
Also the number seperator can vary from a space question mark and also a dash
12-34
12.34
So the string could be EG “the cat can't count, the dog can 12-67” or “the cat can count 1234.5678 so can the dog”
Is there any clever way in Delphi I can extract the numbers? Or would I have to do it by scanning the string in code.
Any help would be appreciated
Thanks
colin
If you have Delphi XE or up, you can use regular expressions. This is completely untested, based on David Heffernan's answer:
function ExtractNumbers(const s: string): TArray<string>;
var
regex: TRegEx;
match: TMatch;
matches: TMatchCollection;
i: Integer;
begin
Result := nil;
i := 0;
regex := TRegEx.Create("\d+");
matches := regex.Matches(s);
if matches.Count > 0 then
begin
SetLength(Result, matches.Count);
for match in matches do
begin
Result[i] := match.Value;
Inc(i);
end;
end;
end;
I think this function is what you are looking for:
function ExtractNumbers(const s: string): TArray<string>;
var
i, ItemIndex: Integer;
LastCharWasDigit: Boolean;
len: Integer;
Count: Integer;
Start: Integer;
begin
len := Length(s);
if len=0 then begin
Result := nil;
exit;
end;
Count := 0;
LastCharWasDigit := False;
for i := 1 to len do begin
if TCharacter.IsDigit(s[i]) then begin
LastCharWasDigit := True;
end else if LastCharWasDigit then begin
inc(Count);
LastCharWasDigit := False;
end;
end;
if LastCharWasDigit then begin
inc(Count);
end;
SetLength(Result, Count);
ItemIndex := 0;
Start := 0;
for i := 1 to len do begin
if TCharacter.IsDigit(s[i]) then begin
if Start=0 then begin
Start := i;
end;
end else begin
if Start<>0 then begin
Result[ItemIndex] := Copy(s, Start, i-Start);
inc(ItemIndex);
Start := 0;
end;
end;
end;
if Start<>0 then begin
Result[ItemIndex] := Copy(s, Start, len);
end;
end;
function ExtractNumberInString ( sChaine: String ): String ;
var
i: Integer ;
begin
Result := '' ;
for i := 1 to length( sChaine ) do
begin
if sChaine[ i ] in ['0'..'9'] then
Result := Result + sChaine[ i ] ;
end ;
end ;
inspired by user2029909 response
function ExtractNumberInString (sChaine: String; Start : Integer = 1): TArray<String> ;
var
i, j: Integer ;
TmpStr : string;
begin
j := 0;
for i := Start to Length( sChaine ) do
begin
if sChaine[ i ] in ['0'..'9'] then
TmpStr := TmpStr + sChaine[ i ]
else
if TmpStr <> '' then
begin
SetLength(Result, Length(Result) + 1);
Result[j] := TmpStr;
TmpStr := '';
Inc(j);
end;
end ;
end ;
EDIT 1.: The function below will read the first floating point number after the position Start in the String S and register its end position as Last. This function does NOT work for different cases of float numbers, such as:
3.1415926535897932384d0; A double-format approximation to Pi
3.010299957f-1; Log2, in single format
-0.000000001s9 e^(i*Pi), in short format
0.0s0; A floating-point zero in short format
0s0; Also a floating-point zero in short format
https://www.cs.cmu.edu/Groups/AI/html/cltl/clm/node19.html
function ExtractFloatStr(Start : integer; S : string; var Last : integer) : string;
var
i, Lstr : integer;
chln : char;
str_acm : string;
Numeric, Sign, DeciSep, Exponent : boolean;
begin
Numeric := False; Sign := False; DeciSep := False; Exponent := False;
Lstr := length(S);
If (Start > 0) and (Start <= Lstr) then
i := Start-1
Else i := 0;
Last := -1; chln := #0;
str_acm := '';
repeat
begin
i := i + 1;
chln := S[i];
//ShowMessage('str_acm['+IntToStr(i)+'] = '+str_acm+'P');
If Last = -1 then
begin
If chln = '-' then
begin
{ Signs will only count if they are the first element }
If (str_acm = '') then { Signs can only be added at the leftmost position }
begin
Sign := True;
str_acm := str_acm + chln;
end
{ If there's something already registered as number, a right-side Sign will mean two things }
Else begin
{ Signs cannot be added at the right side of any number or Decimal Separator }
If Numeric = True then { End of the reading, in case there's already a valid series of digits }
begin
{Last := i-1;} { ex.: -1.20----; -.20--- }
If i > 1 then
begin
If (S[i-1] = 'E') or (S[i-1] = 'e') then
str_acm := str_acm + chln
Else begin
Last := i-1;
end;
end;
end
Else begin { A mixture of various characters without numeric logic}
str_acm := ''; { So start over the reading }
Sign := False; { ex.: -.--- }
end;
end;
end;
If (chln in ['.',',']) then
begin
If (DeciSep = False) then { Decimal Separators can only be added once }
begin
str_acm := str_acm + DecimalSeparator;
DeciSep := True;
end
{ If a Decimal Separator was already accounted, a second one will mean two things }
Else begin
If Numeric = True then { End of the reading, in case there's already a valid series of digits }
Last := i-1 { ex.: -1.20...; -0.20. }
Else begin { A mixture of various characters without numeric logic }
str_acm := ''; { So start over the reading }
DeciSep := False; { ex.: -... }
end;
end;
end;
If (chln in ['0'..'9']) then
begin
Numeric := True; { Numbers can be added after any other characters, be it Sign and/or Decimal Separator }
str_acm := str_acm + chln; { Ex.: -1; -2.1; -.1; -1. }
end;
If (chln = 'E') or (chln = 'e') then
begin
If Exponent = False then
begin
If Numeric = True then { E for the power of 10 can only be added once and after a series of digits }
begin { Ex.: 1.0E10; -.0E2; -4.E3 }
str_acm := str_acm + chln;
Exponent := True;
end
Else begin { The abscense of a previous series of digits does not allow the insertion of E }
str_acm := ''; { E cannot start a floating point number and cannot succeed a sign or }
end; { decimal separator if there isn't any previous number }
end { Ex.: -.E; .E; -E; E }
Else begin
Last := i-1; { E cannot appear twice. A second one means the end of the reading }
end;
end;
If chln = '+' then { Plus (+) sign will only be registered after a valid exponential E character }
begin
If (i > 1) and (Exponent = True) then
begin
If (S[i-1] = 'E') or (S[i-1] = 'e') then
str_acm := str_acm + chln
Else begin
Last := i-1; { If it's added after anything other than E, the reading ends }
end;
end;
If Exponent = False then
begin
If (Numeric = True) then
begin
Last := i-1; { If it's added after anything other than E, the reading ends }
end
Else begin
str_acm := ''; { If it's added after anything other than E, and if there isn't any }
Exponent := False; { valid series of digits, the reading restarts }
end;
end;
end;
{ If any character except the ones from the Floating Point System are added }
If not (chln in ['0'..'9','-','+',',','.','E','e']) then
begin
{ After an already accounted valid series of digits }
If (str_acm <> '') then
begin
If (Numeric = True) then
Last := i-1 { End of the reading. Ex.: -1.20A; -.20%; 120# }
Else begin
str_acm := '';
Sign := False; DeciSep := False; Exponent := False;
end;
end;
end;
end;
//ShowMessage('i = '+IntToStr(i)+#13+str_acm+'P');
end;
until((Last <> -1) or (i = Lstr));
If (i = Lstr) and (Numeric = True) then
Last := i;
{ The Loop does not filter the case when no number is inserted after E, E- or E+ }
{ So it's necessary to check and remove if E,E-,E+,e,e-,e+ are the last characters }
If Last <> -1 then
begin
Lstr := length(str_acm);
If (str_acm[Lstr] = '+') or (str_acm[Lstr] = '-') then
begin
SetLength(str_acm,Lstr-1);
Last := Last - 1;
end;
Lstr := length(str_acm);
If (str_acm[Lstr] = 'E') or (str_acm[Lstr] = 'e') then
begin
SetLength(str_acm,Lstr-1);
Last := Last - 1;
end;
Result := str_acm;
end
Else Result := '';
end; { ExtractFloatStr }
EDIT 2.: Another function using the previous one to read a series of numbers in the same string.
var
TFloatType = real;
TVetorN = array of TFloatType;
procedure ExtractFloatVectorStr(Str : string; var N : integer; var FloatVector : TVetorN);
var { Extract floating point numbers from string reading from left to right }
i, j, k, Lstr, Lstr1 : integer; { Register the numbers in FloatVector as the type TVetorN }
char1 : char; { Register the amount of numbers found as the integer N }
str_acm : string;
begin
Str := AdjustLineBreaks(Str,tlbsCRLF);
Lstr := length(Str);
Lstr1 := 0;
char1 := #0;
i := 1; j := 0; k := 0; str_acm := '';
SetLength(FloatVector,j+1);
repeat
begin
If (i <= Lstr) then
begin
str_acm := ExtractFloatStr(i, Str, k);
Lstr1 := length(str_acm);
If (Lstr1 > 0) and (str_acm <> '') then
begin
j := j + 1;
SetLength(FloatVector,j+1);
FloatVector[j] := StrToFloat(str_acm);
i := k + 1;
end
Else i := i + 1;
end;
end;
until(i > Lstr);
N := j;
end; { ExtractFloatVectorStr }