Перечисление возможных значений набора в Delphi

у меня есть алгоритм расчета в 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. может быть более четырех вариантов (да, я знаю, что он растет экспоненциально, и если слишком много вариантов, алгоритм может занять вечность).

6 ответов


попробовать

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

Я знаю, что этот вопрос довольно старый, но это мое предпочтение, так как это просто и естественно для меня :

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;

ваш код не компилируется, потому что ваше перечисление (TMyOption) имеет менее 8 значений, а Delphi использует минимально возможный размер (в байтах) для наборов. Таким образом, переменная byte будет работать для вас.

Если у вас есть набор с более чем 8, но менее чем 16 возможных элементов, слово будет работать (а не целое число).

для более чем 16, но менее чем 32 переменной DWord и typecast.

для более чем 32 возможных элементов, я думаю, что лучший подход использовать массив байтов или что-то в этом роде.


500-ответ внутренней ошибки сервера, вероятно, самый простой.

другой подход, который с меньшей вероятностью нарушит изменения количества опций, будет заключаться в объявлении массива boolean и включении/выключении их. Это медленнее, чем работа с чистыми целыми числами. Главное преимущество, вам не нужно будет изменять целочисленный тип, который вы используете, и вы можете использовать его, если у вас есть более 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;

кастинг из целого числа в множество невозможен, но Tondrej как пишет статья в блоге on SetToString и StringToSet это выставляет то, что вы хотите в 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


проблема в том, что вы пытаетесь привести к типу set вместо перечисляемого типа. Вы можете выполнять приведение между integer и enumerated, поскольку оба типа являются порядковыми, но вы не можете выполнить приведение к набору, поскольку они используют bitfiels, как вы уже отметили. Если вы используете:

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

это будет работать, хотя это не то, что вы хотите.

У меня была эта же проблема несколько месяцев назад и пришел к выводу, что вы не можете перечислить содержимое набора в Delphi (по крайней мере в Delphi 7), потому что язык не определяет такую операцию на множестве.

редактировать: кажется, что вы можете даже в D7, см. coments к этому ответу.