Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to save/load Set of Types?

I have this code

type
  TXSample = (xsType1, xsType2, xsType3, xsType4, xsType5, xsType6, xsType6, xsTyp7, xsType8); // up to FXSample30;  
..

private
  FXSample = Set of TXSample;  
..

published
  property Sample: TXSample read FXSample  write FXSample; 
..

  //if Sample has a value of
  Sample := [xsType2, xsType4, xsType5, xsType6, xsTyp7];

how can i save/load the property of Sample?
i would like to save it in the database.
is it possible?

like image 259
XBasic3000 Avatar asked Mar 04 '12 08:03

XBasic3000


6 Answers

Provided your set will never exceed 32 possibilities (Ord(High(TXSample)) <= 31), then it is perfectly fine to typecast the set into an Integer and back:

type
  TXSamples = set of TXSample;
var 
  XSamples: TXSamples;
begin
  ValueToStoreInDB := Integer(XSamples);
  Integer(XSamples) := ValueReadFromDB;
end;

To be more specific: SizeOf(TXSamples) has to be precisely equal to SizeOf(StorageTypeForDB). Thus the following ranges apply for Ord(High(TXSample)) when typecasting TXSamples to:

  • Byte: Ord(High(TXSample)) < 8
  • Word: 8 <= Ord(High(TXSample)) < 16
  • Longword: 16 <= Ord(High(TXSample)) < 32
  • UInt64: 32 <= Ord(High(TXSample)) < 64
like image 168
NGLN Avatar answered Oct 21 '22 10:10

NGLN


Directly typecasting a set variable is not possible in Delphi, but internally Delphi stores the set as a byte-value. By using an untyped move, it is easy copied into an integer. Note that these functions only go up to a size of 32 (bounds of an integer). To increase the bounds, use Int64 instead.

function SetToInt(const aSet;const Size:integer):integer;
begin
  Result := 0;
  Move(aSet, Result, Size);
end;

procedure IntToSet(const Value:integer;var aSet;const Size:integer);
begin
  Move(Value, aSet, Size);
end;

Demo

type
  TMySet = set of (mssOne, mssTwo, mssThree, mssTwelve=12);
var
  mSet: TMySet;
  aValue:integer;
begin
  IntToSet(7,mSet,SizeOf(mSet));
  Include(mSet,mssTwelve);
  aValue := SetToInt(mSet, SizeOf(mSet));
end;
like image 38
Ramon Speets Avatar answered Oct 21 '22 09:10

Ramon Speets


Personally, I would convert the set to an integer and store it in the database as an INT field, like others suggested. @teran suggested using the TIntegerSet type, and here is my approach working on native integers using bit operations.

Note that you can use SampleInInteger() to determine whether a certain element from the enumeration is present in the integer mask generated by SampleSetToInteger().

Here's the code:

program Project1;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  System.SysUtils;

type
  { .: TXSample :. }
  TXSample = (xsType1 = 0, xsType2, xsType3, xsType4, xsType5,
    xsType6, xsType7, xsType8); // up to FXSample30;
  TXSampleSet = set of TXSample;

// Converts a TXSampleSet to an integer.
function SampleSetToInteger(const S: TXSampleSet): Integer;
var
  Sample: TXSample;
begin
  Result := 0;

  for Sample := Low(TXSample) to High(TXSample) do
    if (Sample in S) then
      Result := Result or (1 shl Ord(Sample));
end;

// Converts an integer to TXSampleSet.
function IntegerToSampleSet(const Int: Integer): TXSampleSet;
var
  I: Integer;
begin
  Result := [];

  for I := 0 to Ord(High(TXSample)) do
    if Int and (1 shl I) <> 0 then
      Result := Result + [TXSample(I)];
end;

// Checks if a TXSample is present in the integer.
function SampleInInteger(const S: TXSample; const Int: Integer): Boolean;
begin
  Result := Int and (1 shl Ord(S)) <> 0;
end;

var
  XSample, XSample1: TXSampleSet;
  Tmp: Integer;
begin
  XSample := [xsType2, xsType4, xsType5, xsType6, xsType7];
  XSample1 := [xsType1];
  Tmp := SampleSetToInteger(XSample);

  Writeln(Tmp);
  XSample1 := IntegerToSampleSet(Tmp);
  if (xsType5 in XSample1) then
    Writeln('Exists');
  if (SampleInInteger(xsType1, Tmp)) then
    Writeln('Exists in int');


  Readln;
end.
like image 24
Pateman Avatar answered Oct 21 '22 09:10

Pateman


A Delphi set is simply a collection of (possibly) related boolean flags. Each boolean flag corresponds to whether or not the matching ordinal value is in the set.

You could certainly pack a set into an integer value by representing the set as a bitset. Or you could create a textual representation of the set.

However, both of these options leave you with no tractable ability to query the database at the SQL level. For this reason I would advise you to represent each value in the set, i.e. each boolean flag, as a separate field (i.e. column) of the database table. This gives you the most powerful representation of the data.

like image 21
David Heffernan Avatar answered Oct 21 '22 08:10

David Heffernan


the easiest way to store set in database (as @DavidHeffernan mentioned in comment) is to convert your set to bit-mask. in int32 (integer) value you have 32 bits and can save set up to 32 fields; Delphi has TIntegerSet (see http://docwiki.embarcadero.com/Libraries/en/System.SysUtils.TIntegerSet) type defined in SysUtils. it is declared as:

TIntegerSet = set of 0..SizeOf(Integer) * 8 - 1;

so using it, it is simple to convert set to integer and back (just casting TIngeterSet to integer or vice versa);

bit-mask is also good option because it is only one INT field in your database table.

also you can create separate table in your DB to store set content (main table (id, ...), and setValuesTable (main_id, setElementValue)) (this option is good for using in db queries)

here is an example of using TIntegerSet:

program Project1;
{$APPTYPE CONSOLE}
uses System.SysUtils;

type
    TXSample = (xsType1, xsType2, xsType3, xsType4, xsType5, xsType6,  xsType7, xsType8);
    TSampleSet = set of TXSample;



    function SampleSetToInteger(ss : TSampleSet) : integer;
    var intset : TIntegerSet;
        s : TXSample;
    begin
        intSet := [];
        for s in ss do
            include(intSet, ord(s));

        result := integer(intSet);
    end;

    function IntegerToSampleSet(mask : integer) : TSampleSet;
    var intSet : TIntegerSet;
        b : byte;
    begin
        intSet := TIntegerSet(mask);
        result := [];
        for b in intSet do
            include(result, TXSample(b));
    end;

var xs : TSampleSet;
    mask : integer;
begin
    xs := [xsType2, xsType6 .. xsType8];

    mask := SampleSetToInteger(xs);     //integer mask
    xs := IntegerToSampleSet(mask);
end.
like image 36
teran Avatar answered Oct 21 '22 09:10

teran


Set variables can be saved successfully to a TStream descendant. Here's an example.

Just create a new vcl forms application, add two TButton components to it and fill in the OnClick events for each button as illustrated in the example below.

This was created in XE4 so the uses clause might be different for other versions of Delphi but that should be trivial to change by removing the namespace indicators before each unit in the uses clause. Saving a set type variable with articulated values is possible to a binary file and easily with Delphi. In other words,

Also suggested is taking a look at the TypInfo unit if you have the source or just using the functions provided which make dissecting Set types down to their text representation fairly simple though no example is provided here. That is suggested if you want to include saving to a config or ini file or in a persistence format that is text editable.

The one below is the simplest one that I know of. Looking at the binary output of a set type saved to a stream like the one below implies that it is saved in the smallest possible bitmapped representation for the set based on its size. The one below maps to one byte on disk (the value is 5) which means that each value must be mapped to a power of 2 (seThis = 1, seThat = 2, seTheOther = 4) just like manually created constant bitmasked values. The compiler likely enforces that it follows rules that forces set to retain their ordinality. This example was tested an works in Delphi XE4.

Hope that helps.

Brian Joseph Johns

unit Unit1;

interface

uses Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes,Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs,
     Vcl.StdCtrls;

type
  TSomeEnum = (seThis, seThat, seTheOther);
  TSomeEnumSet = set of TSomeEnum;

  TForm1 = class(TForm)
    Button1: TButton;
    Button2: TButton;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var

  Form1: TForm1;
  SomeSetVar: TSomeEnumSet;
  SomeBoolean: Boolean;
  SomeInt: Integer;

implementation

{$R *.dfm}


procedure TForm1.Button1Click(Sender: TObject);
begin
  SomeSetVar := [seThis, seTheOther];
  SomeBoolean := True;
  SomeInt := 31415;

  with TFileStream.Create('SetSave.bin',fmCreate or fmOpenWrite or fmShareCompat) do
  try
    Write(SomeSetVar,SizeOf(SomeSetVar));
    Write(SomeBoolean,SizeOf(Boolean));
    Write(SomeInt,SizeOf(Integer));
  finally
    Free;
  end;
  SomeSetVar := [];
  SomeInt := 0;
  SomeBoolean := False;

end;

procedure TForm1.Button2Click(Sender: TObject);
var
  ResponseStr: string;
begin
  with TFileStream.Create('SetSave.bin',fmOpenRead or fmShareCompat) do
  try
    Read(SomeSetVar,SizeOf(SomeSetVar));
    Read(SomeBoolean,SizeOf(Boolean));
    Read(SomeInt,SizeOf(Integer));
  finally
    Free;
  end;

  ResponseStr := 'SomeSetVar = ';
  if (seThis in SomeSetVar) then
    ResponseStr := ResponseStr + 'seThis ';

  if (seThat in SomeSetVar) then
    ResponseStr := ResponseStr + 'seThat ';

  if (seTheOther in SomeSetVar) then
    ResponseStr := ResponseStr + 'seTheOther ';

  ResponseStr := ResponseStr + ' SomeBoolean = ' + BoolToStr(SomeBoolean);

  ResponseStr := ResponseStr + ' SomeInt = ' + IntToStr(SomeInt);

  ShowMessage(ResponseStr);

end;

end.
like image 38
Shhhh Digital Avatar answered Oct 21 '22 10:10

Shhhh Digital