在Delphi中枚举可能的集合值

6

我有一个使用Delphi编写的计算算法,其中包含多个不同的选项,我需要尝试每一种选项的组合以找到最优解。

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

我想使用一个整数循环来枚举它们:
for EnumerationInteger := 0 to 15 do begin
    Options := TMyOptions(EnumerationInteger);
end;

这段代码无法编译。我想知道是否有一种相当简单的方法可以将Integer转换为Set(大多数网上的问题都是从Set转换为Integer),如果有,它是什么?

另一种可能性是将Integer用作位字段:

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

然后使用按位与测试成员身份:

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

我已经尝试过这种方法,它可以工作,但感觉使用集合会更自然并且更好地利用类型系统(尽管我正在超越所说的类型系统来枚举集合)。
有比枚举底层整数表示更好/更安全的方法来枚举所有可能的集合组合吗?
注:
1. 我知道理论上不能保证一个集合的整数值(尽管我认为如果您不玩枚举编号,实际上是可以保证的)。
2. 可能会有多于四个选项(是的,我知道它呈指数级增长,如果有太多选项,算法可能需要永远执行)。
6个回答

5

我知道这个问题很旧,但这是我的偏好,因为它对我来说简单自然:

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;

非常好,看起来更整洁(尽管我可能会坚持使用另一个,因为它更快)。 - Jonathan Morgan

4

尝试

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

确实可以工作。我猜对于成员超过8个的集合,它不起作用?(尽管到那时算法可能已经太慢了)。 - Jonathan Morgan
1
使用 sizeof(SetVar) 命令来查看 Delphi 中特定集合的表示方式。 - 500 - Internal Server Error

2

您的代码无法编译,因为您的枚举(TMyOption)少于8个值,而Delphi利用最小可能的大小(以字节为单位)来设置。因此,一个字节变量就可以满足您的需求。

如果您有一个具有超过8个但少于16个可能元素的集合,则Word将起作用(而不是整数)。

对于超过16个但少于32个的元素,使用DWord变量并进行类型转换。

对于超过32个可能元素,我认为更好的方法是使用字节数组或类似的东西。


1
问题不在于整数的大小,而在于他试图将其转换为set类型(TMyOptions),而不是枚举类型。 - Alberto Martinez
Alberto:在这种情况下,将set类型转换为byte-word-dword类型是完全可行和有意义的。 - jachguate

0
问题在于您试图将类型转换为集合类型而不是枚举类型。您可以在整数和枚举类型之间进行转换,因为两者都是序数类型,但您不能将其转换为集合,因为它们使用位字段,正如您已经注意到的那样。如果您使用以下代码:
for EnumerationInteger := 0 to 15 do begin
  Option := TMyOption(EnumerationInteger);
end;

虽然这可以工作,但不是你想要的。

几个月前我也遇到了同样的问题,并得出结论,在Delphi中无法枚举集合的内容(至少在Delphi 7中)因为该语言没有定义集合上的此类操作。

编辑:看来即使在D7中也可以,参见此答案的评论。


请查看被接受的答案。使用Byte而不是Integer确实可行。 - Jonathan Morgan
1
嗯...你可以枚举一个集合的内容。只是有点不直观。例如:对于 I := Low(Enum) to high(Enum) do /// if I in Set then。 - Ken Bourassa
2
或者,从 Delphi 2005 开始,您可以直接枚举内容:for el in s do ... - Rob Kennedy

0

500 - 内部服务器错误的答案可能是最简单的。

另一种方法,不太可能在选项数量发生变化时出错的方法是声明一个布尔数组,并在需要时切换开/关。虽然这比使用纯整数要慢一些,但主要优势是你无需更改所使用的整数类型,并且如果有超过32个选项,你仍然可以使用它。

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;

0

无法将整数转换为集合,但Tondrej曾经写过一篇关于SetToStringStringToSet博客文章,其中介绍了在SetOrdValue方法中所需的内容:

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;

你的代码将变成这样:

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

--jeroen


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接