AFAIK there's no built-in function for that. Searching the web I found this function and it works for me, but I prefer not to use it since it's assembly and I can't understand what it is doing. So I wrote this function that also works:
function Cardinality(const PSet: PByteArray;
const SizeOfSet(*in bytes*): Integer): Integer;
const
Masks: array[0..7] of Byte = (1, 2, 4, 8, 16, 32, 64, 128);
var
I, J: Integer;
begin
Result := 0;
for I := 0 to SizeOfSet - 1 do
for J := 0 to 7 do
if (PSet^[I] and Masks[J]) > 0 then
Inc(Result);
end;
Now, I want to know if I can rely on this function? Or maybe there's a trick behind the set data type and that's why delphi doesn't have a built-in method for that.
But if
my function is reliable then
how can I improve it to:
SizeOfSet
parameterI want to call it like Cardinality(AnySet)
instead of Cardinality(@AnySet, SizeOf(TAnySet))
.
By the way, I need to compile this in both XE and XE5.
In earlier version of Delphi you can do this :
function Card(ASet: TSet): Integer;
var
k: TSetElement;
begin
Result := 0;
for k in ASet do Inc(Result);
end;
You can implement this with generics and RTTI. Like so:
uses
SysUtils, TypInfo;
type
ERuntimeTypeError = class(Exception);
TSet<T> = class
strict private
class function TypeInfo: PTypeInfo; inline; static;
public
class function IsSet: Boolean; static;
class function Cardinality(const Value: T): Integer; static;
end;
const
Masks: array[0..7] of Byte = (1, 2, 4, 8, 16, 32, 64, 128);
implementation
{ TSet<T> }
class function TSet<T>.TypeInfo: PTypeInfo;
begin
Result := System.TypeInfo(T);
end;
class function TSet<T>.IsSet: Boolean;
begin
Result := TypeInfo.Kind=tkSet;
end;
function GetCardinality(const PSet: PByteArray;
const SizeOfSet(*in bytes*): Integer): Integer; inline;
var
I, J: Integer;
begin
Result := 0;
for I := 0 to SizeOfSet - 1 do
for J := 0 to 7 do
if (PSet^[I] and Masks[J]) > 0 then
Inc(Result);
end;
class function TSet<T>.Cardinality(const Value: T): Integer;
var
EnumTypeData: PTypeData;
begin
if not IsSet then
raise ERuntimeTypeError.Create('Invalid type in TSet<T>, T must be a set');
Result := GetCardinality(PByteArray(@Value), SizeOf(Value));
end;
Usage:
Writeln(TSet<SomeSet>.Cardinality(Value));
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With