Generate three random characters in Delphi - delphi

Hi I am trying to generate three random characters using a function in Delphi, the code is this:
function generate(cantidad: integer): string;
const
letras_mi = 'abcdefghijklmnopqrstuvwxyz';
const
letras_ma = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
const
numeros = '0123456789';
var
finalr: string;
begin
finalr := '';
finalr := finalr + IntToStr(Random(Length(letras_mi)) + 1);
finalr := finalr + IntToStr(Random(Length(letras_ma)) + 1);
finalr := finalr + IntToStr(Random(Length(numeros)) + 1);
Result := finalr;
end;
the problem is that things like 20142 me back when I'm actually waiting 3 characters constant random variables.

Your code is converting integer index values to strings. Note that your only reference to your constants is to take their length. You return indices rather than characters.
You could fix your code by using the integer indices you generate to reference elements within your string constants. Mason and Ken showed how to do that.
Personally I would do away with the constants and write
Chr(ord('a') + Random(26))
and
Chr(ord('A') + Random(26))
and
Chr(ord('0') + Random(10))
The ordinal values of these characters were designed way back when to allow such code.

You're adding the result of Random to your finalr, not the random letter from the constants.
Try something like this instead - it uses the return of Random as the index into the string constant characters:
function generate(cantidad: integer): string;
const
letras_mi = 'abcdefghijklmnopqrstuvwxyz';
letras_ma = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
numeros = '0123456789';
begin
Result := '';
Result := Result + letras_mi[Random(Length(letras_mi)) + 1];
Result := Result + letras_ma[Random(Length(letras_ma)) + 1];
Result := Result + numeros[Random(Length(numeros)) + 1];
end;

Let's look at what your code is doing, as a compiler sees it:
IntToStr(Random(Length(letras_mi)) + 1)
Call IntToStr on the result of:
Call Random on the result of:
Add
Length(letras_mi)
1
IntToStr takes a number (such as 5) and turns it into a string (such as '5'). What you want to do is use the random value to index into your array, like so:
letras_mi[Random(Length(letras_mi)) + 1]

function RandomString(const ALength: Integer): String;
var
i: Integer;
LCharType: Integer;
begin
Result := '';
for i := 1 to ALength do
begin
LCharType := Random(3);
case LCharType of
0: Result := Result + Chr(ord('a') + Random(26));
1: Result := Result + Chr(ord('A') + Random(26));
2: Result := Result + Chr(ord('0') + Random(10));
end;
end;
end;

This will generate random strings that looks like words.
function GenerateRandomWord(CONST Len: Integer=16; StartWithVowel: Boolean= FALSE): string;
CONST
sVowels: string= 'AEIOUY';
sConson: string= 'BCDFGHJKLMNPQRSTVWXZ';
VAR
i: Integer;
B: Boolean;
begin
B:= StartWithVowel;
SetLength(Result, Len);
for i:= 1 to len DO
begin
if B
then Result[i]:= sVowels[Random(Length(sVowels)) + 1]
else Result[i]:= sConson[Random(Length(sConson)) + 1];
B:= NOT B;
end;
end;
So, use it like this: GenerateRandomWord(3);

const
Alphabetdown = 'abcdefghijklmnopqrstuvwxyz' ;
procedure TForm1.Button1Click(Sender: TObject);
var
sGeneratedAccNo : string ;
begin
sGeneratedAccNo := sGeneratedAccNo + Alphabetdown[Random(Length(Alphabetdown) + 1)] ;
showMessage(sGeneratedAccNo) ;

Faster way would be avoiding re-allocation of memory time and again.
function generate(cantidad: integer): string;
const
letras_mi = 'abcdefghijklmnopqrstuvwxyz';
numeros = '0123456789';
begin
SetLength(Result, 3); // only alloc memory once
Result[1] := letras_mi[Random(Length(letras_mi)) + 1];
Result[2] := UpCase(letras_mi[Random(Length(letras_mi)) + 1]);
Result[3] := numeros[Random(Length(numeros)) + 1];
end;
http://docwiki.embarcadero.com/Libraries/XE2/en/System.UpCase
http://www.freepascal.org/docs-html/rtl/system/upcase.html
And sometimes even slightly faster would be using local variable to avoid few extra UniqueString calls for var-parameter Result.
http://docwiki.embarcadero.com/Libraries/XE2/en/System.UniqueString
http://www.freepascal.org/docs-html/rtl/system/uniquestring.html
However timings or CPU-level code check should be made for one's specific compiler version and options to see what difference this actually makes, if any.
function generate(cantidad: integer): string;
const
letras_mi = 'abcdefghijklmnopqrstuvwxyz';
numeros = '0123456789';
var local: string;
begin
SetLength(local, 3); // only alloc memory once
local[1] := letras_mi[Random(Length(letras_mi)) + 1];
local[2] := UpCase(letras_mi[Random(Length(letras_mi)) + 1]);
local[3] := numeros[Random(Length(numeros)) + 1];
Result := local;
end;
PS. Ord-based approach is also better here than picking a char from array/string, but that is independent issue. Also i'd be wary to use Chr function with Delphi 2009 or newer, it would only work uniform on #0..#127 values. Explicitly declared typecasts like AnsiChar(i) and WideChar(i) might be more stable substitute in cause one day you would need letters outside 7-bit subrange, like eña and other European-specific ones.

function generate(cantidad: integer): string;
const
letras_mi = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
var i:integer;
begin
Result := '';
for I := 1 to cantidad do
Result := Result + letras_mi[Random(Length(letras_mi)) + 1];
end;
function generateNum(cantidad: integer): string;
const
letras_mi = '0123456789';
var i:integer;
begin
Result := '';
for I := 1 to cantidad do
Result := Result + letras_mi[Random(Length(letras_mi)) + 1];
end;

Related

Delphi (2006): how to Split by new line and break at the same time

I have this simple operation in Java, where the string is split by new line and break.
String i= "Holidays
Great.
Bye";
String []linesArray = i.split("\\r?\\n");
I would like to obtain the same result in Delphi 2006.
Is it valid to use the following steps?
charArray[0] := '\\r';
charArray[1] := '\\n';
strArray := strA.Split(charArray);
I interpret your request like this: "Split a string at both CR and LF." which implies that CR+LF gives an empty string element. For instance, 'alpha'#13'beta'#10'gamma'#13#10'delta' yields the five elements 'alpha', 'beta', 'gamma', '', and 'delta'.
If so, and if you are using a non-ancient version of Delphi, this is really simple:
var S := 'alpha'#13'beta'#10'gamma'#13#10'delta';
var Parts := S.Split([#13, #10]);
for var Part in Parts do
ShowMessage(Part);
For old Delphi versions
The code above requires TStringHelper (crucially) and also makes use of inline variable declarations, for in loops, and generics.
For old Delphi versions, you can do it manually:
type
TStringArray = array of string;
function Split(const S: string): TStringArray;
var
Count: Integer;
const
Delta = 512;
procedure Add(const Part: string);
begin
if Length(Result) = Count then
SetLength(Result, Length(Result) + Delta);
Result[Count] := Part;
Inc(Count);
end;
var
p, i: Integer;
begin
Result := nil;
Count := 0;
p := 0; // previous delim
for i := 1 to Length(S) do
if S[i] in [#13, #10] then
begin
Add(Copy(S, Succ(p), i - p - 1));
p := i;
end;
Add(Copy(S, Succ(p)));
SetLength(Result, Count);
end;
procedure TForm1.FormCreate(Sender: TObject);
var
S: string;
Parts: TStringArray;
i: Integer;
begin
S := 'alpha'#13'beta'#10'gamma'#13#10'delta';
Parts := Split(S);
for i := 0 to High(Parts) do
ShowMessage(Parts[i]);
end;

Generate random password in Delphi

I have a following function to generate random passwords:
function GeneratePassword(ALength: Integer; Mode: TPasswordMode): string;
const
cLower = 'abcdefghijklmnopqrstuvwxyz';
cUpper = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
cNumbers = '0123456789';
cExtra = '_.';
var
i : Integer;
S : string;
iM: BYTE;
begin
if Mode = [] then Exit;
i := 0;
Randomize;
while (i < ALength) do
begin
iM := RANDOM(4);
case iM of
0: if (pmLower in Mode) then begin
S := S + cLower[1+RANDOM(Length(cLower))];
Inc(i);
end;
1: if (pmUpper in Mode) then begin
S := S + cUpper[1+RANDOM(Length(cUpper))];
Inc(i);
end;
2: if (pmNumbers in Mode) then begin
S := S + cNumbers[1+RANDOM(Length(cNumbers))];
Inc(i);
end;
3: if (pmExtra in Mode) then begin
S := S + cExtra[1+RANDOM(Length(cExtra))];
Inc(i);
end;
end;
end;
Result := S;
end;
How to make this function so that a capital letter and a special character appear only once, but always? Sometimes there is no capital letter or special character when I'm generating passwords.
To be sure to have one special char and one uppercase you can do that :
function GeneratePassword(ALength: Integer; Mode: TPasswordModes): string;
const
cLower = 'abcdefghijklmnopqrstuvwxyz';
cUpper = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
cNumbers = '0123456789';
cExtra = '_.';
var
iM: Byte;
i: integer;
begin
if Mode = [] then Exit;
Result := '';
i := 0;
if pmUpper in Mode then
Inc(i);
if pmExtra in Mode then
Inc(i);
// add lower case and/or number
while Result.Length < (ALength - i) do
begin
iM := Random(2);
case iM of
0: if (pmLower in Mode) then begin
Result := Result + cLower[1 + Random(Length(cLower))];
end;
1: if (pmNumbers in Mode) then begin
Result := Result + cNumbers[1 + Random(Length(cNumbers))];
end;
end;
end;
// add uppercase and/or extra
if i > 0 then
begin
if pmUpper in Mode then
Result := Result.Insert(1 + Random(Length(Result)), cUpper[1 + Random(Length(cUpper))]);
if pmExtra in Mode then
Result := Result.Insert(1 + Random(Length(Result)), cExtra[1 + Random(Length(cExtra))]);
end;
end;
type
TPasswordMode = (pmLower, pmUpper, pmNumbers, pmExtra);
TPasswordModes = set of TPasswordMode;
implementation
function GeneratePassword(ALength: Integer; Mode: TPasswordModes): string;
const
cLower = 'abcdefghijklmnopqrstuvwxyz';
cUpper = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
cNumbers = '0123456789';
cExtra = '_.';
var
i : Integer;
S : string;
iM: BYTE;
begin
if Mode = [] then Exit;
i := 0;
Randomize;
while (i < ALength) do
begin
iM := RANDOM(4);
case iM of
0: if (pmLower in Mode) then begin
S := S + cLower[1+RANDOM(Length(cLower))];
Inc(i);
end;
1: if (pmUpper in Mode) then begin
S := S + cUpper[1+RANDOM(Length(cUpper))];
Inc(i);
Mode := Mode - [pmUpper]; // This I added
end;
2: if (pmNumbers in Mode) then begin
S := S + cNumbers[1+RANDOM(Length(cNumbers))];
Inc(i);
end;
3: if (pmExtra in Mode) then begin
S := S + cExtra[1+RANDOM(Length(cExtra))];
Inc(i);
Mode := Mode - [pmExtra]; // This I added
end;
end;
end;
Result := S;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
ShowMessage(GeneratePassword(10,[pmLower,pmUpper,pmNumbers,pmExtra]));
end;
This is not a complete solution but with this you will at least remove Upper and Extra from the requirements as soon as they get taken. You now check in the end if they ever were ever added if required and then add them if so required.
Edit:
I was in a hurry when I typed the above. You just need to check in the end if the generated password contains an Upper and Extra character. If not, you still need to add them as that was one of your requirements.
Here is example that first makes sure all extra modes are filled and the rest. It prefills Result with spaces and then replaces with random chars until all spaces are replaced.
function GetRandomEmptyPos(const aStr: string): integer; inline;
begin
// find random empty position
repeat
Result := Random(Length(aStr)) + 1;
until aStr[Result] = ' ';
end;
function GeneratePassword2(aLength: Integer; aModes: TPasswordModes): string;
const
cLower = 'abcdefghijklmnopqrstuvwxyz';
cUpper = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
cNumbers = '0123456789';
cExtra = '_.';
var
i,vPos: integer;
vMode: TPasswordMode;
begin
if (aLength = 0) or (aModes = []) then Exit;
Randomize;
// Prefill Result with empty spaces
Result := StringOfChar(' ', aLength);
// Add extra characters at random places
for vMode in aModes do
begin
vPos := GetRandomEmptyPos(Result);
case vMode of
pmLower: Result[vPos] := cLower[Random(Length(cLower)) + 1];
pmUpper: Result[vPos] := cUpper[Random(Length(cUpper)) + 1];
pmNumbers: Result[vPos] := cNumbers[Random(Length(cNumbers)) + 1];
pmExtra: Result[vPos] := cExtra[Random(Length(cExtra)) + 1];
end;
end;
// Add random char on emtpy spaces
for i := 1 to Result.Length do
if Result[i] = ' ' then
Result[i] := String(cLower + cNumbers)[Random(Length(cLower) + Length(cNumbers)) + 1];
end;
unrefined code but maybe it can be useful ...
function RandomPassword(PLen: Integer): string;
var
strBase: string;
strUpper: string;
strSpecial: string;
strRecombine: string;
begin
strRecombine:='';
Result := '';
Randomize;
//string with all possible chars
strBase := 'abcdefghijklmnopqrstuvwxyz1234567890';
strUpper:='ABCDEFGHIJKLMNOPQRSTUVWXYZ';
strSpecial:='#!_';
// Start Random
strRecombine:= strUpper[Random(Length(strUpper)) + 1];
Result:=strRecombine;
strRecombine:= strSpecial[Random(Length(strSpecial))+1];
repeat
Result := Result + strBase[Random(Length(strBase)) + 1];
until (Length(Result) = PLen);
RandomRange(2, Length(strBase));
Result[RandomRange(2, PLen)]:=strRecombine[1];
//result:=Result+strRecombine;
end;

Loop over files in a directory using the shell in delphi

I want to loop through all the files in a given directory and return their version number and exe name. I have tried digging into the shell to see if I can pull this off, however I have not been able to find a solution. Any tips would be appreciated.
This does it:
Drop a TMemo and a TButton on your form and do
type
TVerInfo = packed record
vMajor, vMinor, vRelease, vBuild: word;
end;
function GetFileVerNumbers(const FileName: string): TVerInfo;
var
len, dummy: cardinal;
verdata: pointer;
verstruct: pointer;
const
InvalidVersion: TVerInfo = (vMajor: 0; vMinor: 0; vRelease: 0; vBuild: 0);
begin
len := GetFileVersionInfoSize(PWideChar(FileName), dummy);
if len = 0 then
Exit(InvalidVersion);
GetMem(verdata, len);
try
GetFileVersionInfo(PWideChar(FileName), 0, len, verdata);
VerQueryValue(verdata, '\', verstruct, dummy);
result.vMajor := HiWord(TVSFixedFileInfo(verstruct^).dwFileVersionMS);
result.vMinor := LoWord(TVSFixedFileInfo(verstruct^).dwFileVersionMS);
result.vRelease := HiWord(TVSFixedFileInfo(verstruct^).dwFileVersionLS);
result.vBuild := LoWord(TVSFixedFileInfo(verstruct^).dwFileVersionLS);
finally
FreeMem(verdata);
end;
end;
function GetFileVer(const FileName: string): string;
begin
with GetFileVerNumbers(FileName) do
result := IntToStr(vMajor) + '.' +
IntToStr(vMinor) + '.' + IntToStr(vRelease) + '.' +
IntToStr(vBuild);
end;
procedure TForm1.Button1Click(Sender: TObject);
const
path = 'C:\WINDOWS';
var
SR: TSearchRec;
begin
Memo1.Clear;
if FindFirst(IncludeTrailingBackslash(path) + '*.exe', faAnyFile, SR) = 0 then
try
repeat
Memo1.Lines.Add(SR.Name + #9 +
GetFileVer(IncludeTrailingBackslash(path) + SR.Name));
until FindNext(SR) <> 0;
finally
FindClose(SR);
end;
end;

Convert hex string to ansistring in Delphi 2010

I used to use this function to convert hex string to string in Delphi 6 :
const
testSign = '207F8060287F585054505357FFD55861';
function Hex2Dec(const data: string): byte;
var
nH1, nH2: byte;
begin
if data[1] in ['0' .. '9'] then
nH1 := strtoint(data[1])
else
nH1 := 9 + ord(data[1]) - 64;
if data[2] in ['0' .. '9'] then
nH2 := strtoint(data[2])
else
nH2 := 9 + ord(data[2]) - 64;
Result := nH1 * 16 + nH2;
end;
function HexStrToStr(const HexStr: string): string;
var
BufStr: string;
LenHex: Integer;
x, y: Integer;
begin
LenHex := Length(HexStr) div 2;
x := 1;
y := 0;
while y <> LenHex do
begin
Inc(y);
BufStr := BufStr + Chr(Hex2Dec(HexStr[x] + HexStr[x + 1]));
Inc(x, 2);
end;
Result := BufStr;
end;
Now I want to use the function with Delphi 2010.
const
testSign: AnsiString = '207F8060287F585054505357FFD55861';
function Hex2Dec(const data: ansistring): byte;
var
nH1, nH2: byte;
begin
if data[1] in ['0' .. '9'] then
nH1 := strtoint(data[1])
else
nH1 := 9 + ord(data[1]) - 64;
if data[2] in ['0' .. '9'] then
nH2 := strtoint(data[2])
else
nH2 := 9 + ord(data[2]) - 64;
Result := nH1 * 16 + nH2;
end;
function HexStrToStr(const HexStr: ansistring): ansistring;
var
BufStr: ansistring;
LenHex: Integer;
x, y: Integer;
begin
LenHex := Length(HexStr) div 2;
x := 1;
y := 0;
while y <> LenHex do
begin
Inc(y);
BufStr := BufStr + Chr(Hex2Dec(HexStr[x] + HexStr[x + 1]));
Inc(x, 2);
end;
Result := BufStr;
end;
Output from first code in D6 :
' '#$7F'€`('#$7F'XPTPSWÿÕXa'
Output from second code in D2010 :
' '#$7F#$0080'`('#$7F'XPTPSWÿÕXa'
How do I fix the code in D2010 so it can produces same result like D6?
Besides the solutions others provided, you can also make use of the built-in function:
function HexStrToStr(const HexStr: string): string;
var
tmp: AnsiString;
begin
Assert(not Odd(Length(HexStr)), 'HexToStr input length must be an even number');
SetLength(tmp, Length(HexStr) div 2);
HexToBin(PWideChar(HexStr), #tmp[1], Length(tmp));
result := tmp;
end;
This implementation assumes that the hex-encoded string has been an Ansistring in the first place. For flexibility I suggest to use TBytes instead.

How to expose a Delphi set type via Soap

I'm currently creating soap wrappers for some Delphi functions so that we can easily use them from PHP, C# and Delphi.
I wonder what's the best way to expose sets.
type
TCountry = (countryUnknown,countryNL,countryD,countryB,countryS,countryFIN,countryF,countryE,countryP,countryPl,countryL);
TCountrySet = set of TCountry;
function GetValidCountrySet(const LicensePlate:string; const PossibleCountriesSet:TCountrySet):TCountrySet;
I'm currently wrapping it like this for the soap server:
type
TCountryArray = array of TCountry;
function TVehicleInfo.GetValidCountrySet(const LicensePlate:string; const PossibleCountriesSet:TCountryArray):TCountryArray;
It works, but I need to write a lot of useless and ugly code to convert sets-->arrays and arrays-->sets.
Is there an easier, more elegant, or more generic way to do this?
You could use TypInfo and use a bit of clever casting.
uses TypInfo;
type
TCountry = (cnyNone, cnyNL, cnyD, cnyGB, cnyF, cnyI);
TCountrySet = set of TCountry;
TCountryArray = array of TCountry;
TEnumIntegerArray = array of Integer;
TEnumByteArray = array of Byte;
function GetEnumNamesInSet(const aTypeInfo: PTypeInfo; const aValue: Integer; const aSeparator: string = ','): string;
var
IntSet: TIntegerSet;
i: Integer;
begin
Result := '';
Integer( IntSet ) := aValue;
for i := 0 to SizeOf(Integer) * 8 - 1 do begin
if i in IntSet then begin
if Result <> '' then begin
Result := Result + ',';
end;
Result := Result + GetEnumName(aTypeInfo, i);
end;
end;
end;
function SetToIntegerArray(const aTypeInfo: PTypeInfo; const aValue: Integer): TEnumIntegerArray;
var
IntSet: TIntegerSet;
i: Integer;
begin
SetLength(Result, 0);
Integer( IntSet ) := aValue;
for i := 0 to SizeOf(Integer) * 8 - 1 do begin
if i in IntSet then begin
SetLength(Result, Length(Result) + 1);
Result[Length(Result) - 1] := i;
end;
end;
end;
function SetToByteArray(const aTypeInfo: PTypeInfo; const aValue: Byte): TEnumByteArray;
var
IntSet: TIntegerSet;
i: Integer;
begin
SetLength(Result, 0);
Integer( IntSet ) := aValue;
for i := 0 to SizeOf(Byte) * 8 - 1 do begin
if i in IntSet then begin
SetLength(Result, Length(Result) + 1);
Result[Length(Result) - 1] := i;
end;
end;
end;
Then use as:
procedure TEnumForm.FillMemo;
var
Countries: TCountrySet;
// EIA: TEnumIntegerArray;
EBA: TEnumByteArray;
CA: TCountryArray;
i: Integer;
cny: TCountry;
begin
Countries := [cnyNL, cnyD];
CountriesMemo.Text := GetEnumNamesInSet(TypeInfo(TCountry), Byte(Countries));
// if SizeOf(TCountry) > SizeOf(Byte) then begin
// EIA := SetToIntegerArray(TypeInfo(TCountry), Integer(Countries));
// end else begin
EBA := SetToByteArray(TypeInfo(TCountry), Byte(Countries));
// end;
CountriesMemo.Lines.Add('====');
CountriesMemo.Lines.Add('Values in Array: ');
// if SizeOf(TCountry) > SizeOf(Byte) then begin
// CA := TCountryArray(EIA);
// end else begin
CA := TCountryArray(EBA);
// end;
for i := 0 to Length(CA) - 1 do begin
CountriesMemo.Lines.Add(IntToStr(Ord(CA[i])));
end;
CountriesMemo.Lines.Add('====');
CountriesMemo.Lines.Add('Names in Array: ');
// if SizeOf(TCountry) > SizeOf(Byte) then begin
// CA := TCountryArray(EIA);
// end else begin
CA := TCountryArray(EBA);
// end;
for i := 0 to Length(CA) - 1 do begin
cny := CA[i];
CountriesMemo.Lines.Add(GetEnumName(TypeInfo(TCountry), Ord(cny)));
end;
end;
You will need to select the proper casting based on the size of the TCountry enum. If it has 8 members it will be a Byte, any bigger and it will be an Integer. Anyway, Delphi will complain on the cast of Byte(Countries) or Integer(Countries) when you get it wrong.
Please note:
The functions now take the TypeInfo of TCountry - the elements of the TCountrySet. They could be changed to take TypeInfo(TCountrySet). However that would mean having the functions work out what elements are in the set and I simply haven't had the time or inclination to do that yet.
Soap should be used in a platform and language agnostic way - I would design all data transfer objects (DTO) based on simple types e.g. array of string, without language specific features. Then map the DTO to the matching business objects. This also will give you an 'anticorruption layer'.

Resources