Detect if string contains a float? - delphi

How can I detect if a string contains a float. For example: '0.004'
But without using StrToFloat because that function are slow but rather by iterating through chars.
function IsInteger(const S: String): Boolean;
var
P: PChar;
begin
P := PChar(S);
Result := True;
while not (P^ = #0) do
begin
case P^ of
'0'..'9': Inc(P);
else
Result := False;
Break;
end;
end;
end;
This will check if string is a positive integer but not a float..

I would use TryStrToFloat():
if TryStrToFloat(str, value, FormatSettings) then
....
If you are prepared to use the default system wide format settings then you can omit the final parameter:
if TryStrToFloat(str, value) then
....

Can you use a RegEx here? Something like:
([+-]?[0-9]+(?:\.[0-9]*)?)

The problem with this question is that saying "is too slow" doesn't tell much. What does the profiler tells to you? Do you have an informed idea about the input data? What about different notations, for example, 6.02e23?
If your input data is mostly noise, then using regular expressions (as answered here) may improve things but only as a first filter. You could then add a second step to actually obtain your number, as explained by David's answer.

Related

Delphi XE3: Convert WideChar array to Unicode string HOWTO?

I've done some research here regarding the problem given above and come up with the following code:
VarStr = array of WideChar;
function ArrayToString(const a: VarStr): UnicodeString;
begin
if Length(a) > 0 then
begin
ShowMessage ('Länge des übergebenen Strings: ' + IntToStr(Length(a)));
SetString(Result, PWideChar(#a[0]), Length(a) div 2)
end
else
Result := '';
end;
ShowMessage displays the correct number of characters in a given array, but the result of the function is always an empty string.
Your ideas please?
You are passing the wrong length value. You only ask for half of the characters. Fix your code like this:
function ArrayToString(const a: VarStr): string;
begin
SetString(Result, PWideChar(a), Length(a));
end;
However, you also report that your function returns an empty string. The most likely cause for that is that you are passing invalid input to the function. Consider this program:
{$APPTYPE CONSOLE}
type
VarStr = array of WideChar;
function ArrayToStringBroken(const a: VarStr): UnicodeString;
begin
SetString(Result, PWideChar(#a[0]), Length(a) div 2);
end;
function ArrayToStringSetString(const a: VarStr): UnicodeString;
begin
SetString(Result, PWideChar(a), Length(a));
end;
var
a: VarStr;
begin
a := VarStr.Create('a', 'b', 'c', 'd');
Writeln(ArrayToStringBroken(a));
Writeln(ArrayToStringSetString(a));
end.
The output is:
ab
abcd
So as well as the problem with the code in your question, you would seem to have problems with the code that is not in your question.
Perhaps when you said:
The result of the function is always an empty string.
You actually meant that no text is displayed when you pass the returned value to ShowMessage. That's a completely different thing altogether. As #bummi points out in comments, ShowMessage will truncate its input at the first null-terminator that is encountered. Use proper debugging tools to inspect the contents of variables.
Result:= Trim(string(a));
UPDATE: As colleagues graciously pointed in comments, this is a wrong answer! It works only because internal string and dynamic array implementation are pretty similar and there is no guarantee that such code would work in the future compilator versions. The correct way to DynArray->String conversion is described in the David answer. I would not delete my answer to preserve comments, in my opinion their worth is much greater..

Enumerate possible set values in Delphi

I have a calculation algorithm in Delphi with a number of different options, and I need to try every combination of options to find an optimal solution.
TMyOption = (option1, option2, option3, option4);
TMyOptions = set of TMyOption;
I wondered about using an Integer loop to enumerate them:
for EnumerationInteger := 0 to 15 do begin
Options := TMyOptions(EnumerationInteger);
end;
This does not compile. What I was wondering was if there was any fairly simple method to convert from Integer to Set (most questions on the Web try to go the other way, from Set to Integer), and if so what is it?
Another possibility is to just use the Integer as a bit-field:
C_Option1 = 1;
C_Option2 = 2;
C_Option3 = 4;
C_Option4 = 8;
and then test membership with a bitwise and:
if (Options and C_Option2) > 0 then begin
...
end;
I've tried this, and it works, but it feels like working with sets would be more natural and use the type system better (even though I'm going outside the said type system to enumerate the sets).
Is there a better/safer way to enumerate all possible set combinations than enumerating the underlying integer representation?
Notes:
I know that the integer values of a set are not guaranteed in theory (though I suspect they are in practice if you don't play with the enumeration numbering).
There could be more than four options (yes, I know that it grows exponentially and if there are too many options the algorithm could take forever).
I know this question is quite old, but this is my preference since it's simple and natural to me :
function NumericToMyOptions(n: integer): TMyOptions;
var
Op: TMyOption;
begin
Result:= [];
for Op:= Low(TMyOption) to High(TMyOption) do
if n and (1 shl ord(Op)) > 0 then Include(Result, Op);
end;
Try
var EnumerationByte: Byte;
...
for EnumerationByte := 0 to 15 do begin
Options := TMyOptions(EnumerationByte);
end;
Your code does not compile because your enumeration (TMyOption) have less than 8 values, and Delphi utilize the minimum possible size (in bytes) for sets. Thus, a byte variable will work for you.
If you have a set with more than 8 but less than 16 possible elements, a Word will work (and not an integer).
For more than 16 but less than 32 a DWord variable and typecast.
For more than 32 possible elements, I think a better approach is to use an array of bytes or something like that.
500 - Internal Server Error's answer is probably the most simple.
Another approach that would less likely to break with changes to the number of options would be to declare an array of boolean, and switch them on/off. This is slower than working with pure integers though. The main advantage, you won't need to change the integer type you use, and you can use it if you have more than 32 options.
procedure DoSomething
var BoolFlags : Array[TOption] of Boolean;
I: TOption;
function GetNextFlagSet(var Bools : Array of Boolean) : Boolean;
var idx, I : Integer;
begin
idx := 0;
while Bools[idx] and (idx <= High(Bools)) do Inc(idx);
Result := idx <= High(Bools);
if Result then
for I := 0 to idx do
Bools[I] := not Bools[I];
end;
begin
for I := Low(BoolFlags) to High(BoolFlags) do BoolFlags[i] := False;
repeat
if BoolFlags[Option1] then
[...]
until not GetNextFlagSet(BoolFlags);
end;
Casting from an Integer to a Set is not possible, but Tondrej once wrote a blog article on SetToString and StringToSet that exposes what you want in the SetOrdValue method:
uses
TypInfo;
procedure SetOrdValue(Info: PTypeInfo; var SetParam; Value: Integer);
begin
case GetTypeData(Info)^.OrdType of
otSByte, otUByte:
Byte(SetParam) := Value;
otSWord, otUWord:
Word(SetParam) := Value;
otSLong, otULong:
Integer(SetParam) := Value;
end;
end;
Your code then would become this:
for EnumerationInteger := 0 to 15 do begin
SetOrdValue(TypeInfo(TMyOptions), Options, EnumerationInteger);
end;
--jeroen
The problem is that you are trying to cast to the set type instead of the enumerated type. You can cast between integer and enumerated because both are ordinal types, but you can't cast to a set because they use bitfiels as you already noted. If you use:
for EnumerationInteger := 0 to 15 do begin
Option := TMyOption(EnumerationInteger);
end;
it would work, although is not what you want.
I had this same problem a few months ago and came to the conclusion that you can't enumerate the contents of a set in Delphi (at least in Delphi 7) because the language doesn't define such operation on a set.
Edit: It seems that you can even in D7, see coments to this answer.

How can I search faster for name/value pairs in a Delphi TStringList?

I implemented language translation in an application by putting all strings at runtime in a TStringList with:
procedure PopulateStringList;
begin
EnglishStringList.Append('CAN_T_FIND_FILE=It is not possible to find the file');
EnglishStringList.Append('DUMMY=Just a dummy record');
// total of 2000 record appended in the same way
EnglishStringList.Sorted := True; // Updated comment: this is USELESS!
end;
Then I get the translation using:
function GetTranslation(ResStr:String):String;
var
iIndex : Integer;
begin
iIndex := -1;
iIndex := EnglishStringList.IndexOfName(ResStr);
if iIndex >= 0 then
Result := EnglishStringList.ValueFromIndex[iIndex] else
Result := ResStr + ' (Translation N/A)';
end;
Anyway with this approach it takes about 30 microseconds to locate a record, is there a better way to achieve the same result?
UPDATE: For future reference I write here the new implementation that uses TDictionary as suggested (works with Delphi 2009 and newer):
procedure PopulateStringList;
begin
EnglishDictionary := TDictionary<String, String>.Create;
EnglishDictionary.Add('CAN_T_FIND_FILE','It is not possible to find the file');
EnglishDictionary.Add('DUMMY','Just a dummy record');
// total of 2000 record appended in the same way
end;
function GetTranslation(ResStr:String):String;
var
ValueFound: Boolean;
begin
ValueFound:= EnglishDictionary.TryGetValue(ResStr, Result);
if not ValueFound then Result := Result + '(Trans N/A)';
end;
The new GetTranslation function performs 1000 times faster (on my 2000 sample records) then the first version.
THashedStringList should be better, I think.
In Delphi 2009 or later I would use TDictionary< string,string > in Generics.Collections.
Also note that there are free tools such as http://dxgettext.po.dk/ for translating applications.
If THashedStringList works for you, that's great. Its biggest weakness is that every time you change the contents of the list, the Hash table is rebuilt. So it will work for you as long as your list remains small or doesn't change very often.
For more info on this, see: THashedStringList weakness, which gives a few alternatives.
If you have a big list that may be updated, you might want to try GpStringHash by gabr, that doesn't have to recompute the whole table at every change.
I think that you don't use the EnglishStringList(TStringList) correctly. This is a sorted list, you add elements (strings), you sort it, but when you search, you do this by a partial string (only the name, with IndexOfName).
If you use IndexOfName in a sorted list, the TStringList can't use Dicotomic search. It use sequential search.
(this is the implementation of IndexOfName)
for Result := 0 to GetCount - 1 do
begin
S := Get(Result);
P := AnsiPos('=', S);
if (P <> 0) and (CompareStrings(Copy(S, 1, P - 1), Name) = 0) then Exit;
end;
I think that this is the reason of poor performance.
The alternative is use 2 TStringList:
* The first (sorted) only containts the "Name" and a pointer to the second list that contain the value; You can implement this pointer to the second list using the "pointer" of Object property.
* The second (not sorted) list containt the values.
When you search, you do it at first list; In this case you can use the Find method. when you find the name, the pointer (implemented with Object property) give you the position on second list with the value.
In this case, Find method on Sorted List is more efficient that HashList (that must execute a funcion to get the position of a value).
Regards.
Pd:Excuse-me for mistakes with english.
You can also use a CLASS HELPER to re-program the "IndexOfName" function:
TYPE
TStringsHelper = CLASS HELPER FOR TStrings
FUNCTION IndexOfName(CONST Name : STRING) : INTEGER;
END;
FUNCTION TStringsHelper.IndexOfName(CONST Name : STRING) : INTEGER;
VAR
SL : TStringList ABSOLUTE Self;
S,T : STRING;
I : INTEGER;
BEGIN
IF (Self IS TStringList) AND SL.Sorted THEN BEGIN
S:=Name+NameValueSeparator;
IF SL.Find(S,I) THEN
Result:=I
ELSE IF (I<0) OR (I>=Count) THEN
Result:=-1
ELSE BEGIN
T:=SL[I];
IF CompareStrings(COPY(T,1,LENGTH(S)),S)=0 THEN Result:=I ELSE Result:=-1
END;
EXIT
END;
Result:=INHERITED IndexOfName(Name)
END;
(or implement it in a descendant TStrings class if you dislike CLASS HELPERs or don't have them in your Delphi version).
This will use a binary search on a sorted TStringList and a sequential search on other TStrings classes.

Any way to get TStringList.CommaText to not escape commas with quotes?

I'm doing some work with code generation, and one of the things I need to do is create a function call where one of the parameters is a function call, like so:
result := Func1(x, y, Func2(a, b, c));
TStringList.CommaText is very useful for generating the parameter lists, but when I traverse the tree to build the outer function call, what I end up with looks like this:
result := Func1(x, y, "Func2(a, b, c)");
It's quoting the third argument because it contains commas, and that produced invalid code. But I can't do something simplistic like StringReplace all double quotes with empty strings, because it's quite possible that a function argument could be a string with double quotes inside. Is there any way to make it just not escape the lines that contain commas?
You could set QuoteChar to be a space, and you'd merely get some extra spaces in the output, which is generally OK since generated code isn't usually expected to look pretty. String literals would be affected, though; they would have extra spaces inserted, changing the value of the string.
Free Pascal's TStrings class uses StrictDelimiter to control whether quoting occurs when reading the DelimitedText property. When it's true, quoting does not occur at all. Perhaps Delphi treats that property the same way.
Build an array of "unlikely characters" : non-keyable like †, ‡ or even non-printable like #129, #141, #143, #144.
Verify you don't have the 1st unlikely anywhere in your StringList.CommaText. Or move to the next unlikely until you get one not used in your StringList.CommaText. (Assert that you find one)
Use this unlikely char as the QuoteChar for your StringList
Get StringList.DelimitedText. You'll get the QuoteChar around the function parameters like: result := Func1(x, y, †Func2(a, b, c)†);
Replace the unlikely QuoteChar (here †) by empty strings...
What about using the Unicode version of AnsiExtractQuotedStr to remove the quotes?
Write your own method to export the contents of your TStringList to a string.
function MyStringListToString(const AStrings: TStrings): string;
var
i: Integer;
begin
Result := '';
if AStrings.Count = 0 then
Exit;
Result := AStrings[0];
for i := 1 to AStrings.Count - 1 do
Result := Result + ',' + AStrings[i];
end;
Too obvious? :-)
Alternatively, what would happen if you set StringList.QuoteChar to #0 and then called StringList.DelimitedText?
We have written a descendant class of TStringList in which reimplemented the DelimitedText property. You can copy most of the code from the original implementation.
var
LList: TStringList;
s, LOutput: string;
begin
LList := TStringList.Create;
try
LList.Add('x');
LList.Add('y');
LList.Add('Func2(a, b, c)');
for s in LList do
LOutput := LOutput + s + ', ';
SetLength(LOutput, Length(LOutput) - 2);
m1.AddLine('result := Func1(' + LOutput + ')');
finally
LList.Free;
end;
end;
Had the same problem, here's how I fixed it:
s := Trim(StringList.Text)
that's all ;-)

How to use TComparer to sort a list of records?

I have a record that holds data about a file:
TYPE
RFile= record
public
FileName : string;
Resolution : Integer;
FileSize : Cardinal;
Rating : Byte;
end;
PFile= ^RFile;
And I keep a list of these files/records in a TList<>
TFileList= class(TList<PFile>)
procedure SortByFilename;
procedure SortByRating;
procedure SortByResolution;
procedure SortBySize;
end;
I have methods like SortByFilename, SortBySize, etc in which I sort the list.
I do "classic" sorting.
Now I want to upgrade to the new-and-cool System.Generics.Defaults.TComparer.
From what I understand I need to assign a comparer to my TFileList, like
TIntStringComparer = class(TComparer<String>)
public
function Compare(const Left, Right: String): Integer; override;
end;
How do I do this?
How do I deal with one comparer for each data field (filename, filesize, resolution)?
Update:
This code compiles but I have an EIntegerOverflow because the FileSize is a cardinal while I return an integer (diff between two cardinals).
Sort(TComparer<PFile>.Construct(
function(CONST A,B: PFile): integer
begin
Result:= A.FileSize - B.FileSize;
end
));
When you write a comparer for a numeric type, you should never use subtraction, even if your data type is signed.
Indeed, try to compare a = 100 and b = -2147483640 as Integers; clearly a > b, but subtraction will yield the wrong result.
Instead, you should always do something similar to
if a = b then
Result := 0
else if a < b then
Result := -1
else
Result := 1;
But Delphi's RTL already contains functions for this: there are several CompareValue overloads in the Math unit (for different types of integers and floats -- but, unfortunately, not for Cardinals).
Thus, although your snippet will work "most of the time" if you do
Result := Integer(A.FileSize) - Integer(B.FileSize)
this is not good enough: For one thing, not every Cardinal will fit in a Integer. Also, as noted above, subtraction is not the way to go.
In your case, you can simply use the if thing above directly, or you could create a new CompareValue overload for Cardinals. Or, you could do
Result := CompareValue(Int64(A.FileSize), Int64(B.FileSize)).
(Also, as others have stated in comments, you should reconsider if it is wise to use a Cardinal to store a file size in the first place. If you upgrade this to an Int64 or UInt64 you can write simply
Result := CompareValue(A.FileSize, B.FileSize).)

Resources