Randomize array? [duplicate] - delphi

This question already has answers here:
Randomize StringList
(3 answers)
Closed 9 years ago.
In pascal, i want to randomly organize an array. Therefore the majority of the time the array should be organized differently.
Consider this array setup
const
ARRAY_ELEMENTS = 3;
SetLength(iIndex, ARRAY_ELEMENTS);
for i := Low(iIndex) to High(iIndex) do
begin
case i of
0: iIndex[i] := 0;
1: iIndex[i] := 1;
2: iIndex[i] := 2;
end;
end;
How is it possible for the iIndex[] that contains the value 0 not to always be in the first element of the array and for the iIndex[] that contains value 2 not to be always the last value of the array but generate the order of the array randomly so that the order of the array is not always the same on initialization?

This code permutes an integer array, but I'm not sure if it is optimal (probably isn't).
type
TDynIntegerArray = array of integer;
procedure PermuteArray(A: TDynIntegerArray);
var
B: TDynIntegerArray;
Z: TDynIntegerArray;
π: TDynIntegerArray;
i: Integer;
j: Integer;
k: Integer;
begin
B := Copy(A);
SetLength(Z, Length(A));
SetLength(π, Length(A));
for i := 0 to High(Z) do
Z[i] := i;
for i := 0 to High(π) do
begin
π[i] := RandomFrom(Z);
for j := 0 to High(Z) do
begin
if Z[j] = π[i] then
begin
for k := j to High(Z) - 1 do
Z[k] := Z[k+1];
SetLength(Z, length(Z) - 1);
break;
end;
end;
end;
for i := 0 to High(A) do
A[i] := B[π[i]];
end;
A much faster, but less cool approach is simply to randomly swap the items, one pair at a time:
procedure FastPermuteArray(A: TDynIntegerArray);
procedure Swap(n, m: integer);
var
tmp: integer;
begin
tmp := A[n];
A[n] := A[m];
A[m] := tmp;
end;
var
i: Integer;
begin
for i := High(A) downto 1 do
Swap(i, RandomRange(0, i));
end;

Try something like this:
uses
System.Generics.Collections;
const
ARRAY_ELEMENTS = 3;
var
iArray: array of Integer;
iIndex: TList<Integer>;
I, j: Integer;
begin
Randomize;
SetLength(iArray, ARRAY_ELEMENTS);
iIndex := TList<Integer>.Create;
try
iIndex.Count := ARRAY_ELEMENTS;
for i := 0 to Pred(ARRAY_ELEMENTS) do
iIndex[i] := i;
for i := Low(iArray) to High(iArray) do
begin
j := Random(iIndex.Count);
iArray[iIndex[j]] := i;
iIndex.Delete(j);
end;
finally
iIndex.Free;
end;
end;
If you don't have TList<T> available in your version of Delphi, you can use a plain TList instead:
uses
Classes;
const
ARRAY_ELEMENTS = 3;
var
iArray: array of Integer;
iIndex: TList;
I, j: Integer;
begin
Randomize;
SetLength(iArray, ARRAY_ELEMENTS);
iIndex := TList.Create;
try
iIndex.Count := ARRAY_ELEMENTS;
for i := 0 to Pred(ARRAY_ELEMENTS) do
iIndex[i] := Pointer(I);
for i := Low(iArray) to High(iArray) do
begin
j := Random(iIndex.Count);
iArray[Integer(iIndex[j])] := i;
iIndex.Delete(j);
end;
finally
iIndex.Free;
end;
end;

Related

Delphi changing Chars in string - missunderstood behavior - XE3

I'm doing a code on some kind of encoder/decoder, and can't figure out the strange behavior for more than 2 days now... I hope someone might understand and explain to me why is happening, whatever it is...
Here's the main code, which does the thing (I removed the form's info, buttons etc, just the core to avoid garbage)
unit Encoder;
//
interface
//
var
Enc : array [1..71] of Record
Char: Char;
Encr: string;
Enc: array [1..5] of Char;
end;
EncodeBuffer: TStringList;
implementation
{$R *.dfm}
procedure TEncrypter.Encode;
var
s, t, u, h, h2, h3, h4, h5: integer;
begin
s := EncodeBuffer.Count;
h := 0;
h2 := 1;
h3 := 2;
h4 := 3;
h5 := 4;
while h < s do
begin
t := EncodeBuffer.Strings[h].Length;
if t > 0 then
begin
for u := 0 to t-1 do
begin
EncodeBuffer.Strings[h] := EncodeBuffer.Strings[h].Replace(EncodeBuffer.Strings[h].Chars[u], EncodeChar(EncodeBuffer.Strings[h].Chars[u], 1));
end;
end;
h := h + 5;
end;
while h2 < s do
begin
t := EncodeBuffer.Strings[h2].Length;
if t > 0 then
begin
for u := 0 to t-1 do
begin
EncodeBuffer.Strings[h2] := EncodeBuffer.Strings[h2].Replace(EncodeBuffer.Strings[h2].Chars[u], EncodeChar(EncodeBuffer.Strings[h2].Chars[u], 2));
end;
end;
h2 := h2 + 5;
end;
while h3 < s do
begin
t := EncodeBuffer.Strings[h3].Length;
if t > 0 then
begin
for u := 0 to t-1 do
begin
EncodeBuffer.Strings[h3] := EncodeBuffer.Strings[h3].Replace(EncodeBuffer.Strings[h3].Chars[u], EncodeChar(EncodeBuffer.Strings[h3].Chars[u], 3));
end;
end;
h3 := h3 + 5;
end;
while h4 < s do
begin
t := EncodeBuffer.Strings[h4].Length;
if t > 0 then
begin
for u := 0 to t-1 do
begin
EncodeBuffer.Strings[h4] := EncodeBuffer.Strings[h4].Replace(EncodeBuffer.Strings[h4].Chars[u], EncodeChar(EncodeBuffer.Strings[h4].Chars[u], 4));
end;
end;
h4 := h4 + 5;
end;
while h5 < s do
begin
t := EncodeBuffer.Strings[h5].Length;
if t > 0 then
begin
for u := 0 to t-1 do
begin
EncodeBuffer.Strings[h5] := EncodeBuffer.Strings[h5].Replace(EncodeBuffer.Strings[h5].Chars[u], EncodeChar(EncodeBuffer.Strings[h5].Chars[u], 5));
end;
end;
h5 := h5 + 5;
end;
end;
procedure TEncrypter.Decode;
var
s, t, u, h, h2, h3, h4, h5: integer;
begin
s := EncodeBuffer.Count;
h := 0;
h2 := 1;
h3 := 2;
h4 := 3;
h5 := 4;
while h < s do
begin
t := EncodeBuffer.Strings[h].Length;
if t > 0 then
begin
for u := 0 to t-1 do
begin
EncodeBuffer.Strings[h] := EncodeBuffer.Strings[h].Replace(EncodeBuffer.Strings[h].Chars[u], DecodeChar(EncodeBuffer.Strings[h].Chars[u], 1));
end;
end;
h := h + 5;
end;
while h2 < s do
begin
t := EncodeBuffer.Strings[h2].Length;
if t > 0 then
begin
for u := 0 to t-1 do
begin
EncodeBuffer.Strings[h2] := EncodeBuffer.Strings[h2].Replace(EncodeBuffer.Strings[h2].Chars[u], DecodeChar(EncodeBuffer.Strings[h2].Chars[u], 2));
end;
end;
h2 := h2 + 5;
end;
while h3 < s do
begin
t := EncodeBuffer.Strings[h3].Length;
if t > 0 then
begin
for u := 0 to t-1 do
begin
EncodeBuffer.Strings[h3] := EncodeBuffer.Strings[h3].Replace(EncodeBuffer.Strings[h3].Chars[u], DecodeChar(EncodeBuffer.Strings[h3].Chars[u], 3));
end;
end;
h3 := h3 + 5;
end;
while h4 < s do
begin
t := EncodeBuffer.Strings[h4].Length;
if t > 0 then
begin
for u := 0 to t-1 do
begin
EncodeBuffer.Strings[h4] := EncodeBuffer.Strings[h4].Replace(EncodeBuffer.Strings[h4].Chars[u], DecodeChar(EncodeBuffer.Strings[h4].Chars[u], 4));
end;
end;
h4 := h4 + 5;
end;
while h5 < s do
begin
t := EncodeBuffer.Strings[h5].Length;
if t > 0 then
begin
for u := 0 to t-1 do
begin
EncodeBuffer.Strings[h5] := EncodeBuffer.Strings[h5].Replace(EncodeBuffer.Strings[h5].Chars[u], DecodeChar(EncodeBuffer.Strings[h5].Chars[u], 5));
end;
end;
h5 := h5 + 5;
end;
end;
function TEncrypter.EncodeChar(Sign: Char; Encoder: integer) : Char;
var
t: integer;
begin
for t := 1 to 71 do
begin
if Sign = enc[t].Char then
begin
Result := enc[t].Enc[Encoder];
Break;
end
else
Result := Sign;
end;
end;
function TEncrypter.DecodeChar(Sign: Char; Encoder: integer) : Char;
var
t: integer;
begin
for t := 1 to 71 do
begin
if Sign = enc[t].Enc[Encoder] then
begin
Result := enc[t].Char;
Break;
end
else
Result := Sign;
end;
end;
I have an Enc array created on FormCreate event, which fills Enc[1 to 71].Char and Enc[1 to 71].Enc[1 to 5] with random char order;
The code is made (or at least should be) so that it uses different encode list from array for each 5th line (line 1 enc[x].enc[1], line 2 enc[x].enc[2], and then line 5 enc[x].enc[5], and line 6 back to enc[x].enc[1] and so on...)
If I encode 5 lines from Memo1, which are:
Memo1
Memo2
Memo3
Memo4
Memo5
I get some random words with 5 chars each, however when decoding it back, I get returned
Memo1
Memo2
memo3
Memo4
Memo5
(notice the lower m letter in 3rd line);
If I then encode this again, I get the exact same encoded stringlist as in the first case, only that here, the 3rd line's 3rd char! (wtf?) is changed with the same as the firstone is.
so, for Memo3 I get q7M0e, and for memo3 I get q7q0e, which makes no sense to me, as the position of a char should be the same, as far as I understand by the code.
Is there anything I'm missing here, noticable in the code above??
Comment if there's a need for me to paste the complete form's (unit's) code and exe example, I'll give it on a web and link to that...
Edit:
Here's the "key", by which I'm encoding/decoding: http://txt.do/128b
There are several problems with your approach.
One problem is with your use of String.Replace(), which replaces ALL occurrences of one Char with another Char. Once you replace a given Char, you can potentially replace that same index with a different value later on in your loops, thus trashing your data while you are looping.
Another problem is your decoding logic. You are allowing each un-encoded Char to be encoded with one of 5 different Chars. If those encoded Char values are duplicated at all across your Enc[1..71].Enc array for the same value of Encoder, you will not be able to know which Enc[1..71].Char to use for decoding. It is not enough that your arrays are simply random, but they also need to be unique for the same value of Encoder.
Also, your loops are redundant and overly complicated. They can be greatly simplified.
Try something more like this instead:
function TEncrypter.EncodeChar(Sign: Char; Encoder: integer) : Char;
var
t: integer;
begin
for t := 1 to 71 do
begin
if Sign = Enc[t].Char then
begin
Result := Enc[t].Enc[Encoder];
Exit;
end;
end;
Result := Sign;
end;
function TEncrypter.DecodeChar(Sign: Char; Encoder: integer) : Char;
var
t: integer;
begin
for t := 1 to 71 do
begin
if Sign = Enc[t].Enc[Encoder] then
begin
Result := Enc[t].Char;
Exit;
end;
end;
Result := Sign;
end;
procedure TEncrypter.Encode;
var
t, u, h: integer;
s: String;
begin
for h := 0 to EncodeBuffer.Count-1 do
begin
s := EncodeBuffer.Strings[h];
t := Length(s);
if t > 0 then
begin
for u := 0 to t-1 do
begin
s[u+1] := EncodeChar(s[u+1], (h mod 5) + 1);
end;
EncodeBuffer.Strings[h] := s;
end;
end;
end;
procedure TEncrypter.Decode;
var
t, u, h: integer;
s: String;
begin
for h := 0 to EncodeBuffer.Count-1 do
begin
s := EncodeBuffer.Strings[h];
t := Length(s);
if t > 0 then
begin
for u := 0 to t-1 do
begin
s[u+1] := DecodeChar(s[u+1], (h mod 5) + 1);
end;
EncodeBuffer.Strings[h] := s;
end;
end;
end;
// FormCreate
var
I, J, K, L: Integer;
Temp: Array[1..71] of Char;
NumInTemp: Integer;
begin
...
// initialize Enc[].Char as needed...
for I := 1 to 71 do
begin
Enc[I].Char := ...;
end;
// uniquely initialize each Enc[].Enc array for one value of Encoder...
for I := 1 to 5 do
begin
for J := 1 to 71 do
Temp[J] := ...; // must be unique for this iteration of I...
NumInTemp := 71;
// randomly assign Temp array to Enc[I].Enc array
for J := 1 to 71 do
begin
K := 1 + Random(NumInTemp);
Enc[J].Enc[I] := Temp[K];
for L := K+1 to NumInTemp do
Temp[L-1] := Temp[L];
Dec(NumInTemp);
end;
end;
...
end;
If you then expand your arrays to allow all printable ASCII characters, not just 71 of them, then the code gets a little simpler:
var
Enc : array [32..126] of Record
Char: Char;
Encr: string;
Enc: array [1..5] of Char;
end;
EncodeBuffer: TStringList;
function TEncrypter.EncodeChar(Sign: Char; Encoder: integer) : Char;
var
t: integer;
begin
if (Sign >= #32) and (Sign <= #126) then
Result := Enc[Ord(Sign)].Enc[Encoder]
else
Result := Sign;
end;
function TEncrypter.DecodeChar(Sign: Char; Encoder: integer) : Char;
var
t: integer;
begin
for t := Low(Enc) to High(Enc) do
begin
if Sign = Enc[t].Enc[Encoder] then
begin
Result := Enc[t].Char;
Exit;
end;
end;
Result := Sign;
end;
procedure TEncrypter.Encode;
var
u, h: integer;
s: String;
begin
for h := 0 to EncodeBuffer.Count-1 do
begin
s := EncodeBuffer.Strings[h];
for u := Low(s) to High(s) do
s[u] := EncodeChar(s[u], (h mod 5) + 1);
EncodeBuffer.Strings[h] := s;
end;
end;
procedure TEncrypter.Decode;
var
u, h: integer;
s: String;
begin
for h := 0 to EncodeBuffer.Count-1 do
begin
s := EncodeBuffer.Strings[h];
for u := Low(s) to High(s) do
s[u] := DecodeChar(s[u], (h mod 5) + 1);
EncodeBuffer.Strings[h] := s;
end;
end;
// FormCreate
var
I, J, K, L: Integer;
Temp: Array[32..126] of Char;
NumInTemp: Integer;
begin
...
for I := Low(Enc) to High(Enc) do
Enc[I].Char := Char(I);
for I := 1 to 5 do
begin
for J := Low(Temp) to High(Temp) do
Temp[J] := Char(J);
NumInTemp := Length(Temp);
for J := Low(Enc) to High(Enc) do
begin
K := Low(Temp) + Random(NumInTemp);
Enc[J].Enc[I] := Temp[K];
for L := K+1 to (Low(Temp)+NumInTemp) do
Temp[L-1] := Temp[L];
Dec(NumInTemp);
end;
end;
end;
And if you set up a separate decoder table instead of using Enc[].Enc, you can simplify TEncrypter.DecodeChar() to a similar lookup that TEncrypter.EncodeChar() uses, without having to use a loop at all. I will leave that as an exercise for you.
Let's say I start with a string "abc".
I want to replace all "a" with "c", replace all "b" with "Q", and replace all "c" with "7".
Perhaps I would write:
S := 'abc';
S := S . Replace ( 'a', 'c' );
S := S . Replace ( 'b', 'Q' );
S := S . Replace ( 'c', '7' );
The result is '7Q7'. Oops! Why wasn't "a" replaced with "c"?
Well, it was.
After the first call to Replace, S was 'cbc'.
After the second call, S was 'cQc'.
The last call replaced both 'c's with '7'.
I imagine you are doing the same sort of thing here.
I couldn't give you the specific results of your code character for character unless we saw how you were populating the Enc structure.

Delphi - Get line index and data index from given data

So I have list of data that may contain different charters:
1dAAbt54
agFlE9dA
1295RTdd
First line data contains: 1d, AA, bt, 54. All I need is function that gives me the data of given index. Example: data of index 6 is Fl (Line - 2, Index in line is 2). Every line lenght is 8 and data length is 2;
How can I make such function in Delphi?
The result function should be something like this:
procedure (DataList: TStringList; DataIndex: Integer; var LineIndex: Integer; var PosInLine: Integer);
begin
//do the algorithm
end;
Sorry for my bad english...
Answer to the first version of your question
The following is an answer to the first version of your question (before you edited it):
function GetIndexOfInteger(DataList: TStringList; DataIndex: Integer;
out LineIndex: Integer; out PosInLine: Integer): boolean;
var
x, y: Integer;
InNum: boolean;
NumStart: integer;
ValIndex: integer;
begin
result := false;
for y := 0 to DataList.Count - 1 do
begin
InNum := false;
ValIndex := 0;
for x := 1 to Length(DataList[y]) do
begin
if (DataList[y][x] <> chr(32)) and not InNum then
begin
NumStart := x;
InNum := true;
inc(ValIndex);
end;
if InNum and ((DataList[y][x] = chr(32)) or
(x = Length(DataList[y]))) then
begin
if StrToInt(Copy(DataList[y], NumStart, x - NumStart +
IfThen(x = Length(DataList[y]), 1))) = DataIndex then
begin
LineIndex := y + 1;
PosInLine := ValIndex;
result := true; // Roberts is on D7.
Exit; //
end;
InNum := false;
Continue;
end;
end;
end;
end;
Try it:
procedure TForm4.FormCreate(Sender: TObject);
var
SR: TStringList;
line, col: integer;
begin
SR := TStringList.Create;
SR.Add('1 2 3');
SR.Add('4 5 6');
SR.Add('7 8 9');
SR.Add('10 11 12 13');
if GetIndexOfInteger(SR, 13, line, col) then
ShowMessage(Format('%d, %d', [line,col]));
end;
Answer to the second version of your question
(And this is so easy you could've done it yourself! :)
function GetIndexOfItemInListOfPairs(DataList: TStringList; Data: String; out LineIndex: Integer; out PosInLine: Integer): boolean;
var
x, y: Integer;
begin
result := false;
for y := 0 to DataList.Count - 1 do
for x := 0 to Length(DataList[y]) div 2 - 1 do
if Copy(DataList[y], 2*x + 1, 2) = Data then
begin
LineIndex := y + 1;
PosInLine := x + 1;
Exit(true);
end;
end;
procedure TForm4.FormCreate(Sender: TObject);
var
SR: TStringList;
line,col:integer;
begin
SR := TStringList.Create;
SR.Add('1dAAbt54');
SR.Add('agFlE9dA');
SR.Add('1295RTdd');
if GetIndexOfItemInListOfPairs(SR, 'RT', line, col) then
ShowMessage(Format('%d, %d', [line,col]));
end;
Answer to the third version of your question
procedure TForm4.FormCreate(Sender: TObject);
var
RowIndex, ColIndex: Word;
begin
DivMod(6 {index} - 1, 4 {items per row}, RowIndex, ColIndex);
inc(RowIndex);
inc(ColIndex);
ShowMessageFmt('%d, %d', [RowIndex, ColIndex]);
end;
How about ...
procedure Search( DataList: TStringList; DataIndex: Integer; var LineIndex: Integer; var PosInLine: Integer);
var
j, LineLen: integer;
Line: string;
begin
LineIndex := 0;
PosInLine := 0;
for j := 0 to DataList.Count - 1 do
begin
Line := DataList[j];
Inc( LineIndex);
LineLen := Length( Line) div 2;
if DataIndex >= LineLen then
begin
Dec( DataIndex, LineLen);
continue
end;
PosInLine := LineLen;
break
end;
if PosInLine = 0 then // No find
LineIndex := 0
end;
Oops. The question has been changed completely. Here is the procedure for the question v 1.0 :)
procedure FindIndex(Data:TStringList; Index:integer;var LineIndex,PosInLine:Integer);
var i:integer;
CurrentStr:String;
StrToFind:String;
begin
LineIndex:=0;
PosInLine:=0;
StrToFind:=intToStr(Index)+' ';
for i:=0 to Data.Count-1 do
begin
CurrentStr:=' '+Data.Strings[i]+' ';
IF POS(' '+StrToFind,CurrentStr)>0 then
begin
LineIndex:=i+1;
//now we need to find PosInLine
PosInLine:=1;
repeat
CurrentStr:=Trim(CurrentStr)+' ';
IF Pos(StrToFind,CurrentStr)=1 then exit; //we found it
CurrentStr:=copy(CurrentStr,POS(' ',CurrentStr),length(CurrentStr));
inc(PosInLine);
until (CurrentStr='');
exit;
end;
end;
end;
Tested with this code
var T:TStringList;
Li,Pi:integer;
i:integer;
begin
T:=TStringList.Create();
T.Add('1 2 3');
T.Add(' 4 5 6');
T.Add('7 8 9');
T.Add('10 11 12 ');
for i:=0 to 13 do
begin
FindIndex(T,i,Li,Pi);
Memo1.Lines.Add(IntToStr(i)+':'+IntToStr(Li)+'-'+IntToStr(Pi))
end;
end;

How to sort the items of a TcxImageComboBox by Description?

I've found a good component to implement a Caption<->Value List for ComboBox:
Is there a ComboBox that has Items like a TcxRadioGroup?
The only problem is: It has a Sorted property, but that doesn't work.
So, how do I sort the Items of a TcxImageComboBox?
Quick and dirty method, should work fine for most cases:
function CompareItems(AFirst: TcxImageComboBoxItem; ASecond: TcxImageComboBoxItem): Integer;
begin
Result := AnsiCompareText(AFirst.Description, ASecond.Description);
end;
procedure SortCxComboBoxItems(AItems: TcxImageComboBoxItems);
var
I : Integer;
J : Integer;
PMin : Integer;
begin
AItems.BeginUpdate;
try
// Selection Sort (http://en.wikipedia.org/wiki/Selection_sort)
for I := 0 to AItems.Count - 1 do
begin
PMin := I;
for J := I + 1 to AItems.Count - 1 do
begin
if CompareItems(AItems[J], AItems[PMin]) < 0 then begin
PMin := J;
end;
end;
if PMin <> I then
begin
AItems[PMin].Index := I;
end;
end;
finally
AItems.EndUpdate;
end;
end;

How can I quickly convert an array of numeral characters into an integer?

Situation: a whole number saved as hex in a byte array(TBytes). Convert that number to type integer with less copying, if possible without any copying.
here's an example:
array = ($35, $36, $37);
This is '5', '6', '7' in ansi. How do I convert it to 567(=$273) with less trouble?
I did it by copying twice. Is it possible to be done faster? How?
You can use LookUp Table instead HexToInt...
This procedure works only with AnsiChars and of course no error checking is provided!
var
Table :array[byte]of byte;
procedure InitLookupTable;
var
n: integer;
begin
for n := 0 to Length(Table) do
case n of
ord('0')..ord('9'): Table[n] := n - ord('0');
ord('A')..ord('F'): Table[n] := n - ord('A') + 10;
ord('a')..ord('f'): Table[n] := n - ord('a') + 10;
else Table[n] := 0;
end;
end;
function HexToInt(var hex: TBytes): integer;
var
n: integer;
begin
result := 0;
for n := 0 to Length(hex) -1 do
result := result shl 4 + Table[ord(hex[n])];
end;
function BytesToInt(const bytes: TBytes): integer;
var
i: integer;
begin
result := 0;
for i := 0 to high(bytes) do
result := (result shl 4) + HexToInt(bytes[i]);
end;
As PA pointed out, this will overflow with enough digits, of course. The implementation of HexToInt is left as an exercise to the reader, as is error handling.
You can do
function CharArrToInteger(const Arr: TBytes): integer;
var
s: AnsiString;
begin
SetLength(s, length(Arr));
Move(Arr[0], s[1], length(s));
result := StrToInt(s);
end;
procedure TForm1.FormCreate(Sender: TObject);
var
a: TBytes;
begin
a := TBytes.Create($35, $36, $37);
Caption := IntToStr(CharArrToInteger(a));
end;
If you know that the string is null-terminated, that is, if the final character in the array is 0, then you can just do
function CharArrToInteger(const Arr: TBytes): integer;
begin
result := StrToInt(PAnsiChar(#Arr[0]));
end;
procedure TForm1.FormCreate(Sender: TObject);
var
a: TBytes;
begin
a := TBytes.Create($35, $36, $37, 0);
Caption := IntToStr(CharArrToInteger(a));
end;
The most natural approach, however, is to use an array of characters instead of an array of bytes! Then the compiler can do some tricks for you:
procedure TForm1.FormCreate(Sender: TObject);
var
a: TCharArray;
begin
a := TCharArray.Create(#$35, #$36, #$37);
Caption := IntToStr(StrToInt(string(a)));
end;
It cannot be any faster than that ;-)
function HexToInt(num:pointer; size:Cardinal): UInt64;
var i: integer;
inp: Cardinal absolute num;
begin
if(size > SizeOf(Result)) then Exit;
result := 0;
for i := 0 to size-1 do begin
result := result shl 4;
case(PByte(inp+i)^) of
ord('0')..ord('9'): Inc(Result, PByte(inp+i)^ - ord('0'));
ord('A')..ord('F'): Inc(Result, PByte(inp+i)^ - ord('A') + 10);
ord('a')..ord('f'): Inc(Result, PByte(inp+i)^ - ord('a') + 10);
end;
end;
end;
function fHexToInt(b:TBytes): UInt64; inline;
begin
Result:=HexToInt(#b[0], Length(b));
end;
...
b:TBytes = ($35, $36, $37);
HexToInt(#b[0], 3);
fHexToInt(b);

Is there an inverse function of *SysUtils.Format* in Delphi

Has anyone written an 'UnFormat' routine for Delphi?
What I'm imagining is the inverse of SysUtils.Format and looks something like this
UnFormat('a number %n and another %n',[float1, float2]);
So you could unpack a string into a series of variables using format strings.
I've looked at the 'Format' routine in SysUtils, but I've never used assembly so it is meaningless to me.
This is called scanf in C, I've made a Delphi look-a-like for this :
function ScanFormat(const Input, Format: string; Args: array of Pointer): Integer;
var
InputOffset: Integer;
FormatOffset: Integer;
InputChar: Char;
FormatChar: Char;
function _GetInputChar: Char;
begin
if InputOffset <= Length(Input) then
begin
Result := Input[InputOffset];
Inc(InputOffset);
end
else
Result := #0;
end;
function _PeekFormatChar: Char;
begin
if FormatOffset <= Length(Format) then
Result := Format[FormatOffset]
else
Result := #0;
end;
function _GetFormatChar: Char;
begin
Result := _PeekFormatChar;
if Result <> #0 then
Inc(FormatOffset);
end;
function _ScanInputString(const Arg: Pointer = nil): string;
var
EndChar: Char;
begin
Result := '';
EndChar := _PeekFormatChar;
InputChar := _GetInputChar;
while (InputChar > ' ')
and (InputChar <> EndChar) do
begin
Result := Result + InputChar;
InputChar := _GetInputChar;
end;
if InputChar <> #0 then
Dec(InputOffset);
if Assigned(Arg) then
PString(Arg)^ := Result;
end;
function _ScanInputInteger(const Arg: Pointer): Boolean;
var
Value: string;
begin
Value := _ScanInputString;
Result := TryStrToInt(Value, {out} PInteger(Arg)^);
end;
procedure _Raise;
begin
raise EConvertError.CreateFmt('Unknown ScanFormat character : "%s"!', [FormatChar]);
end;
begin
Result := 0;
InputOffset := 1;
FormatOffset := 1;
FormatChar := _GetFormatChar;
while FormatChar <> #0 do
begin
if FormatChar <> '%' then
begin
InputChar := _GetInputChar;
if (InputChar = #0)
or (FormatChar <> InputChar) then
Exit;
end
else
begin
FormatChar := _GetFormatChar;
case FormatChar of
'%':
if _GetInputChar <> '%' then
Exit;
's':
begin
_ScanInputString(Args[Result]);
Inc(Result);
end;
'd', 'u':
begin
if not _ScanInputInteger(Args[Result]) then
Exit;
Inc(Result);
end;
else
_Raise;
end;
end;
FormatChar := _GetFormatChar;
end;
end;
I know it tends to scare people, but you could write a simple function to do this using regular expressions
'a number (.*?) and another (.*?)
If you are worried about reg expressions take a look at www.regexbuddy.com and you'll never look back.
I tend to take care of this using a simple parser. I have two functions, one is called NumStringParts which returns the number of "parts" in a string with a specific delimiter (in your case above the space) and GetStrPart returns the specific part from a string with a specific delimiter. Both of these routines have been used since my Turbo Pascal days in many a project.
function NumStringParts(SourceStr,Delimiter:String):Integer;
var
offset : integer;
curnum : integer;
begin
curnum := 1;
offset := 1;
while (offset <> 0) do
begin
Offset := Pos(Delimiter,SourceStr);
if Offset <> 0 then
begin
Inc(CurNum);
Delete(SourceStr,1,(Offset-1)+Length(Delimiter));
end;
end;
result := CurNum;
end;
function GetStringPart(SourceStr,Delimiter:String;Num:Integer):string;
var
offset : integer;
CurNum : integer;
CurPart : String;
begin
CurNum := 1;
Offset := 1;
While (CurNum <= Num) and (Offset <> 0) do
begin
Offset := Pos(Delimiter,SourceStr);
if Offset <> 0 then
begin
CurPart := Copy(SourceStr,1,Offset-1);
Delete(SourceStr,1,(Offset-1)+Length(Delimiter));
Inc(CurNum)
end
else
CurPart := SourceStr;
end;
if CurNum >= Num then
Result := CurPart
else
Result := '';
end;
Example of usage:
var
st : string;
f1,f2 : double;
begin
st := 'a number 12.35 and another 13.415';
ShowMessage('Total String parts = '+IntToStr(NumStringParts(st,#32)));
f1 := StrToFloatDef(GetStringPart(st,#32,3),0.0);
f2 := StrToFloatDef(GetStringPart(st,#32,6),0.0);
ShowMessage('Float 1 = '+FloatToStr(F1)+' and Float 2 = '+FloatToStr(F2));
end;
These routines work wonders for simple or strict comma delimited strings too. These routines work wonderfully in Delphi 2009/2010.

Resources