Getting File Sizes > And then Getting the Total Size? - delphi

This should be easy, but I cannot seem to get it right as I seem to be confusing myself and converting to and from strings, integers and floats and whatnot.
Basically, I am populating a TListView with FileNames in one column, and in another column returning the File Size to the corresponding FileName. I am using a rather neat function found from here, which looks like this:
function FileSizeStr ( filename: string ): string;
const
// K = Int64(1000); // Comment out this line OR
K = Int64(1024); // Comment out this line
M = K * K;
G = K * M;
T = K * G;
var
size: Int64;
handle: integer;
begin
handle := FileOpen(filename, fmOpenRead);
if handle = -1 then
result := 'Unable to open file ' + filename
else try
size := FileSeek ( handle, Int64(0), 2 );
if size < K then result := Format ( '%d bytes', [size] )
else if size < M then result := Format ( '%f KB', [size / K] )
else if size < G then result := Format ( '%f MB', [size / M] )
else if size < T then result := Format ( '%f GB', [size / G] )
else result := Format ( '%f TB', [size / T] );
finally
FileClose ( handle );
end;
end;
This returns values such as: 235.40 KB
So with the above, my TListView may be populated like so:
Now in the Label Data Size, I would like to return the Total Size of the Files in the Listview, so in this example, the values from the Size column would need adding up to return the Total Size, something like:
1.28 MB + 313.90 KB + 541.62 KB + 270.96 KB
Obviously it cannot be added on just like that, because the values contain decimal points, some values may be in Kb, and other in Mb etc. This is my problem, I cannot think of an easy solution to add (get) the Total Size of the Files, and then return it in the same formatted string as shown.
I would really appreciate some insight or tips how to work with this kind of data, I am just endlessly confusing myself with different conversions etc and not really sure which way to do this.
Many Thanks in advance :)
UPDATE 1
Following the advice from Marc B, I changed the function to the following which seems to work:
var
iFileSize: Int64;
implementation
function GetSizeOfFile(FileName: string): Int64;
var
Handle: Integer;
begin
Handle := FileOpen(FileName, fmOpenRead);
if Handle = -1 then
MessageDlg('Unable to open file ' + FileName, mtError, [mbOk], 0)
else try
iFileSize := iFileSize + FileSeek(Handle, Int64(0), 2);
finally
FileClose(Handle);
end;
Result := iFileSize;
end;
function FormatFileSize(AValue: Int64): string;
const
K = Int64(1024);
M = K * K;
G = K * M;
T = K * G;
begin
if AValue < K then Result := Format ( '%d bytes', [AValue] )
else if AValue < M then Result := Format ( '%f KB', [AValue / K] )
else if AValue < G then Result := Format ( '%f MB', [AValue / M] )
else if AValue < T then Result := Format ( '%f GB', [AValue / G] )
else Result := Format ( '%f TB', [AValue / T] );
end;
It may be useful for anyone else should they need it :)
UPDATE 2
Additionally, see the answer Ken White posted which provides more valuable information, and a cleaner update of the GetSizeOfFile function, which works great:

Separate the "get file information" from the "format the size string" into two separate functions. The file information function fetches the file size and adds it to a running total, THEN calls the formatting function to convert the simple integer into the "nice" string.

The easiest way would be to change your function to return the file size, and use a separate function to format the results.
I know you've already accepted an answer, but the updated code you posted has a couple of problems (one was in the original version, too).
First, your method of getting the file size is extremely slow, especially if you're going to be using this to list a lot of files. You're actually opening the file, moving the file pointer to the end of the file to get the size, and then closing the file. Also, this may fail if the file is open by another application exclusively.
Second, your new version of GetSizeOfFile has a logic error. You're adding to the global cumulative value every time (which is what you want), but you're also returning that new global value, which you don't want according to the sample image you posted.
Here's a replacement for GetSizeOfFile that should work for you, along with sample use:
function GetSizeOfFile( const FileName: String ): Int64;
var
Rec : TSearchRec;
begin
Result := 0;
if (FindFirst(FileName, faAnyFile, Rec) = 0) then
begin
Result := Rec.Size;
FindClose(Rec);
end;
end;
Sample use:
var
FileSize: Int64;
FileSizeString: string;
begin
{ Whatever code }
FileSize := GetSizeOfFile(SomeFileName);
iFileSize := iFileSize + NewSize;
FileSizeString := FormatFileSize(NewSize);
{ Add your file to your ListView.}
end;

Related

How to print code128C?

I am trying to print a code128C (numbers only) but I believe that the way of sending the data is incorrect ... at the time of reading the code the conversion does not result in the data initially informed.
In code128A I submit an ASCCI code, the printer converts to hex and print...the reader convert it back to ASCII.
In code128C if I submit an ASCCI, at the time of reading the reader converts to decimal, which does not result in the initial value.
EX:
128A Input: '1' Printer: 31 Reading: 1
128C Input: '1' Printer: 31 Reading: 49
I imagine that I should submit the input code already in integer .... but as the command is composed of other information I do not know how to send it in integer.
This is the code of code128A:
ComandoAnsiString := tp.cod128A('12'); //Data entry
function TTP650.cod128A(cod: AnsiString): AnsiString;
begin
// Fill out the CODE 128 printing protocol
Result := #29+#107+#73 + chr(length(cod)+2) + #123+#65 + cod;
end;
WritePrinter( HandleImp, PAnsiChar(ComandoAnsiString), Length(ComandoAnsiString),
CaracteresImpressos); //send to printer
This is the code I've been trying with code128C:
ComandoAnsiString := tp.cod128C('12');
function TTP650.cod128C(cod: AnsiString): AnsiString;
begin
Result := #29+#107+#73 + chr(length(cod)+2) + #123+#67 + cod;
end;
WritePrinter( HandleImp, PAnsiChar(ComandoAnsiString), Length(ComandoAnsiString),
CaracteresImpressos);
I'm dealing with a thermal printer and one codebar reader simple, default.
The sending codes(WritePrinter) are from the library WinSpool ... the rest are codes written by me.
Important code information is on pages 47 to 50 of the guide.
Guide
Assuming users will enter the wanted barcodes as a string of digits which may be stored somewhere as string and at the time of printing, passed to the printing function as human readable string.
The printing function will then convert to an array of bytes, packing the digits according to CODE C (each pair of two decimal digits, forming a value 00..99, stored in a byte). Iow, if the entry string of digits is e.g. '123456', then this is represented by three bytes with values 12, 34, 56.
function cod128C(const cod: string): TBytes;
const
GS = 29; // GS - Print bar code
k = 107; // k - -"-
m = 73; // m - CODE128
CS = 123; // { - select code set //}
CC = 67; // C - CODE C
var
i, len, n, x: integer;
s: string;
begin
len := Length(cod);
if len = 0 then exit;
// raise for odd number of digits in cod, ...
// if Odd(len) then
// raise Exception.Create('cod must have even number of digits');
s := cod;
// ... alternatively assume a preceeding zero digit before the first digit
// in cod
if Odd(len) then
begin
s := '0'+s;
inc(len);
end;
len := len div 2; // we pack 2 digits into one byte
SetLength(result, 6 + len);
result[0] := GS;
result[1] := k;
result[2] := m;
result[3] := 2 + len; // length of cod, + 2 for following code set selector
result[4] := CS;
result[5] := CC;
n := length(s);
i := 1; // index to S
x := 6; // index to result
while i < n do
begin
result[x] := StrToInt(MidStr(s, i, 2));
inc(i, 2);
inc(x, 1);
end;
end;
And with a form with a button, edit and memo you can test the function and send it to your printer with the following.
procedure TForm1.Button1Click(Sender: TObject);
var
cmnd: TBytes;
i: integer;
s: string;
begin
cmnd := cod128C(Edit1.Text);
for i := 0 to Length(cmnd)-1 do
s := s+IntToStr(cmnd[i])+', ';
Memo1.Lines.Add(s);
WritePrinter( HandleImp, #cmnd[0], Length(cmnd), CaracteresImpressos);
end;
You may want to add a check for only decimal digits in the input string, but I leave that to you.

Sort several arrays together and return the ranking number in the all-arrays combined score

I have 2 tables like this
As you can see, if you look at Total you can see the score of each player in 3 rounds. I have to do a list (from the 1st to the 12th) indicating the highest score.
Here the player with 28 points, must have the number 1 (instead of that 8 which is generated by default), the player with 22 must have the number 2 instead of 11... So I have to sort the TOTAL columns and return the position in the correct label.
When I click the button I underlined, the procedure is called:
var vettore:array[1..12] of integer;
indici:array[1..12] of integer;
i:smallint;
begin
for i := 1 to 6 do
begin
vettore[i]:= StrToInt(StringGrid1.Cells[5,i]); //col,row
indici[i] := i;
end;
for i := 6 to 12 do
begin
vettore[i]:= StrToInt(StringGrid2.Cells[5,i]); //col,row
indici[i] := i;
end;
In this way I load inside vettore all the TOTAL numbers in the rows of both tables, and in indici you can find the number of the label on the right of the table (they indicates the position). Now I thought I could use any sorting method since I have only 12 elements (like the Quick Sort).
My problem is this: how can I change the labels texts (the ones on right of the tables) according with the sorted array? It's like the picture above shows.
Every label is called (starting from 1) mvp1, mvp2, mvp3, mvp4... I think this can be helpful because if (maybe) I will have to do a for loop for change the text of each label, I can use a TFindComponent.
If it could be helpful, here there is the function I wrote with javascript on my website (it works):
var totals = [], //array with the scores
indices = []; //array with the indices
for (var i=0; i<6; i++) {
totals[i] = parseInt(document.getElementById('p'+i).value, 10);
indices[i] = i;
}
for (var i=6; i<12; i++) {
totals[i] = parseInt(document.getElementById('p'+i).value, 10);
indices[i] = i;
}
indices.sort(function(a, b) {
return totals[b]- totals[a];
});
for (var i=0; i<indices.length; i++) {
document.getElementById('mvp'+(indices[i]+1)).value = (i+1);
}
AS. Since only delphi is listed in tags, that means that any Delphi version is okay. I'd refer to delphi-xe2.
1st we would use Advanced Records to hold the data for a single participant. Some links are below, google for more.
http://docwiki.embarcadero.com/RADStudio/XE5/en/Structured_Types#Records_.28advanced.29
http://delphi.about.com/od/adptips2006/qt/newdelphirecord.htm
http://sergworks.wordpress.com/2012/03/13/record-constructors-in-delphi/
.
type
TClanResults = record
public
type All_GPs = 1..3;
var GP: array [All_GPs] of Cardinal;
var Players: string;
var Clan_ID: integer;
private
function CalcTotal: Cardinal;
function CalcAverage: single; inline;
public
property Total: Cardinal read CalcTotal;
property AVG: single read CalcAverage;
end;
{ TClanResults }
function TClanResults.CalcAverage: single;
begin
Result := Self.Total * ( 1.0 / Length(GP) );
end;
function TClanResults.CalcTotal: Cardinal;
var score: cardinal;
begin
Result := 0;
for score in GP do
Inc(Result, score);
end;
The expression Self.Total * ( 1.0 / Length(GP) ); can be also written as Self.Total / Length(GP). However i'd like to highlight some Delphi quirks here.
in Pascal there are two division operators: float and integer; 3 div 2 = 1 and 3 / 2 = 1.5. Choosing wrong one causes compilation errors at best and data precision losses at worst.
I'd prefer explicit typecast from integer Length to float, but Delphi does not support it. So i multiply by 1.0 to cast. Or i may add 0.0.
Division takes a lot longer than multiplication - just do it with pen and paper to see. When you have a data-crunching loop, where all elements are divided by the same number, it is good idea to cache 1 / value into a temp variable, and then mutiply each element by it instead. Since GP is of fixed size, it is compiler that calculates (1.0 / Length(GP)) and substitutes this constant. If you would allow different clans to have different amount of games - and turn GP into being dynamic arrays of different sizes - you would be to explicitly add a variable inside the function and to calc coeff := 1.0 / Length(GP); before loop started.
Now we should make a container to hold results and sort them. There can be several approaches, but we'd use generics-based TList<T>.
http://docwiki.embarcadero.com/Libraries/XE5/en/System.Generics.Collections.TList
http://docwiki.embarcadero.com/Libraries/XE5/en/System.Generics.Collections.TList.Sort
http://docwiki.embarcadero.com/Libraries/XE5/en/System.Generics.Defaults.TComparer.Construct
http://docwiki.embarcadero.com/Libraries/XE5/en/System.Generics.Defaults.TComparison
The TList is an object, so you would have to CREATE it and to FREE it. I think you can make it a PUBLIC property of your MainForm, then create the list in TMainForm.OnCreate event and free it in TMainForm.OnDestroy event.
Another, lazier approach, would be using a regular dynamic array and its extensions.
http://docwiki.embarcadero.com/RADStudio/XE5/en/Structured_Types#Dynamic_Arrays
http://docwiki.embarcadero.com/Libraries/XE5/en/System.TArray
http://docwiki.embarcadero.com/Libraries/XE5/en/System.SetLength
http://docwiki.embarcadero.com/Libraries/XE5/en/System.Generics.Collections.TArray.Sort
http://docwiki.embarcadero.com/CodeExamples/XE5/en/Generics_Collections_TArray_(Delphi)
However, i'll use TList below. Again, i assume that other routines in you program already and correctly create and destroy the given var ClanData: TList<TClanResults>; object instance.
type
TClansTable = TList<TClanResults>;
procedure TMainForm.Input;
var row: TClanResults
begin
Self.ClanData.Clear;
row.Clan_ID := 1;
row.Players := JclStringList.Add(['John', 'James', 'Jenny']).Join(' and ');
row.GP[1] := 2;
row.GP[1] := 5;
row.GP[1] := 7;
Self.ClanData.Add(row);
row.Clan_ID := 2;
row.Players := JclStringList.Add(['Mary', 'Mark', 'Marge']).Join(' and ');
row.GP[1] := 3;
row.GP[1] := 6;
row.GP[1] := 2;
Self.ClanData.Add(row);
...
end;
procedure SortOnTotal(const Table: TClansTable);
begin
Table.Sort(
TComparer<TClanResults>.Construct(
function(const Left, Right: TClanResults): Integer
begin Result := - (Left.Total - Right.Total) end
// negating since we need reversed order: large to little
)
);
end;
Now finally we need to know how to show that table on the screen. I would use typical TStringGrid as the most simplistic widget. I suggest you to look some advanced string grid from JediVCL or something from Torry.net so you would be able to specify columns styles. It is obvious that integers should be right-aligned on the screen and averages should be comma-aligned. However stock TStringGrid does not have kind of GetCellStyle event, so you would need some advanced grid derivative to add it. It is left as your home-task.
http://docwiki.embarcadero.com/RADStudio/XE5/en/String_Grids
http://docwiki.embarcadero.com/Libraries/XE5/en/Vcl.Grids.TStringGrid_Properties
Delphi TStringGrid Flicker - remains as your homework too.
.
procedure TMainForm.DumpTableToGrid(const Data: TClansTable; const grid: TStringGrid);
const TableFields = 8;
var row: integer;
ss: array of string;
res: TClanResults;
procedure DumpTheRow; var col: integer;
begin
for col := 0 to TableFields - 1 do begin
grid.Cells[ col, row ] := ss[ col ];
end;
begin
grid.Options := [ goFixedVertLine, goVertLine, goHorzLine, goColSizing, goColMoving, goThumbTracking ];
grid.ColCount := TableFields;
SetLength( ss, TableFields );
grid.RowCount := 1 + Data.Count;
grid.FixedRows := 1;
grid.FixedColumns := 1;
row := 0; // headers
ss[0] := ''; // number in the row, self-evident
ss[1] := 'Players';
ss[2] := 'GP 1';
....
ss[7] := 'Clan ID';
DumpTheRow;
for res in Data do begin // we assume Data already sorted before calling this
Inc(row);
ss[0] := IntToStr( row );
ss[1] := res.Players;
ss[2] := IntToStr( res.GP[1] );
...
ss[6] := FloatToStrF( res.AVG, ffFixed, 4, 2);
ss[7] := IntToStr( res.Clan_ID );
DumpTheRow;
end;
end;
Now, it is unclear what you mean by those labels. I can guess, that you want to show there ranks according to both your two clans combined positions. The externals labels are a bad idea for few reasons.
FindComponent is not too fast. Okay, you may find them once, cache in array of TLabel and be done. But why bother with extra workarounds?
user may resize the window, making it taller or shorter. Now there are 3 labels visible, in a minute there would be 30 labels visible, in a minute there will be 10 labels... How would you re-generate them in runtime ? So there would be enough of those always and in proper positions ? Actually just put them into the grid itself.
VCL sucks at form scaling. Now that Winodws 8.1 is out the fonts resolution might be different on different displays. There would be usually 96DPI on you main display, but as you would drag the window onto your secondary display there would be 120DPI, and on your mate's laptop (examples: Lenovo ThinkPad Yoga Pro and Lenovo IdeaPad Yoga 2) there might be like 200DPI or Retina-grade 300DPI. Still you would have to control your labels so their text would be shown exactly to the right of grid rows text, no matter what value would be rows of each height and each font.
So, i think they should be INSIDE the row. If you want to highlight them - use bold font, or coloured, or large, or whatever inside the grid.
TRanks = record min, max: word; end;
TClanResults = record
...
RanksCombined: TRanks;
...
end;
You correctly shown that some clans might have the same results and share the rank.
Before continuing you, as a JS user, have to notice a basis difference between record and class datatypes. record is operated by value while class is operated by reference. That means for class instances and variables you have to manually allocate memory for new elements and to dispose it for no longer used ones. Since class variable is a reference to some anonymous class instance(data). Hence the different containers of class-type elements can point to the single real element(data, instance), providing for easy data changing and cheaper sorting. Then for record instances (and record variable IS record data) you don't care about memory allocation and life times, yet would have copying data between different record instances, and if you change the one instance, to apply it to other containers you would have to copy it back. This difference is very visible in for element in container loops, whether we can change element.field or not.
http://docwiki.embarcadero.com/Libraries/XE2/en/System.Generics.Collections.TObjectList.Create
So let us have few more data structures for sorting and calculating. For example
TAvgAndRanks = class
avg: single; rank: TRanks;
table: TClansTable; idx: integer;
end;
We'll have then modification for the data dumper:
procedure TMainForm.DumpTableToGrid(const Data: TClansTable; const grid: TStringGrid);
const TableFields = 9;
...
row := 0; // headers
....
ss[7] := 'Clan ID';
ss[8] := 'Rank';
DumpTheRow;
...
ss[7] := IntToStr( res.Clan_ID );
with res.RanksCombined do
if min = max
then ss[9] := IntToStr(min)
else ss[9] := IntToStr(min) + ' - ' + IntToStr(max);
DumpTheRow;
Another approach would be to keep ranks externally using something like
TClanPtr = record table: TClansTable; idx: integer; end;
TClanSortData = record avg: single; rank: TRanks; end;
TClanRanksCombined = TDictionary<TClanPtr, TClanSortData>;
This approach is more extensible (allows in different window "attach" different extended data to the clans), but would require much more boilerplate. If you liek it more, your homework would be to implement it.
procedure MakeRanks(const clans: array of TClansTable);
var tab: TClansTable; idx: integer;
total: TObjectList<TAvgAndRanks>;
ar : TAvgAndRanks;
res: TClanResults;
// for spanning ranks with same avg
r_curr, r_min: word;
r_span, r_idx: integer;
r_avg: single;
r_chg: boolean;
begin
total := TObjectList<TAvgAndRanks>.Create( True ); // auto-free by container
try
for tab in clans do
for idx := 0 to tab.Count - 1 do begin
res := tab[ idx ];
ar := TAvgAndRanks.Create; // but creation is still manual
ar.table := tab;
ar.idx := idx;
ar.avg := res.AVG;
total.Add(ar);
end;
if total.Count <= 0 then Abort;
if total.Count = 1 then begin
ar := total[0];
res := ar.table[ ar.idx ];
res.RanksCombined.min := 1;
res.RanksCombined.max := 1;
ar.table[ ar.idx ] := res; // copying back updated data
Exit; // from procedure - nothing to do
end;
total.Sort(
TComparer<TAvgAndRanks>.Construct(
function(const Left, Right: TAvgAndRanks): Integer
begin Result := - (Left.avg - Right.avg) end
// negating since we need reversed order: large to little
)
);
(***** calculating ranks with spans ****)
r_curr := 1;
r_min := 1;
r_span := 0;
r_idx := 0;
r_avg := total[0].avg;
for idx := 1 to total.Count - 1 do begin
ar := total[ idx ];
inc(r_curr);
if r_avg = ar.avg then inc(r_span);
if (r_avg <> ar.avg) or (idx = total.Count - 1) then begin
for r_idx := r_idx to r_idx + r_span do begin
with total[ r_idx ] do begin // class == reference, can update directly
rank.min := r_min;
rank.max := r_min + r_span;
end;
end;
Assert( (r_curr = r_min + r_span + 1) or ( r_avg = ar.avg ) );
r_min := r_curr;
r_span := 0;
r_idx := idx;
r_avg := ar.avg;
end;
end;
(*** saving calculated ranks ***)
for ar in total do begin
res := ar.table[ ar.idx ];
res.RanksCombined := ar.ranks;
ar.table[ ar.idx ] := res; // copying back updated data
end;
finally
Total.Destroy;
end;
end;

Subtract until reach desired value

Good morning all.
I'm currently trying to figure out something that i'm confident is simple enough but is proving to be a task and a half to actually work out.
I'm working on a project that's designed to minimize drive usage by relocating various files elsewhere. I've got an array (0..12) of int64 values that contains the file sizes of the files i might potentially want to move. The array is ordered in a way that's predicted largest file size down to predicted smallest file size. I've also got the names of these files stored in a different array (known as WoWData, also [0..12]). I've then got an "installation size", and a "desired size".
My task is to calculate which files i need to move in order to bring the "installation size" down to the "desired size" by going through the array of file sizes, and taking the value away from the Installation size until i reach <= desired size.
Here's some sample code (Delphi/Firemonkey) i've been trying to work with. It's confusing me trying to figure out how to go about such a task and so there'll no doubt be a lot of issues with it;
Global Vars;
_WoWDataFileSize : Array [0..12] of Int64;
// "TBWoWDir" is a TTrackBar (Firemonkey)
var
TotalSize, ReqSize, DiffSize, CurDiff : Int64;
i : Integer;
begin
// Set up initial values to work with
ReqSize := Round(TBWoWDir.Value); // Requested Size
TotalSize := Round(TBWoWDir.Max); // Actual installation size
CurDiff := 0; // Assume as "Current Difference in size"
// Calculate difference between install and requested size
DiffSize := TotalSize - ReqSize; // This calculates correctly
// The below is what i'm struggling with
repeat
for i := Low(_WoWDataFileSize) to High(_WoWDataFileSize) do
begin
CurDiff := ReqSize - _WoWDataFileSize[i];
end;
until CurDiff <= ReqSize;
end;
I did try using just a repeat .. until loop without the for loop, but again, i'm getting far too confused while trying to figure it out.
Let me provide an example. Let's assume that _WoWDataFileSize[0] is 200, and _WoWDataFileSize[1] through to _WoWDataFileSize[12] are the same value as their array index (e.g. _WoWDataFileSize[6] = 6, _WoWDataFileSize[8] = 8, etc).
If i wanted to calculate the value of 150 (which would be 200 - 12 - 11 - 10 - 9 - 8, or Array[0] - Array[12] - Array[11] - Array[10] - Array[9] - Array[8] according to the array), and get a list of files i need to move to meet this requirement from the WoWData array, how would i write the routine?
150 could be replaced by any number as i'm working towards a dynamic user-requested size specified by TBWoWDir.Value.
I'm thinking i might need to do a While loop and use i := i+1 setup. Realistically, i could go through and hardcode it so it takes away one value in the array at a time and check each time to see if i'm <= desired value-- it'd be 2-3 lines for each item (so a total of 24-36 lines), but this is both messy to maintain and not optimal. I'm interested to see how it would be done in a loop. I typically don't have trouble with loops, but this is hardly a standard one for me.
curdiff:= 0;
i:= Low(_WoWDataFileSize) - 1;
while (curdiff <= reqsize) and (i < High(_WoWDataFileSize)) do
begin
inc (i);
curdiff:= curdiff + _WoWDataFileSize[i];
end;
At the end of the loop, either you've attained the required reduction in size or you've iterated through the entire array.
It is IMHO just two line missing in your code :o)
CurDiff := ReqSize;
// repeat
for i := Low(_WoWDataFileSize) to High(_WoWDataFileSize) do
begin
CurDiff := CurrDiff - _WoWDataFileSize[i];
if CurDiff <= ReqSize then break; // breaks the for..to loop
end;
// until CurDiff <= ReqSize;
EDIT No need for the repeat...until loop
But IMHO it is not very useful only to count the sizes without storing which files match.
So using a CustomObject and Lists (thanx to Generics) it will be very simple:
type
TFileObject = class
private
FName : string;
FSize : Int64;
public
constructor Create( AName : string; ASize : Int64 );
published
property Name : string read FName;
property Size : Int64 read FSize;
end;
procedure MoveFileObject(AMaxSize : Int64; ASrcList, ATarList : TList<TFileObject> );
var
LItem : TFileObject;
LSize : Int64;
begin
LSize := 0;
for LItem in ASrcList do
begin
if LSize + LItem.Size <= AMaxSize then
begin
LSize := LSize + LItem.Size;
ATarList.Add( LItem );
end;
end;
end;
Thanks to everyone for their answers, i figured out where i was going wrong. When i was calculating in my initial question, i'd forgotten to account for my division on the values (for the sake of showing MB instead of Bytes as TBWoWDir.Value was livebound to a TLabel.text, but the actual size was being divided before assigning TBWoWDir.Max).
Thanks to a few tweaks from an answer by No'am Newman, i managed to figure this out for myself. Here's how i got the result i was after (or much closer to it);
Global Vars;
_WoWDataFileSize : Array [0..12] of Int64;
Global Const;
_WoWData : Array [0..12] of String;
// "TBWoWDir" is a TTrackBar (Firemonkey)
[...]
var
ReqSize : int64;
DiffSize, CurDiff : Int64;
i, ii : Integer;
FilesTot : Integer;
FILESMSG : String;
begin
// Set up initial values to work with
ReqSize := Round(TBWoWDir.Value) * 1024 * 1024; // Requested Size - Multiplied from formatting
TotalSize := Round(TBWoWDir.Max) * 1024 * 1024; // Actual installation size - Multiplied from formatting
DiffSize := TotalSize - ReqSize; // Calculate Difference
CurDiff := 0; // Reset Current Difference
i := -1; // Reset i
repeat
inc (i); // Increment i
CurDiff := CurDiff + _WoWDataFileSize[i]; // Add current array item file size to CurDiff
until (CurDiff >= (DiffSize)) or (i >= 12); // Repeat until we reach ideal size or the end of the array
// Calculate which array item we stopped at
for ii := 0 to i do // use i from previous loop as the max
begin
FILESMSG := FILESMSG + 'File: ' + WoWData[ii] +
' | Size: ' + IntToStr(_WoWDataFileSize[ii])+' '#13#10;
FilesTot := FilesTot + _WoWDataFileSize[ii];
end;
// Show Message providing details
ShowMessage('CurDiff:' + IntToStr(CurDiff div 1024 div 1024) +
' | DiffSize: ' + IntToStr(DiffSize div 1024 div 1024) +
' | Array i: ' +
IntToStr(i) +#13#10+
'Difference between CurDiff and DiffSize: '+ IntToStr(((DiffSize div 1024 div 1024) - (CurDiff div 1024 div 1024)))+#13#10#13#10+
'File Details' +#13#10#13#10+
FilesMsg +#13#10#13#10+
'Total Size: ' + IntToStr(FilesTot));
end;
The code is there to tell me which files i need to copy (so modifying it to copy the files now isn't too difficult), and the whole ShowMessage is there for self-proof (as i use ShowMessage during development when i need to verify a value is returning correctly, as i'm sure many others do as well).

Convert Int64 to Base30 and Back

For a registration code I want to convert an Int64 to base30 (30 so that only uppercase characters and excluding 0,O,I,1,etc.) and back.
This is not too difficult using functions like:
const
Base = 30;
Base30CharSet = '23456789ABCDEFGHJKLMNPRSTVWXYZ';
function ConvertIntToBase30(ANumber: Int64): string;
begin
if(ANumber = 0) then
Result := Copy(Base30CharSet, 1, 1)
else begin
Result := '';
while(ANumber <> 0) do begin
Result := Copy(Base30CharSet, (ANumber mod Base)+1, 1) + Result;
ANumber := ANumber div Base;
end;
end;
end;
function ConvertBase30ToInt(ANumber: string): Int64;
var
i: integer;
begin
Result := 0;
for i := 1 to Length(ANumber) do begin
Result := Result + (Pos(ANumber[i], Base30CharSet)-1);
if(i < Length(ANumber)) then
Result := Result * Base;
end;
end;
The snag is that I am interested in the Int64's bits, so I could be dealing with a number like $FFFFFFFFFFFFFFFF = -1.
To work around this I thought I would store and remove the sign (abs()) and include the sign as an extra character appended to the base30 result. The problem the occurs at the lower limit of Int64 as calling abs(-9223372036854775808) results in an overflow.
Does anyone have a solution or better algorithm to solve this problem?
The way to deal with it is having a character to indicate it is a negative number so that you can decode back. For negative number, just flip the bit from 1 to 0 and remove the sign bit before encoding and when decode, do a flip back and add the sign bit. Below is working codes
function InvertIntOff(const ANumberL, ANumberH: Integer): Int64;
asm
XOR EAX,$FFFFFFFF
XOR EDX,$FFFFFFFF
end;
function InvertIntOn(const ANumberL, ANumberH: Integer): Int64;
asm
XOR EAX,$FFFFFFFF
XOR EDX,$FFFFFFFF
OR EDX,$80000000
end;
function ConvertIntToBase(ANumber: Int64): string;
const
CBaseMap: array[0..31] of Char = (
'2','3','4','5','6','7','8','9', //0-7
'A','B','C','D','E','F','G','H', //8-15
'J','K','L','M','N', //16-20
'P','Q','R','S','T','U','V','X','W','Y','Z'); //21-31
var
I: Integer;
begin
SetLength(Result, 15);
I := 0;
if ANumber < 0 then
begin
Inc(I);
Result[I] := '1';
ANumber := InvertIntOff(ANumber and $FFFFFFFF, (ANumber and $FFFFFFFF00000000) shr 32);
end;
while ANumber <> 0 do
begin
Inc(I);
Result[I] := CBaseMap[ANumber and $1F];
ANumber := ANumber shr 5;
end;
SetLength(Result, I);
end;
function ConvertBaseToInt(const ABase: string): Int64;
var
I, Index: Integer;
N: Int64;
begin
Result := 0;
if Length(ABase) > 0 then
begin
if ABase[1] = '1' then
Index := 2
else
Index := 1;
for I := Index to Length(ABase) do
begin
case ABase[I] of
'2'..'9':
N := Ord(ABase[I]) - Ord('2');
'A'..'H':
N := Ord(ABase[I]) - Ord('A') + 8;
'J'..'N':
N := Ord(ABase[I]) - Ord('J') + 16;
'P'..'Z':
N := Ord(ABase[I]) - Ord('P') + 21;
else
raise Exception.Create('error');
end;
if I > Index then
Result := Result or (N shl ((I - Index) * 5))
else
Result := N;
end;
if ABase[1] = '1' then
Result := InvertIntOn(Result and $FFFFFFFF, (Result and $FFFFFFFF00000000) shr 32);
end;
end;
procedure TestBase32;
var
S: string;
begin
S := ConvertIntToBase(-1);
ShowMessage(S + ' / ' + IntToStr(ConvertBaseToInt(S)) + ' ? -1');
S := ConvertIntToBase(-31);
ShowMessage(S + ' / ' + IntToStr(ConvertBaseToInt(S)) + ' ? -31');
S := ConvertIntToBase(1);
ShowMessage(S + ' / ' + IntToStr(ConvertBaseToInt(S)) + ' ? 1');
S := ConvertIntToBase(123456789);
ShowMessage(S + ' / ' + IntToStr(ConvertBaseToInt(S)) + ' ? 123456789');
S := ConvertIntToBase(-123456789);
ShowMessage(S + ' / ' + IntToStr(ConvertBaseToInt(S)) + ' ? -123456789');
end;
I think you are almost there by considering abs()...
But rather than using abs() why not simply ignore the sign for processing the value of the Int64 itself ? As far as I can tell, you are in fact already doing this so only one minor addition is needed to the encoding routine:
if aNumber < 0 then
// negative
else
// positive;
The only problem then is the LOSS of sign information in the resulting Base30 string. So treat that as a separate problem to be solved using the new information gained from the aNumber < 0 test...
I see you have excluded all chars that could be confused for 0 or 1 but have also excluded 0 and 1 themselves. You could therefore use 0 and 1 to indicate positive or negative (or vice versa).
Depending on the purpose of these routines, the placement of the 0/1 in the result could be entirely arbitrary (if you wished to obfuscate things and make the placement of the 0/1 random rather than a consistent lead/trail character).
When encoding simply drop a sign indicator into the result string at random, and when decoding handle the 0/1 character whenever as the sign marker it is encountered, but skipped for the purposes of decoding the value.
Of course, if obfuscation is not an issue then simply consistently pre or post fix the sign indicator.
You could even simply choose to use '1' to indicate negative and the LACK of a '1' to indicate/assume positive (this would simplify the zero value case a little I think)
The easy answer is to turn range checking off, even just for the method that you're calling abs in.
If you don't care about an extra char or two you could split the int64 into words or dwords and string those together. I would be more tempted to go to base32 and use bit shifts for speed and ease of use. Then your encoding becomes
Base32CharSet[(ANumber shr 5) % 32]
and a similar pos() based approach for the decode.

Improve speed on Crc16 calculation

I need to calculate Crc16 checksums with a $1021 polynom over large files, below is my current implementation but it's rather slow on large files (eg a 90 MB file takes about 9 seconds).
So my question is how to improve my current implementation (to make it faster), I have googled and looked at some samples implementing a table lookup but my problem is that I don't understand how to modify them to include the polynom (probably my math is failing).
{ based on http://miscel.dk/MiscEl/CRCcalculations.html }
function Crc16(const Buffer: PByte; const BufSize: Int64;
const Polynom: WORD=$1021; const Seed: WORD=0): Word;
var
i,j: Integer;
begin
Result := Seed;
for i:=0 to BufSize-1 do
begin
Result := Result xor (Buffer[i] shl 8);
for j:=0 to 7 do begin
if (Result and $8000) <> 0 then
Result := (Result shl 1) xor Polynom
else Result := Result shl 1;
end;
end;
Result := Result and $FFFF;
end;
If you want this to be fast, you need to implement a table-lookup CRC algorithm.
See chapter 10 of A PAINLESS GUIDE TO CRC ERROR DETECTION ALGORITHMS INDEX V3.00 (9/24/96)
Look for CRC routines from jclMath.pas unit of Jedi Code Library. It uses CRC lookup tables.
http://jcl.svn.sourceforge.net/viewvc/jcl/trunk/jcl/source/common/
Your Result variable is a Word, which means there are 64k possible values it could have upon entry to the inner loop. Calculate the 64k possible results that the loop could generate and store them in an array. Then, instead of looping eight times for each byte of the input buffer, simply look up the next value of the checksum in the array. Something like this:
function Crc16(const Buffer: PByte; const BufSize: Int64;
const Polynom: Word = $1021; const Seed: Word = 0): Word;
{$J+}
const
Results: array of Word = nil;
OldPolynom: Word = 0;
{$J-}
var
i, j: Integer;
begin
if (Polynom <> OldPolynom) or not Assigned(Results) then begin
SetLength(Results, 65535);
for i := 0 to Pred(Length(Results)) do begin
Results[i] := i;
for j := 0 to 7 do
if (Results[i] and $8000) <> 0 then
Results[i] := (Results[i] shl 1) xor Polynom
else
Results[i] := Results[i] shl 1;
end;
OldPolynom := Polynom;
end;
Result := Seed;
for i := 0 to Pred(BufSize) do
Result := Results[Result xor (Buffer[i] shl 8)];
end;
That code recalculates the lookup table any time Polynom changes. If that parameter varies among a set of values, then consider caching the lookup tables you generate for them so you don't waste time calculating the same tables repeatedly.
If Polynom will always be $1021, then don't even bother having a parameter for it. Calculate all 64k values in advance and hard-code them in a big array, so your entire function is reduced to just the last three lines of my function above.
Old thread, i know. Here is my implementation (just one loop):
function crc16( s : string; bSumPos : Boolean = FALSE ) : Word;
var
L, crc, sum, i, x, j : Word;
begin
Result:=0;
L:=length(s);
if( L > 0 ) then
begin
crc:=$FFFF;
sum:=length(s);
for i:=1 to L do
begin
j:=ord(s[i]);
sum:=sum+((i) * j);
x:=((crc shr 8) xor j) and $FF;
x:=x xor (x shr 4);
crc:=((crc shl 8) xor (x shl 12) xor (x shl 5) xor x) and $FFFF;
end;
Result:=crc+(Byte(bSumPos) * sum);
end;
end;
Nice thing is also that you can create an unique id with it, for example to get an unique identifier for a filename, like:
function uniqueId( s : string ) : Word;
begin
Result:=crc16( s, TRUE );
end;
Cheers,
Erwin Haantjes

Resources