简体   繁体   English

在Delphi中枚举可能的设置值

[英]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. 我在Delphi中有一个计算算法,有许多不同的选项,我需要尝试每个选项组合来找到最佳解决方案。

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

I wondered about using an Integer loop to enumerate them: 我想知道使用Integer循环枚举它们:

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? 我想知道的是,是否有任何相当简单的方法可以从Integer转换为Set(Web上的大多数问题都试图从另一个方向转换,从Set到Integer),如果是这样,它是什么?

Another possibility is to just use the Integer as a bit-field: 另一种可能性是将Integer用作位域:

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). 可能有四个以上的选项(是的,我知道它会以指数方式增长,如果有太多选项,算法可能会永远占用)。

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;

Try 尝试

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

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. 您的代码无法编译,因为您的枚举(TMyOption)的值小于8,Delphi使用最小可能的大小(以字节为单位)。 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). 如果你有一个超过8但少于16个可能元素的集合,Word将起作用(而不是整数)。

For more than 16 but less than 32 a DWord variable and typecast. 对于超过16但少于32的DWord变量和类型转换。

For more than 32 possible elements, I think a better approach is to use an array of bytes or something like that. 对于超过32个可能的元素,我认为更好的方法是使用字节数组或类似的东西。

The problem is that you are trying to cast to the set type instead of the enumerated type. 问题是您正在尝试转换为set类型而不是枚举类型。 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. 您可以在整数和枚举之间进行转换,因为它们都是序数类型,但您不能转换为集合,因为它们使用了您已经注意到的bitfiels。 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. 几个月前我遇到了同样的问题,得出的结论是你无法枚举Delphi中的一个集合的内容(至少在Delphi 7中),因为语言没有在集合上定义这样的操作。

Edit : It seems that you can even in D7, see coments to this answer. 编辑 :看来你甚至可以在D7中看到这个答案的咒语。

500 - Internal Server Error's answer is probably the most simple. 500 - 内部服务器错误的答案可能是最简单的。

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. 主要优点是,您不需要更改您使用的整数类型,如果您有超过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;

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: 从一个Integer到一个Set的转换是不可能的,但是Tondrej曾经写过一篇关于SetToStringStringToSet博客文章SetToStringSetOrdValue方法中公开了你想要的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;

Your code then would become this: 您的代码将成为这样:

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

--jeroen --jeroen

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM