I'm using code from this article:
How to Convert Numbers (Currency) to Words
I can't seem to understand how the following code works exactly.
try
sIntValue := FormatFloat('#,###', trunc(abs(Number)));
sDecValue := Copy(FormatFloat('.#########', frac(abs(Number))), 2);
if (Pos('E', sIntValue) > 0) then // if number is too big
begin
Result := 'ERROR:';
exit;
end;
except
Result := 'ERROR:';
exit;
end;
How is it checking if the number is too big using the Pos() function? Why is it searching for E in an Integer? This make no sense to me. I would apprecaite any explanation (the code works just fine, I just want to understand why and how).
The code is checking for the use of scientific notation. That is where you write a number like 1000 as '1E3'.
The code is faintly ridiculous though. Hard to know why the author did not use the > comparison operator.
Related
This question already has an answer here:
Delphi check if character is in range 'A'..'Z' and '0'..'9'
(1 answer)
Closed 5 years ago.
for counter := 1 to lengthofpassword do
begin
currentletter:=password[counter];
currentascii:=Ord(currentletter);
if (96<currentascii<123) OR (64<currentascii<91) OR (47<currentascii<58) then
Writeln('valid')
else
asciicheck:=false;
end;
I know this code is wrong but I did it to explain what I want to ask. How can you specify ranges for an if statement? Before, I messed around with lots of if statements and my code wasn't working the way I wanted it to. Basically, I am making a procedure which checks the user input for anything other than uppercase and lowercase alphabet and numbers. This question is different because I was looking for how this problem could be solved using a Case Of statement.
for counter := 1 to lengthofpassword do
begin
currentletter:=password[counter];
currentascii:=Ord(currentletter);
if (currentascii<48) AND (currentascii>57) then
asciipoints:=asciipoints+1;
if (currentascii<65) AND (currentascii>90) then
asciipoints:=asciipoints+1;
if (currentascii<97) AND (currentascii>122) then
asciipoints:=asciipoints+1;
Writeln(asciipoints);
end;
I also tried to do it like this but then realised this wouldn't work because if one statement was satisfied, the others wouldn't be and the points based system wouldn't work either.
Glad you found the answer yourself.
Another way to make sure the password only contains upper and lower case characters and numbers is what I tried to point to: define a set of characters that are valid and check if each character in your password is in these valid characters.
So with a set defined like this:
const
ValidChars = ['A'..'Z', 'a'..'z', '0'..'9'];
you can use statements like
if password[I] in ValidChars then
This statement will however generate a compiler warning in Unicode Delphi, as the type in a set is limited to 256 possible values, and their ordinalities must fall between 0 and 255. This isn't the case for WideChar with 65.536 values. So the set of char defined is in fact a set of AnsiChar. For this task this is acceptable, as every character that needs to be checked is ASCII, so using the function CharInSet will not generate a compiler warning and have a defined behavior - returning False - if the password contains Unicode characters.
This is the resulting code:
const
ValidChars = ['A'..'Z', 'a'..'z', '0'..'9'];
var
I: Integer;
begin
for I := 1 to passwordlength do
begin
if CharInSet(password[I], ValidChars) then
Writeln('valid') // more likely to do nothing and invert the if statement
else
begin
asciicheck := False;
Break; // No need to look further, the check failed
end;
end;
end;
Multiple ranges is best expressed in a case statement:
begin
for counter := 1 to lengthofpassword do
begin
case Ord(password[counter]) of
48..57,
65..90,
97..122 :
Writeln('valid')
else
asciicheck:=false;
end;
end;
end;
Now, this works for characters < #128. If you are working in a unicode application and don't want the restriction of characters being the english alphabet, it is possible to use TCharHelper.IsLetterOrDigit.
if password[counter].IsLetterOrDigit then ...
Thanks to a comment up above, I have found a solution. I ended up using a Case Of statement like this:
for counter := 1 to lengthofpassword do
begin
currentletter:=password[counter];
currentascii:=Ord(currentletter);
case currentascii of
97..122 : asciicheck:=true;
65..90 : asciicheck:=true;
48..57 : asciicheck:=true;
else asciicheck:=false;
end;
end;
Thanks once again.
So I'm trying to determine if two different strings are the same with
if DerobModel.ConstructionCount > 22 then
begin
for i := 22 to DerobModel.ConstructionCount-1 do
begin
ConstructionName[i] := DerobModel.Constructions[i].Name;
ShowMessage(ConstructionName[i]);
ShowMessage(DerobModel.HouseProperties.StringValue['NWall']);
if ConstructionName[i]=DerobModel.HouseProperties.StringValue['NWall'] then
begin
ShowMessage('Hej');
igSurf[0]:=idWallCon[i];
end;
LayerCount[i] := DerobModel.Constructions[i].LayerCount;
idWallCon[i] := i+1;
end;
end;
The ShowMessage for both of the strings returns the same string but somehow it won't go in the if statement. Any ideas?
The = operator for strings is known to work. When strings s1 and s2 are equal, s1 = s2 evaluates true. Otherwise it evaluates false. The = operator has been known to work correctly in all versions of Delphi.
The conclusion to draw is that if the body of your if does not execute, then the two strings are not equal. Now that you know that the two strings are not equal, you can debug the program to work out why two things that you believed to be equal are in fact not equal.
Note that equality testing with = is exact. Letter case is significant. Whitespace is significant. And so on.
Your strings are different, simple as that.
If you want to figure out what exactly is different, you could write an else block portion to compare the strings in detail and show you exactly what is different.
if ConstructionName[i]=DerobModel.HouseProperties.StringValue['NWall'] then
begin
ShowMessage('Hej');
igSurf[0]:=idWallCon[i];
end
else
begin
if (Length(ConstructionName[i]) <>
Length(DerobModel.HouseProperties.StringValue['NWall'])) then
begin
ShowMessage('Length('+IntToStr(Length(ConstructionName[i]))+') <> Length('+
IntToStr(Length(DerobModel.HouseProperties.StringValue['NWall']))+')');
end
else
begin
for LCharPos := 1 to Length(ConstructionName[i]) do
begin
if (ConstructionName[i][LCharPos] <>
DerobModel.HouseProperties.StringValue['NWall'][LCharPos]) then
begin
//Here you might need to rather show the ordinal values of the
//characters to see the difference if they **look** the same due
//to the font of the message.
ShowMessage('Pos['+IntToStr(LCharPos)+'] "'+
ConstructionName[i][LCharPos]+'" <> "'+
DerobModel.HouseProperties.StringValue['NWall'][LCharPos]+'"');
end;
end;
end;
end;
The only thing I can think of that might unexpectedly cause "same" strings to be reported as different is: if they are different string types. E.g. if one is WideString and the other AnsiString, then:
There would have to be an implicit conversion to do the comparison.
And this means one of the strings would be changed.
The change could cause two strings that look the same to actually be different.
I'm doing the way I learned, that is:
with a FOR and taking the Index array one by one, but it is leaving too slow, would otherwise convert it to a String? that leaves quicker?
In my case it would be a Dynamic Array of ShortInt.
For example, given this input:
[0,20,-15]
I would like the following output:
0,20,-15
I suspect that your code is slow because it is performing unnecessary reallocations of the string. However, without seeing your code it's hard to be sure.
Probably the simplest way to code your algorithm is to use TStringBuilder. Whether or not that gives sufficient performance, only you can say.
sb := TStringBuilder.Create;
try
for i := 0 to high(buffer) do
begin
sb.Append(IntToStr(buffer[i]));
if i<high(buffer) then
sb.Append(',');
end;
str := sb.ToString;
finally
sb.Free;
end;
I have to check if I have duplicate paths in a FileListBox (FileListBox has the role of some kind of job list or play list).
Using Delphi's SameText, CompareStr, CompareText, takes 6 seconds. So I came with my own compare function which is (just) a bit faster but not fast enough. Any ideas how to improve it?
function SameFile(CONST Path1, Path2: string): Boolean;
VAR i: Integer;
begin
Result:= Length(Path1)= Length(Path2); { if they have different lenghts then obviously are not the same file }
if Result then
for i:= Length(Path1) downto 1 DO { start from the end because it is more likely to find the difference there }
if Path1[i]<> Path2[i] then
begin
Result:= FALSE;
Break;
end;
end;
I use it like this:
for x:= JList.Count-1 downto 1 DO
begin
sMaster:= JList.Items[x];
for y:= x-1 downto 0 DO
if SameFile(sMaster, JList.Items[y]) then
begin
JList.Items.Delete (x); { REMOVE DUPLICATES }
Break;
end;
end;
Note: The chance of having duplicates is small so Delete is not called often. Also the list cannot be sorted because the items are added by user and sometimes the order may be important.
Update:
The thing is that I lose the asvantage of my code because it is Pascal.
It would be nice if the comparison loop ( Path1[i]<> Path2[i] ) would be optimized to use Borland's ASM code.
Delphi 7, Win XP 32 bit, Tests were done with 577 items in the list. Deleting the items from list IS NOT A PROBLEM because it happens rarely.
CONCLUSION
As Svein Bringsli pointed, my code is slow not because of the comparing algorithm but because of TListBox. The BEST solution was provided by Marcelo Cantos. Thanks a lot Marcelo.
I accepted Svein's answer because it answers directly my question "how to make my comparison function faster" with "there is no point to make it faster".
For the moment I implemented the dirty and quick to implement solution: when I have under 200 files, I use my slow code to check the duplicates. If there are more than 200 files I use dwrbudr's solution (which is damn fast) considering that if the user has so many files, the order is irrelevant anyway (human brain cannot track so many items).
I want to thank you all for ideas and especially Svein for revealing the truth: (Borland's) visual controls are damn slow!
Don't waste time optimising the assembler. You can go from O(n2) to O(n log(n)) — bringing the time down to milliseconds — by sorting the list and then doing a linear scan for duplicates.
While you're at it, forget the SameFile function. The algorithmic improvement will dwarf anything you can achieve there.
Edit: Based on feedback in the comments...
You can perform an order-preserving O(n log(n)) de-duplication as follows:
Sort a copy of the list.
Identify and copy duplicated entries to a third list along with their duplication count minus one.
Walk the original list backwards as per your original version.
In the inner (for y := ...) loop, traverse the duplication list instead. If an outer item matches, delete it, decrement the duplication count, and delete the duplication entry if the count reaches zero.
This is obviously more complicated but it will still be orders of magnitude faster, even if you do horrible dirty things like storing duplication counts as strings, C:\path1\file1=2, and using code like:
y := dupes.IndexOfName(sMaster);
if y <> -1 then
begin
JList.Items.Delete(x);
c := StrToInt(dupes.ValueFromIndex(y));
if c > 1 then
dupes.Values[sMaster] = IntToStr(c - 1);
else
dupes.Delete(y);
end;
Side note: A binary chop would be more efficient than the for y := ... loop, but given that duplicates are rare, the difference ought to be negligible.
Using your code as a starting point, I modified it to take a copy of the list before searching for duplicates. The time went from 5,5 seconds to about 0,5 seconds.
vSL := TStringList.Create;
try
vSL.Assign(jList.Items);
vSL.Sorted := true;
for x:= vSL.Count-1 downto 1 DO
begin
sMaster:= vSL[x];
for y:= x-1 downto 0 DO
if SameFile(sMaster, vSL[y]) then
begin
vSL.Delete (x); { REMOVE DUPLICATES }
jList.Items.Delete (x);
Break;
end;
end;
finally
vSL.Free;
end;
Obviously, this is not a good way to do it, but it demonstrates that TFileListBox is in itself quite slow. I don't believe you can gain much by optimizing your compare-function.
To demonstrate this, I replaced your SameFile function with the following, but kept the rest of your code:
function SameFile(CONST Path1, Path2: string): Boolean;
VAR i: Integer;
begin
Result := false; //Pretty darn fast code!!!
end;
The time went from 5,6 seconds to 5,5 seconds. I don't think there's much more to gain there :-)
Create another sorted list with sortedList.Duplicates := dupIgnore and add your strings to that list, then back.
vSL := TStringList.Create;
try
vSL.Sorted := true;
vSL.Duplicates := dupIgnore;
for x:= 0 to jList.Count - 1 do
vSL.Add(jList[x]);
jList.Clear;
for x:= 0 to vSL.Count - 1 do
jList.Add(vSL[x]);
finally
vSL.Free;
end;
The absolute fastest way, bar none (as alluded to before) is to use a routine that generates a unique 64/128/256 bit hash code for a string (I use the SHA256Managed class in C#). Run down the list of strings, generate the hash code for the strings, check for it in the sorted hash code list, and if found then the string is a duplicate. Otherwise add the hash code to the sorted hash code list.
This will work for strings, file names, images (you can get the unique hash code for an image), etc, and I guarantee that this will be as fast or faster than any other impementation.
PS You can use a string list for the hash codes by representing the hash codes as strings. I've used a hex representation in the past (256 bits -> 64 characters) but in theory you can do it any way you like.
4 seconds for how many calls? Great performance if you call it a billion times...
Anyway, does Length(Path1) get evaluated every time through the loop? If so, store that in an Integer variable prior to looping.
Pointers may yield some speed over the strings.
Try in-lining the function with:
function SameFile(blah blah): Boolean; Inline;
That will save some time, if this is being called thousands of times per second. I would start with that and see if it saves anything.
EDIT: I didn't realize that your list wasn't sorted. Obviously, you should do that first! Then you don't have to compare against every other item in the list - just the prior or next one.
I use a modified Ternary Search Tree (TST) to dedupe lists. You simply load the items into the tree, using the whole string as the key, and on each item you can get back an indication if the key is already there (and delete your visible entry). Then you throw away the tree. Our TST load function can typically load 100000 80-byte items in well under a second. And it could not take any more than this to repaint your list, with proper use of begin- and end-update. The TST is memory-hungry, but not so that you would notice it at all if you only have of the order of 500 items. And much simpler than sorting, comparisons and assembler (if you have a suitable TST implementation, of course).
No need to use a hash table, a single sorted list gives me a result of 10 milliseconds, that's 0.01 seconds, which is about 500 times faster! Here is my test code using a TListBox:
procedure TForm1.Button1Click(Sender: TObject);
var
lIndex1: Integer;
lString: string;
lIndex2: Integer;
lStrings: TStringList;
lCount: Integer;
lItems: TStrings;
begin
ListBox1.Clear;
for lIndex1 := 1 to 577 do begin
lString := '';
for lIndex2 := 1 to 100 do
if (lIndex2 mod 6) = 0 then
lString := lString + Chr(Ord('a') + Random(2))
else
lString := lString + 'a';
ListBox1.Items.Add(lString);
end;
CsiGlobals.AddLogMsg('Start', 'Test', llBrief);
lStrings := TStringList.Create;
try
lStrings.Sorted := True;
lCount := 0;
lItems := ListBox1.Items;
with lItems do begin
BeginUpdate;
try
for lIndex1 := Count - 1 downto 0 do begin
lStrings.Add(Strings[lIndex1]);
if lStrings.Count = lCount then
Delete(lIndex1)
else
Inc(lCount);
end;
finally
EndUpdate;
end;
end;
finally
lStrings.Free;
end;
CsiGlobals.AddLogMsg('Stop', 'Test', llBrief);
end;
I'd also like to point out that your solution would take an extreme amount of time if applied to a huge list (like containing 100,000,000 items or more). Even constructing a hashtable or sorted list would take too much time.
In cases like that you could try another approach : Hash each member, but instead of populating a full-blown hashtable, create a bitset (large enough to contain a close factor to as many slots as there are input items) and just set each bit at the offset indicated by the hashfunction. If the bit was 0, change it to 1. If it was already 1, take note of the offending string-index in a separate list and continue. This results in a list of string-indexes that had a collision in the hash, so you'll have to run it a second time to find the first cause of those collisions. After that, you should sort & de-dupe the string-indexes in this list (as all indexes apart from the first one will be present twice). Once that's done you should sort the list again, but this time sort it on the string-contents in order to easily spot duplicates in a following single scan.
Granted it could be a bit extreme to go this all this length, but at least it's a workable solution for very large volumes! (Oh, and this still won't work if the number of duplicates is very high, when the hash-function has a bad spread or when the number of slots in the 'hashtable' bitset is chosen too small - which would give many collisions which aren't really duplicates.)
I want to know how to increase the value in a FOR-loop statement.
This is my code.
function Check(var MemoryData:Array of byte;MemorySignature:Array of byte;Position:integer):boolean;
var i:byte;
begin
for i := 0 to Length(MemorySignature) - 1 do
begin
while(MemorySignature[i] = $FF) do inc(i); //<< ERROR <<
if(memorydata[i + position] <> MemorySignature[i]) then Result:=false;
end;
Result := True;
end;
The error is: E2081 Assignment to FOR-Loop variable 'i'.
I'm trying to translate an old code from C# to Delphi,but I can't increase 'i'.
Increasing 'i' is not the only way to go,but I want to know where the problem is.
Of course the others are (generally) correct. What wasn't said, is that 'i' in your loop doesn't exist. Delphi uses a CPU register for it. That's why you cannot change it and that's why you should use a 'for' loop (not a 'while') because the 'for' is way faster. Here is your code modified (not tested but I think that you got the idea) - also imho you had some bugs - fixed them also:
function Check(var MemoryData:Array of byte;MemorySignature:Array of byte;Position:integer):boolean;
var i:byte;
begin
Result := True; //moved at top. Your function always returned 'True'. This is what you wanted?
for i := 0 to Length(MemorySignature) - 1 do //are you sure??? Perhaps you want High(MemorySignature) here...
begin
if MemorySignature[i] <> $FF then //speedup - '<>' evaluates faster than '='
begin
Result:=memorydata[i + position] <> MemorySignature[i]; //speedup.
if not Result then
Break; //added this! - speedup. We already know the result. So, no need to scan till end.
end;
end;
end;
...also MemorySignature should have a 'const' or 'var'. Otherwise as it is now the array gets copied. Which means slowdown at each call of 'Check'. Having a 'var' the things are much faster with code unchanged because AFAIS the MemorySignature isn't changed.
HTH
in this case, you can just do a 'continue' instead of inc(i)
In addition to what Lasse wrote, assigning to a loop variable is generally considered a code smell. It makes code harder to read (if you want to leave the loop premataturely, you can express that a lot clearer using break/continue), and is often done by accident, causing all kind of nasty side-effects. So instead of jumping through hoops to make the compiler not do its optimizing fu on any loop where the loop variable is touched, Borland (now CodeGear) bit the bullet and made assigning to the loop variable illegal.
If you really want to mess about manually with loop indices, consider using a while-loop.
If you need to alter a loop counter inside a loop, try using a while loop instead.
BTW, you need your
Result := True
line to be the first line of the function for it to work properly. As it is, it will always return True.
The problem is that the compiler has taken the original FOR-loop code and assumed it knows what is happening, and thus it can optimize the code by outputting specific CPU instructions that runs the fastest, with those assumptions.
If it allowed you to mess with the variable value, those assumptions might go out the window, and thus the code might not work, and that's why it doesn't allow you to change it.
What you should do instead is just have a separate variable that you're actually using, and only use the FOR-loop indexing variable to keep track of how many iterations you've currently executed.
As an example, a typical optimization might be to write CPU-instructions that will stop iterating when the index register drops to zero, rewriting the loop in such a way that it internally counts down, instead of up, and if you start messing with the variable, it could not rewrite the code like that.
As per Mike Sutton, what you need is a while loop, not a for loop.
function Check(var MemoryData: Array of byte;
MemorySignature: Array of byte; Position: Integer):Boolean;
var
i:byte;
begin
Result := True;
i := 0;
while i < Length(MemorySignature) do
begin
while(MemorySignature[i] = $FF) do
Inc(i);
if(MemoryData[i + position] <> MemorySignature[i]) then
Result := False;
Inc(i);
end;
end;
The Delphi implementation of "for" is optimised, but as a result it is less flexible than the C-style