Delphi中枚举可能的设定值

发布于 2024-09-30 16:28:41 字数 874 浏览 0 评论 0原文

我在 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),如果有的话,它是什么?

另一种可能性是只使用整数作为位字段:

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

然后用按位和测试成员资格:

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

我已经尝试过这个,它有效,但感觉使用集合会更自然并且更好地使用类型系统(即使我要超出上述类型系统来枚举集合)。

有没有比枚举底层整数表示更好/更安全的方法来枚举所有可能的集合组合?

注意:

  1. 我知道理论上不能保证集合的整数值(尽管我怀疑如果您不使用枚举编号,它们在实践中就会得到保证)。
  2. 可能有四个以上的选项(是的,我知道它呈指数级增长,如果选项太多,算法可能会永远持续下去)。

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

洛阳烟雨空心柳 2024-10-07 16:28:41

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

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;

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;
平生欢 2024-10-07 16:28:41

尝试

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

Try

var EnumerationByte: Byte;
...
for EnumerationByte := 0 to 15 do begin
    Options := TMyOptions(EnumerationByte);
end;
流绪微梦 2024-10-07 16:28:41

您的代码无法编译,因为您的枚举 (TMyOption) 的值少于 8 个,并且 Delphi 使用集合的最小可能大小(以字节为单位)。因此,字节变量将适合您。

如果您的集合包含超过 8 个但少于 16 个可能的元素,则可以使用 Word(而不是整数)。

对于大于 16 但小于 32 的 DWord 变量和类型转换。

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

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.

画中仙 2024-10-07 16:28:41

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;

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;
吃→可爱长大的 2024-10-07 16:28:41

从 Integer 转换为 Set 是不可能的,但是 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

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

我是有多爱你 2024-10-07 16:28:41

问题是您试图转换为集合类型而不是枚举类型。您可以在整数和枚举之间进行转换,因为两者都是序数类型,但不能转换为集合,因为它们使用您已经注意到的位字段。如果你使用:

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

它会起作用,尽管不是你想要的。

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

编辑:看来你甚至可以在D7中看到对此答案的评论。

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文