Search code examples
delphisetdelphi-xedelphi-xe5

How can I get the number of elements of any variable of type set?


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:

  1. Pass constants to it
  2. Do a type check and make sure that a set is passed to the function
  3. Pass the value instead of its address
  4. Get rid of SizeOfSet parameter

I 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.


Solution

  • 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));