How to fast copy from hash to listview? - delphi

There is a hash pas file http://gpdelphiunits.googlecode.com/svn-history/r4/trunk/src/GpStringHash.pas
We can create hash and add key - value
Question 1:
We want to know how to iterate key - value and copy data to listview.
Question 2: is there a way to fast copy like assign method to it?
Thank you very much in advance.
Dear gabr, Thank you so much for your immediate reply and your hash file. Is there doc or help files or examples or demo for your code ? Thank you so much again.
Just test, I do not know where i did wrong
Thank you so much. I just used your code but there is the following error prompt. Or I made some mistakes:
procedure TForm8.ab;
var
a: TGpStringHash;
i,j, fr:integer;
k: string;
enlist: TGpStringHashenumerator;
kv: TGpStringHashKV;
begin
a:=TGpStringHash.Create;
kv:=TGpStringHashKV.Create;
enlist:= TGpStringHashenumerator.Create(a);
for j:=1 to 10 do begin
if a.HasKey(inttostr(j)) then begin
fr:=a.ValueOf(inttostr(j));
a.Update(inttostr(j),fr+1);
end
else begin
a.Add(inttostr(j),1);
end;
end;
for i:=0 to a.Count -1 do begin
kv:=enlist.GetCurrent;
memo1.Lines.Add(kv.Key + inttostr(kv.value) );
end;
end; /// Division by Zero ERROr ///FindBucket(const key: string): cardinal;
ANSWER:
You're using enumerator improperly. Don't instantiate it in front and always use MoveNext to move to the next element.
// fill 'a' as above
enlist := TGpStringHashenumerator.Create(a);
while enList.MoveNext do begin
kv:=enlist.GetCurrent;
memo1.Lines.Add(kv.Key + inttostr(kv.value) );
end;

1) Use the latest version. It implements enumerators for all containers.
2) No.
EDIT:
I have committed my internal GpStringHash test app to the repository. It can server as a demo on how to use GpStringHash classes.
To enumerate TGpStringHash you would use
var
hash: TGpStringHash;
kv: TGpStringHashKV;
for kv in hash do
// do something with kv.Key and kv.Value
If you're using an older Delphi without support for enumerators, you can use ForEach method with an external callback method.
procedure TGpStringHash.ForEach(enumerator: TGpStringHashEnumMethod);
var
enum: TGpStringHashEnumerator;
begin
enum := GetEnumerator;
try
while enum.MoveNext do
enumerator(enum.Current);
finally FreeAndNil(enum); end;
end; { TGpStringHash.ForEach }

Related

How to correctly use IFileOperation in Delphi to delete the files in a folder

I'm trying to create a simple example of using IFileOperation to delete the files in a
given directory, to include in the answer to another q for comparison with other methods.
Below is the code of my MRE. It
successfully creates 1000 files in a subdirectory off C:\Temp and then attempts to delete
them in the DeleteFiles method. This supposedly "easy" task fails but I'm not sure
exactly where it comes off the rails. The comments in the code show what I'm expecting
and the actual results. On one occasion, instead of the exception noted, I got a pop-up
asking for confirmation to delete an item with an odd name which was evidently an array of
numbers referring to a shell item, but my attempt to capture it using Ctrl-C failed;
I'm fairly sure I'm either missing a step or two, misusing the interfaces involved
or both. My q is, could anybody please show the necessary corrections to the code to get IFileOperation.DeleteItems() to delete the files in question, as I am completely out of my depth with this stuff? I am not interested in alternative methods of deleting these files, using the shell interfaces or otherwise.
procedure TForm2.DeleteFiles;
var
iFileOp: IFileOperation;
iIDList : ItemIDList;
iItemArray : IShellItemArray;
iArray : Array[0..1] of ItemIDList;
Count : DWord;
begin
iFileOp := CreateComObject(CLSID_FileOperation) as IFileOperation;
iIDList := ILCreateFromPath(sPath)^;
// IFileOperation.DeleteItems seems to require am IShellItemArray, so the following attempts
// to create one
// The definition of SHCreateShellItemArrayFromIDLists
// seems to require a a zero-terminated array of ItemIDLists so the next steps
// attempt to create one
ZeroMemory(#iArray, SizeOf(iArray));
iArray[0] := iIDList;
OleCheck(SHCreateShellItemArrayFromIDLists(1, #iArray, iItemArray));
// Next test the number of items in iItemArray, which I'm expecting to be 1000
// seeing as the CreateFiles routine creats that many
OleCheck(iItemArray.GetCount(Count));
Caption := IntToStr(Count); // Duh, this shows Count to be 1, not the expected 1000
OleCheck(iFileOp.DeleteItems(iItemArray));
OleCheck( iFileOp.PerformOperations );
// Returns Exception 'No object for moniker'
end;
procedure TForm2.Button1Click(Sender: TObject);
begin
DeleteFiles;
end;
procedure CreateFiles;
var
i : Integer;
SL : TStringList;
FileName,
FileContent : String;
begin
SL := TStringList.Create;
try
if not (DirectoryExists(sPath)) then
MkDir(sPath);
SL.BeginUpdate;
for i := 0 to 999 do begin
FileName := Format('File%d.Txt', [i]);
FileContent := Format('content of file %s', [FileName]);
SL.Text := FileContent;
SL.SaveToFile(sPath + '\' + FileName);
end;
SL.EndUpdate;
finally
SL.Free;
end;
end;
procedure TForm2.FormCreate(Sender: TObject);
begin
CreateFiles;
end;
You are leaking the memory returned by ILCreateFromPath(), you need to call ILFree() when you are done using the returned PItemIDList.
Also, you should not be dereferencing the PItemIDList. SHCreateShellItemArrayFromIDLists() expects an array of PItemIDList pointers, but you are giving it an array of ItemIDList instances.
Try this instead:
procedure TForm2.DeleteFiles;
var
iFileOp: IFileOperation;
iIDList : PItemIDList;
iItemArray : IShellItemArray;
Count : DWord;
begin
iFileOp := CreateComObject(CLSID_FileOperation) as IFileOperation;
iIDList := ILCreateFromPath(sPath);
try
OleCheck(SHCreateShellItemArrayFromIDLists(1, #iIDList, iItemArray));
finally
ILFree(iIDList);
end;
// Next test the number of items in iItemArray, which I'm expecting to be 1000
// seeing as the CreateFiles routine creates that many
OleCheck(iItemArray.GetCount(Count));
Caption := IntToStr(Count); // Duh, this shows Count to be 1, not the expected 1000
OleCheck(iFileOp.DeleteItems(iItemArray));
OleCheck( iFileOp.PerformOperations );
// Returns Exception 'No object for moniker'
end;
That being said, even if this were working correctly, you are not creating an IShellItemArray containing 1000 IShellItems for the individual files. You are creating an IShellItemArray containing 1 IShellItem for the C:\Temp subdirectory itself.
Which is fine if your goal is to delete the whole folder. But in that case, I would suggest using SHCreateItemFromIDList() or SHCreateItemFromParsingName() instead, and then pass that IShellItem to IFileOperation.DeleteItem().
But, if your goal is to delete the individual files without deleting the subdirectory as well, then you will have to either:
get the IShellFolder interface for the subdirectory, then enumerate the relative PIDLs of its files using IShellFolder.EnumObjects(), and then pass the PIDLs in an array to SHCreateShellItemArray().
get the IShellFolder interface of the subdirectory, then query it for an IDataObject interface using IShellFolder.GetUIObjectOf(), and then use SHCreateShellItemArrayFromDataObject(), or just give the IDataObject directly to IFileOperation.DeleteItems().
get an IShellItem interface for the subdirectory, then query its IEnumShellItems interface using IShellItem.BindToHandler(), and then pass that directly to IFileOperation.DeleteItems().

What type of collection should I use? delphi

I want to use one keys for two values in Delphi some thing like this
TDictionary<tkey, tfirstvalue,tsecondvalue>;
Put your values into a compound structure like a record. Then use that record type as your dictionary value type.
Delphi has not Tuple type.
I don't know your purpose but may dynamic array of record type help.
Type
Tdict_ = reocord
tkey:integer;
tfirstvalue,Tsecondvalue :string;
end;
var
Tdict:array of tdict_
...
procedure adddata(Tkey:integer;tfirstvalue:string;Tsecondvalue :string);
begin
setlength(tdict,length(tdict)+1);
tdict[length(tdict)-1].tkey:=tkey;
tdict[length(tdict)-1].tfirstvalue:=tfirstvalue;
tdict[length(tdict)-1].tsecondtvalue:=tsecondvalue;
end;
but you must write your own "find" function for return index of array .
for example
Function find(tkey:integer):integer;
var i:Integer;
begin
for i:=0 to length(Tdict)-1 do
if tdict[i].tkey=i then
begin
result:=i;
break;
end;
end;
Function deletecalue(tkey:integer):integer;
var i,j:Integer;
begin
i:=find(tkey)
for j:=i to length(Tdict)-2 do
tdict[j]:=tdict[j+1];
setlength(tdict,length(tdict)-1);
end;
if keys are strings type must be changed, but it will be slow for huge date .
Also read This:
https://github.com/malcolmgroves/generics.tuples
TDictionary<TKey, TPair<TFirstValue, TSecondValue>>, as #RudyVelthuis commented, is possible and works. However, TPair (found in System.Generics.Collections) is not meant for that - the two values are named Key and Value, which doesn't make sense here. We should make a copy of TPair, which could be named TTuple.
You can then do MyDictionary.Add('key', TTuple<string, string>.Create('firstvalue', 'secondvalue'));
Since it is implemented as a record (value type), there is no need to free it.

I want to know how to "fill" the TStings defined in CollectLangString?

What is the "engine" under TLang...
TLang is ok in my small project but with larger project It is difficult to manage. I try to figure how it works. I've fund many proc and functions in FMX.Types. I've focus on: CollectLangStart, CollectLangFinish and CollectLangStrings. Calling those function can be compiled but I don't know where and when this TStrings is filled, the TStrings stay empty. The documentation talk about "scene" but it is very limited.
TStyleManager.UpdateScenes must be called between CollectLangStart and copying CollectLangStrings
var
Str: TStrings;
begin
CollectLangStart;
TStyleManager.UpdateScenes;
Str := TStringList.Create;
try
Str.Assign(CollectLangStrings);
Str.SaveToFile(ExtractFilePath(ParamStr(0)) + 'lang.lng');
finally
Str.Free;
CollectLangFinish;
end;
end;

How to change THash.Hash at runtime

How can I change the default THash.Hash algorythm from trhe default SHA-1 to MD5?
The following does not works:
var
StringHash: THash;
begin
StringHash.Create(nil);
StringHash.Hash := 'MD5';
end;
Edit:
Yes you are all right: I apologize for not having mentioned the fact that THash is a class of the new TurboPower LockBox 3.
My apologies again for this omission!
Anyway Sean has already given the answer I was looking for.
Thank you all
Assuming that you are referring to the THash component of TurboPower Lockbox, you can select the hashing algorithm at run-time like so:
function FindHashOfBananaBananaBanana: TBytes;
var
StringHash: THash;
Lib: TCrypographicLibrary;
begin
StringHash := THash.Create( nil);
Lib := TCrypographicLibrary( nil);
try
StringHash.CryptoLibrary := Lib;
StringHash.HashId := SHA512_ProgId; // Find constants for other algorithms
// in unit uTPLb_Constants.
StringHash.HashAnsiString('Banana banana banana');
SetLength( result, StringHash.HashOutputValue.Size);
StringHash.HashOutputValue.Read( result[0], StringHash.HashOutputValue.Size);
StringHash.Burn
finally
StringHash.Free;
Lib.Free
end
end;
Your example code is invalid. The variable type is THASH, the variable name is STRINGHASH. When you construct an instance of a class the format is typically:
var
StringHash:THash;
begin
StringHash := THash.Create();
try
DoSomethingWithStringHash;
finally
StringHash.Free()
end
end;
Fix your example and come back with more details.

Improve speed of own debug visualizer for Delphi 2010

I wrote Delphi debug visualizer for TDataSet to display values of current row, source + screenshot: http://delphi.netcode.cz/text/tdataset-debug-visualizer.aspx . Working good, but very slow. I did some optimalization (how to get fieldnames) but still for only 20 fields takes 10 seconds to show - very bad.
Main problem seems to be slow IOTAThread90.Evaluate used by main code shown below, this procedure cost most of time, line with ** about 80% time. FExpression is name of TDataset in code.
procedure TDataSetViewerFrame.mFillData;
var
iCount: Integer;
I: Integer;
// sw: TStopwatch;
s: string;
begin
// sw := TStopwatch.StartNew;
iCount := StrToIntDef(Evaluate(FExpression+'.Fields.Count'), 0);
for I := 0 to iCount - 1 do
begin
s:= s + Format('%s.Fields[%d].FieldName+'',''+', [FExpression, I]);
// FFields.Add(Evaluate(Format('%s.Fields[%d].FieldName', [FExpression, I])));
FValues.Add(Evaluate(Format('%s.Fields[%d].Value', [FExpression, I]))); //**
end;
if s<> '' then
Delete(s, length(s)-4, 5);
s := Evaluate(s);
s:= Copy(s, 2, Length(s) -2);
FFields.CommaText := s;
{ sw.Stop;
s := sw.Elapsed;
Application.MessageBox(Pchar(s), '');}
end;
Now I have no idea how to improve performance.
That Evaluate needs to do a surprising amount of work. The compiler needs to compile it, resolving symbols to memory addresses, while evaluating properties may cause functions to be called, which needs the debugger to copy the arguments across into the debugee, set up a stack frame, invoke the function to be called, collect the results - and this involves pausing and resuming the debugee.
I can only suggest trying to pack more work into the Evaluate call. I'm not 100% sure how the interaction between the debugger and the evaluator (which is part of the compiler) works for these visualizers, but batching up as much work as possible may help. Try building up a more complicated expression before calling Evaluate after the loop. You may need to use some escaping or delimiting convention to unpack the results. For example, imagine what an expression that built the list of field values and returned them as a comma separated string would look like - but you would need to escape commas in the values themselves.
Because Delphi is a different process than your debugged exe, you cannot direct use the memory pointers of your exe, so you need to use ".Evaluate" for everything.
You can use 2 different approaches:
Add special debug dump function into executable, which does all value retrieving in one call
Inject special dll into exe with does the same as 1 (more hacking etc)
I got option 1 working, 2 should also be possible but a little bit more complicated and "ugly" because of hacking tactics...
With code below (just add to dpr) you can use:
Result := 'Dump=' + Evaluate('TObjectDumper.SpecialDump(' + FExpression + ')');
Demo code of option 1, change it for your TDataset (maybe make CSV string of all values?):
unit Unit1;
interface
type
TObjectDumper = class
public
class function SpecialDump(aObj: TObject): string;
end;
implementation
class function TObjectDumper.SpecialDump(aObj: TObject): string;
begin
Result := '';
if aObj <> nil then
Result := 'Special dump: ' + aObj.Classname;
end;
initialization
//dummy call, just to ensure it is linked c.q. used by compiler
TObjectDumper.SpecialDump(nil);
end.
Edit: in case someone is interested: I got option 2 working too (bpl injection)
I have not had a chance to play with the debug visualizers yet, so I do not know if this work, but have you tried using Evaluate() to convert FExpression into its actual memory address? If you can do that, then type-cast that memory address to a TDataSet pointer and use its properties normally without going through additional Evaluate() calls. For example:
procedure TDataSetViewerFrame.mFillData;
var
DS: TDataSet;
I: Integer;
// sw: TStopwatch;
begin
// sw := TStopwatch.StartNew;
DS := TDataSet(StrToInt(Evaluate(FExpression)); // this line may need tweaking
for I := 0 to DS.Fields.Count - 1 do
begin
with DS.Fields[I] do begin
FFields.Add(FieldName);
FValues.Add(VarToStr(Value));
end;
end;
{
sw.Stop;
s := sw.Elapsed;
Application.MessageBox(Pchar(s), '');
}
end;

Resources