Jonathan Morgan
Jonathan Morgan

Reputation: 320

Enumerate possible set values in Delphi

I have a calculation algorithm in Delphi with a number of different options, and I need to try every combination of options to find an optimal solution.

TMyOption = (option1, option2, option3, option4);
TMyOptions = set of TMyOption;

I wondered about using an Integer loop to enumerate them:

for EnumerationInteger := 0 to 15 do begin
    Options := TMyOptions(EnumerationInteger);
end;

This does not compile. What I was wondering was if there was any fairly simple method to convert from Integer to Set (most questions on the Web try to go the other way, from Set to Integer), and if so what is it?

Another possibility is to just use the Integer as a bit-field:

C_Option1 = 1;
C_Option2 = 2;
C_Option3 = 4;
C_Option4 = 8;

and then test membership with a bitwise and:

if (Options and C_Option2) > 0 then begin
    ...
end;

I've tried this, and it works, but it feels like working with sets would be more natural and use the type system better (even though I'm going outside the said type system to enumerate the sets).

Is there a better/safer way to enumerate all possible set combinations than enumerating the underlying integer representation?

Notes:

  1. I know that the integer values of a set are not guaranteed in theory (though I suspect they are in practice if you don't play with the enumeration numbering).
  2. There could be more than four options (yes, I know that it grows exponentially and if there are too many options the algorithm could take forever).

Upvotes: 6

Views: 6622

Answers (6)

Hasan S
Hasan S

Reputation: 287

I know this question is quite old, but this is my preference since it's simple and natural to me :

function NumericToMyOptions(n: integer): TMyOptions;
var
  Op: TMyOption;
begin
  Result:= [];
  for Op:= Low(TMyOption) to High(TMyOption) do
    if n and (1 shl ord(Op)) > 0 then Include(Result, Op);
end;

Upvotes: 5

Alberto Martinez
Alberto Martinez

Reputation: 2670

The problem is that you are trying to cast to the set type instead of the enumerated type. You can cast between integer and enumerated because both are ordinal types, but you can't cast to a set because they use bitfiels as you already noted. If you use:

for EnumerationInteger := 0 to 15 do begin
  Option := TMyOption(EnumerationInteger);
end;

it would work, although is not what you want.

I had this same problem a few months ago and came to the conclusion that you can't enumerate the contents of a set in Delphi (at least in Delphi 7) because the language doesn't define such operation on a set.

Edit: It seems that you can even in D7, see coments to this answer.

Upvotes: 0

Jeroen Wiert Pluimers
Jeroen Wiert Pluimers

Reputation: 24473

Casting from an Integer to a Set is not possible, but Tondrej once wrote a blog article on SetToString and StringToSet that exposes what you want in the SetOrdValue method:

uses
  TypInfo;

procedure SetOrdValue(Info: PTypeInfo; var SetParam; Value: Integer);
begin
  case GetTypeData(Info)^.OrdType of
    otSByte, otUByte:
      Byte(SetParam) := Value;
    otSWord, otUWord:
      Word(SetParam) := Value;
    otSLong, otULong:
      Integer(SetParam) := Value;
  end;
end;

Your code then would become this:

for EnumerationInteger := 0 to 15 do begin
    SetOrdValue(TypeInfo(TMyOptions), Options, EnumerationInteger);
end;

--jeroen

Upvotes: 0

Ken Bourassa
Ken Bourassa

Reputation: 6467

500 - Internal Server Error's answer is probably the most simple.

Another approach that would less likely to break with changes to the number of options would be to declare an array of boolean, and switch them on/off. This is slower than working with pure integers though. The main advantage, you won't need to change the integer type you use, and you can use it if you have more than 32 options.

procedure DoSomething
var BoolFlags : Array[TOption] of Boolean;
    I: TOption;
  function GetNextFlagSet(var Bools : Array of Boolean) : Boolean;
  var idx, I : Integer;
  begin
    idx := 0;
    while Bools[idx] and (idx <= High(Bools)) do Inc(idx);

    Result := idx <= High(Bools);

    if Result then
      for I := 0 to idx do
        Bools[I] := not Bools[I];
  end;
begin
  for I := Low(BoolFlags) to High(BoolFlags) do BoolFlags[i] := False;

  repeat
    if BoolFlags[Option1] then
      [...]

  until not GetNextFlagSet(BoolFlags);
end;

Upvotes: 0

jachguate
jachguate

Reputation: 17203

Your code does not compile because your enumeration (TMyOption) have less than 8 values, and Delphi utilize the minimum possible size (in bytes) for sets. Thus, a byte variable will work for you.

If you have a set with more than 8 but less than 16 possible elements, a Word will work (and not an integer).

For more than 16 but less than 32 a DWord variable and typecast.

For more than 32 possible elements, I think a better approach is to use an array of bytes or something like that.

Upvotes: 2

Try

var EnumerationByte: Byte;
...
for EnumerationByte := 0 to 15 do begin
    Options := TMyOptions(EnumerationByte);
end;

Upvotes: 4

Related Questions