Lazarus

Programming => General => Topic started by: jamie on September 20, 2020, 09:28:06 pm

Title: Is there a conditional Set that uses a boolean in a single function call ?
Post by: jamie on September 20, 2020, 09:28:06 pm
You have Include and Exclude and all the inline stuff..

I was looking for a ready made conditional set.

For example..

SetSet(TheSetVariable, TheValueMember, True/False);

The idea is to either Set a member in a set or unset it with a single call.

Currently I have to do a Boolean test and branch from that to choose either Include or Exclude, but I have values coming from CheckBoxes, config files etc and it would be nice to do this in a single shot where it would either Set a member or remove it in a single step.

 How do I do that?
Title: Re: Is there a conditional Set that uses a boolean in a single function call ?
Post by: Blaazen on September 20, 2020, 09:54:53 pm
See my old thread https://forum.lazarus.freepascal.org/index.php/topic,24522.msg147681.html#msg147681 (https://forum.lazarus.freepascal.org/index.php/topic,24522.msg147681.html#msg147681) and especially BeniBela's answer :D.
Title: Re: Is there a conditional Set that uses a boolean in a single function call ?
Post by: jamie on September 20, 2020, 10:30:07 pm
I was hoping for a non typed way of doing this without all the casting etc.

There  should be an intrinsic way to do this conditionally and the compiler will adjust the type needed.

For the time being I placed a procedure In the Class I am creating that does this .
Code: Pascal  [Select][+][-]
  1. Function TExCanvas.SetCanvasMode(AValue:TExCanvasMode; Acondition:Boolean):TExCanvasModes;
  2. Begin
  3.   Case ACondition of
  4.    False :Exclude(FCanvasModes,AValue);
  5.    True  :InClude(FCanvasModes,AValue);
  6.   end;
  7.  Result := fcanvasModes;
  8. end;                  
  9.  
Title: Re: Is there a conditional Set that uses a boolean in a single function call ?
Post by: MarkMLl on September 21, 2020, 08:04:22 am
The casts are one of the problems, but it should be possible to work around that using a generic (non-class) function... do those inline properly? C/C++ programmers would of course use a macro.

More of a problem is the conditionals which can cause a pipeline stall, hence the traditional use of a sequence of and/or/nots.

It can of course be simpler to first clear the bit, then to toggle it using xor.

MarkMLl

p.s. Happy Autumnal Equinox, everybody :-)
Title: Re: Is there a conditional Set that uses a boolean in a single function call ?
Post by: avk on September 21, 2020, 09:27:33 am
Something like this?
Code: Pascal  [Select][+][-]
  1. ...
  2.  {$mode delphi}
  3. ...
  4. procedure TurnSetItem<TSet, TItem>(var aSet: TSet; aItem: TItem; aOn: Boolean); inline;
  5. ...
  6. implementation
  7. ...
  8. procedure TurnSetItem<TSet, TItem>(var aSet: TSet; aItem: TItem; aOn: Boolean);
  9. begin
  10.   if aOn then
  11.     aSet += [aItem]
  12.   else
  13.     aSet -= [aItem];
  14. end;
  15.  
Title: Re: Is there a conditional Set that uses a boolean in a single function call ?
Post by: egsuh on September 21, 2020, 09:37:20 am
AFAIK, Sets of Pascal can contain only ordinal types, within 0..255. So if any value may have minus (-) values, then sets do not operate correctly.  I think you may consider using TList or TFPGMap, etc.
Title: Re: Is there a conditional Set that uses a boolean in a single function call ?
Post by: winni on September 21, 2020, 11:11:54 am
Hi!

Good old cast solution:

Code: Pascal  [Select][+][-]
  1. uses .......,UITypes;
  2. ......
  3. type setOfByte = set of byte;
  4.  
  5. procedure TurnSetItem (var setofSomething; var Something; aOn : boolean );
  6. begin
  7.   if aOn then
  8.     setOfByte(setOfSomething) += [byte(Something)]
  9.   else
  10.     setOfByte(setOfSomething) -= [byte(SomeThing)];
  11. end;
  12.  
  13. procedure TForm1.Button4Click(Sender: TObject);
  14. var buttons:  TMsgDlgButtons;
  15. B : TMsgDlgBtn;
  16. S: string='';
  17. SItem : string;
  18. begin
  19. buttons := [mbYes, mbNo, mbCancel];
  20. B := mbCancel;
  21. TurnSetitem (buttons, B,false);
  22. for B in buttons do
  23.    begin
  24.    writeStr(sItem,B);
  25.    s := s+SItem + ' ';
  26.    end;
  27. showMessage (s);
  28. end;
  29.  

Winni
Title: Re: Is there a conditional Set that uses a boolean in a single function call ?
Post by: marcov on September 21, 2020, 12:20:50 pm
Winni: I don't think that is endian safe.
Title: Re: Is there a conditional Set that uses a boolean in a single function call ?
Post by: Thaddy on September 21, 2020, 12:30:03 pm
Set operators can be overriden.  I believe I already posted such a solution.
Title: Re: Is there a conditional Set that uses a boolean in a single function call ?
Post by: winni on September 21, 2020, 01:46:40 pm
Winni: I don't think that is endian safe.

Hi!

I don't see the endian trap.
Can you show it to me?

Winni
Title: Re: Is there a conditional Set that uses a boolean in a single function call ?
Post by: avk on September 21, 2020, 03:26:17 pm
AFAIK, Sets of Pascal can contain only ordinal types, within 0..255. So if any value may have minus (-) values, then sets do not operate correctly.

Now these limitations can be easily overcome. For example this code:
Code: Pascal  [Select][+][-]
  1. program test_set;
  2. {$mode delphi}
  3. uses
  4.   LGUtils;
  5. type
  6.   TRange = -150..150;
  7. var
  8.   s: TGSet<TRange>;
  9.   I: TRange;
  10. begin
  11.   for I in TRange do
  12.     if I mod 20 = 0 then
  13.       s.Include(I);
  14.   for I in TRange do
  15.     if (I mod 20 = 0) and not(I in s) then
  16.       WriteLn('Ololo!');
  17.   for I in s do
  18.     WriteLn(I);
  19. end.
  20.  
prints
Code: Text  [Select][+][-]
  1. -140
  2. -120
  3. -100
  4. -80
  5. -60
  6. -40
  7. -20
  8. 0
  9. 20
  10. 40
  11. 60
  12. 80
  13. 100
  14. 120
  15. 140
  16.  
Title: Re: Is there a conditional Set that uses a boolean in a single function call ?
Post by: Blaazen on September 21, 2020, 07:54:43 pm
I found quite nice way to switch any set of enumeration:
Code: Pascal  [Select][+][-]
  1. unit Unit1;
  2. {$mode objfpc}{$H+}
  3.  
  4. interface
  5.  
  6. uses
  7.   Classes, SysUtils, Forms, Controls, Graphics, Dialogs, StdCtrls;
  8.  
  9. type
  10.    TTestEnum = (te0, te1, te2, te3, te4, te5, te6, te7, te8, te9, te10, te11, te12, te13, te14, te15,
  11.                 te16, te17, te18, te19, te20, te21, te22, te23, te24, te25, te26, te27, te28, te29, te30, te31);
  12.   TTestSet = set of TTestEnum;
  13.  
  14.   { TForm1 }
  15.   TForm1 = class(TForm)
  16.     Button1: TButton;
  17.     procedure Button1Click(Sender: TObject);
  18.   private
  19.  
  20.   public
  21.  
  22.   end;
  23.  
  24. procedure SwitchSet(var ASet; const AItem; AOn: Boolean);
  25.  
  26. var
  27.   Form1: TForm1;
  28.  
  29. implementation
  30.  
  31. procedure SwitchSet(var ASet; const AItem; AOn: Boolean);
  32. type
  33.   TDummyEnum =
  34.     (de0, de1, de2, de3, de4, de5, de6, de7, de8, de9, de10, de11, de12, de13, de14, de15,
  35.      de16, de17, de18, de19, de20, de21, de22, de23, de24, de25, de26, de27, de28, de29, de30, de31);
  36.   TDummySet = set of TDummyEnum;
  37. begin
  38.   if AOn
  39.     then include(TDummySet(ASet), TDummyEnum(AItem))
  40.     else exclude(TDummySet(ASet), TDummyEnum(AItem));
  41. end;
  42.  
  43. {$R *.lfm}
  44.  
  45. { TForm1 }
  46.  
  47. procedure TForm1.Button1Click(Sender: TObject);
  48. var aEnum: TTestEnum;
  49.     aSet: TTestSet;
  50. begin
  51.   aSet:=[];
  52.   for aEnum:=low(TTestEnum) to high(TTestEnum) do
  53.     begin
  54.       SwitchSet(aSet, aEnum, True);
  55.       writeln('IsThere T: ', ord(aEnum), ' ', boolToStr(aEnum in aSet, 'T', 'F'));
  56.       SwitchSet(aSet, aEnum, False);
  57.       writeln('IsThere F: ', ord(aEnum), ' ', boolToStr(aEnum in aSet, 'T', 'F'));
  58.     end;
  59. end;
  60.  
  61. end.
Just start a new project, put there one button and assign OnClick event.
All is in procedure SwitchSet.
I have no clue about endianess. It would be nice if something like this (endian safe) would be part of FPC.
Title: Re: Is there a conditional Set that uses a boolean in a single function call ?
Post by: Blaazen on September 21, 2020, 08:12:36 pm
Unfortunately this does not allow to pass a enum, it must be vatiable.
(i.e. SwitchSet(aSet, te1, True); does not work.)
Title: Re: Is there a conditional Set that uses a boolean in a single function call ?
Post by: MarkMLl on September 21, 2020, 08:27:51 pm
I think there's a fundamental missing operation here, which is being able to multiply an arbitrary set by a boolean to give the potential of clearing it without having to use conditionals (i.e. interrupt the flow of control).

If that operation worked, then xor (symmetric difference) could be used to set/reset an element without having to mess around with casts and without interrupting execution flow.

MarkMLl
Title: Re: Is there a conditional Set that uses a boolean in a single function call ?
Post by: Blaazen on September 21, 2020, 08:52:07 pm
BTW, why compiler 3.3.1 does not complain?
Code: Pascal  [Select][+][-]
  1.   procedure Any(const Anything);
  2.   procedure DoIt;
  3.   ...
  4. implementation
  5.  
  6. procedure Any(const Anything);
  7. begin
  8.  
  9. end;
  10.  
  11. procedure DoIt;
  12. begin
  13.   Any([fsItalic, fsBold]);  //unit1.pas(31,25) Error: Variable identifier expected
  14.   Any(Cardinal([fsItalic, fsBold]));  //ok
  15. end;
Title: Re: Is there a conditional Set that uses a boolean in a single function call ?
Post by: 440bx on September 21, 2020, 09:49:23 pm
BTW, why compiler 3.3.1 does not complain?
Code: Pascal  [Select][+][-]
  1.   procedure Any(const Anything);
  2.   procedure DoIt;
  3.   ...
  4. implementation
  5.  
  6. procedure Any(const Anything);
  7. begin
  8.  
  9. end;
  10.  
  11. procedure DoIt;
  12. begin
  13.   Any([fsItalic, fsBold]);  //unit1.pas(31,25) Error: Variable identifier expected
  14.   Any(Cardinal([fsItalic, fsBold]));  //ok
  15. end;
This a guess based on the way I've seen FPC treat parameters.  The procedure "Any" uses an untyped parameter ("Anything"), because of it being untyped, the compiler will pass a pointer to whatever the parameter "Anything" happens to be.  In the procedure DoIt, in the first statement, the compiler does _not_ create a temporary set it can take the address of and pass to "Any".  In the second statement, the result isn't a set, just an ordinal number and the compiler can easily put that on the stack and pass the address on the stack where that _number_ (not set) is located.

Put a different way, I've observed that FPC does _not_ create temporary sets on the fly.  That part is _not_ a guess, it's a fact.  That fact is where the above guess comes from.

Hopefully, one of the developers can confirm if the above guess is correct or not.

Title: Re: Is there a conditional Set that uses a boolean in a single function call ?
Post by: PascalDragon on September 21, 2020, 10:32:02 pm
BTW, why compiler 3.3.1 does not complain?

At least the following fails with trunk on x86_64-win64 with the error you mentioned:

Code: Pascal  [Select][+][-]
  1. program tformal;
  2.  
  3. {$mode objfpc}
  4.  
  5. type
  6.   TFontStyle = (
  7.     fsItalic,
  8.     fsBold,
  9.     fsUnderlined,
  10.     fsStrikeOut
  11.   );
  12.   TFontStyles = set of TFontStyle;
  13.  
  14. procedure Any(const Anything);
  15. begin
  16.  
  17. end;
  18.  
  19. procedure DoIt;
  20. begin
  21.   Any([fsItalic, fsBold]);  //unit1.pas(31,25) Error: Variable identifier expected
  22.   Any(Cardinal([fsItalic, fsBold]));  //ok
  23. end;
  24.  
  25. begin
  26.  
  27. end.

Does this fail for you as well? Do you have a full, self contained example?

I have no clue about endianess. It would be nice if something like this (endian safe) would be part of FPC.

This should be endian safe, cause you're casting it to set type. If you'd work with bit offsets yourself you'd have to pay attention like the code for StringToSet (https://svn.freepascal.org/cgi-bin/viewvc.cgi/trunk/rtl/objpas/typinfo.pp?view=markup#l1355) does.

I think there's a fundamental missing operation here, which is being able to multiply an arbitrary set by a boolean to give the potential of clearing it without having to use conditionals (i.e. interrupt the flow of control).

If that operation worked, then xor (symmetric difference) could be used to set/reset an element without having to mess around with casts and without interrupting execution flow.

I don't get what you'd try to achieve here. Do you have a pseudo code example? (Also symmetric difference in Pascal is ><, not xor)
Title: Re: Is there a conditional Set that uses a boolean in a single function call ?
Post by: jamie on September 21, 2020, 10:48:49 pm
This may have strayed a little but the initial idea was to be able to set a member of a SET via a condition in a simple call statement..

For example :

  ConditionSet(TheSet. A_Member_of_the_set, A_Boolean_condition);

  So the idea is to either Include a member in the set if true or Exclude a member in the set if false in a single term.

  It would make it so much easier to set the member from a Boolean source that is only known at runtime.

 I think an intrinsic feature could be made here for efficiency instead of trying to hack one that will work with any set.


Title: Re: Is there a conditional Set that uses a boolean in a single function call ?
Post by: Blaazen on September 21, 2020, 10:54:37 pm
Code: Pascal  [Select][+][-]
  1. program tformal;
  2. {$mode objfpc}
  3.  
  4. type
  5.   TFontStyle = (
  6.     fsItalic,
  7.     fsBold,
  8.     fsUnderlined,
  9.     fsStrikeOut
  10.   );
  11.   TFontStyles = set of TFontStyle;
  12.  
  13. var aFS: TFontStyles;
  14.  
  15. procedure Any(const Anything);
  16. begin
  17.   aFS:=aFS+TFontStyles(Anything);
  18. end;
  19.  
  20. procedure DoIt;
  21. begin
  22.   //Any([fsItalic, fsBold]);  //unit1.pas(31,25) Error: Variable identifier expected
  23.   Any(Cardinal([fsItalic, fsBold]));  //ok
  24. end;
  25.  
  26. begin
  27.   writeln(Cardinal(aFS));
  28.   DoIt;
  29.   writeln(Cardinal(aFS));
  30. end.
Output:
Code: [Select]
[v1@nb-msi testset]$ fpc test.pas
Free Pascal Compiler version 3.3.1 [2020/09/04] for x86_64
Copyright (c) 1993-2020 by Florian Klaempfl and others
Target OS: Linux for x86-64
Compiling test.pas
Linking test
29 lines compiled, 0.2 sec
[v1@nb-msi testset]$ ./test
0
3
[v1@nb-msi testset]$
Title: Re: Is there a conditional Set that uses a boolean in a single function call ?
Post by: winni on September 22, 2020, 01:10:44 am
Hi

It is too much to call the adding routine with a typecast.
Let that routine do the typecasting.

I have my above example adapted to your FontStyle(s) example:


Code: Pascal  [Select][+][-]
  1. type setOfByte = set of byte;
  2.  
  3. procedure TurnSetItem (var setofSomething; const Something; aOn : boolean );
  4. begin
  5.   if aOn then
  6.     setOfByte(setOfSomething) += [byte(Something)]
  7.   else
  8.     setOfByte(setOfSomething) -= [byte(SomeThing)];
  9. end;
  10.  
  11. procedure TForm1.Button4Click(Sender: TObject);
  12. var fst:  TFontStyles;
  13.     oneF : TFontStyle;
  14. S: string='';
  15. SItem : string;
  16. begin
  17. oneF := fsBold;
  18. fst := [fsItalic, fsUnderline];
  19. TurnSetitem (fst, oneF,true);
  20. for oneF in fst do
  21.    begin
  22.    writeStr(sItem,oneF);
  23.    s := s+SItem + ' ';
  24.    end;
  25. showMessage (s);
  26. end;                  

Winni
Title: Re: Is there a conditional Set that uses a boolean in a single function call ?
Post by: ASerge on September 22, 2020, 01:35:37 am
Code: Pascal  [Select][+][-]
  1. ...
  2. procedure SwitchSet(var ASet; const AItem; AOn: Boolean);
  3. type
  4.   TDummyEnum = (de0, de1, de2, de3, de4, de5, de6, de7, de8, de9, de10, de11, de12, de13, de14, de15,
  5.      de16, de17, de18, de19, de20, de21, de22, de23, de24, de25, de26, de27, de28, de29, de30, de31);
  6.   TDummySet = set of TDummyEnum;
  7. begin
  8.   if AOn then
  9.     Include(TDummySet(ASet), TDummyEnum(AItem))
  10.   else
  11.     Exclude(TDummySet(ASet), TDummyEnum(AItem));
  12. end;
  13. ...
In my opinion, the best solution. Fast. The only thing is, I would replace the long TDummyEnum with TDummyEnum = 0..31;
Title: Re: Is there a conditional Set that uses a boolean in a single function call ?
Post by: jamie on September 22, 2020, 03:02:17 am
This will also not work with Constants.
I made an example using that example to show what I mean.
Code: Pascal  [Select][+][-]
  1. Type
  2.   Test = (one, two);
  3.   Dummy = 1..31;
  4.   DummySet = Set of Dummy;
  5.   Procedure SetDummy(Var TheSet; theValue:Byte; theState:Boolean) inline;
  6.    begin
  7.      Case TheState of
  8.       False :Exclude(DummySet(TheSet), Dummy(TheValue));
  9.       True  :Include(DummySet(TheSet), Dummy(TheValue));
  10.    end;
  11.   End;
  12. var
  13.   S:DummySet;
  14. begin
  15.    SetDummy(S, Ord(One),True);
  16.    SetDummy(S, 4, False);
  17. end;                                        
  18.  

 As you can see I was able to use a constant this way however, this leads to a problem because now the "One" in the enum could be coming from any ENUM that has  a "one" in it, In otherwords it may not belong to the SET that is being reference to.

 I am sure the compiler test to ensure the ENUM belongs to the set under normal conditions when compiling for constants.

 Like I said, this needs to be an intrinsic function so the compiler can do a check for correctness of ENUMS belonging to the set.
Title: Re: Is there a conditional Set that uses a boolean in a single function call ?
Post by: winni on September 22, 2020, 03:14:34 am
Hi!

I knew that my solution was too simple for this forum.

K I S S

Winni
Title: Re: Is there a conditional Set that uses a boolean in a single function call ?
Post by: PascalDragon on September 22, 2020, 09:26:25 am
Code: Pascal  [Select][+][-]
  1. program tformal;
  2. {$mode objfpc}
  3.  
  4. type
  5.   TFontStyle = (
  6.     fsItalic,
  7.     fsBold,
  8.     fsUnderlined,
  9.     fsStrikeOut
  10.   );
  11.   TFontStyles = set of TFontStyle;
  12.  
  13. var aFS: TFontStyles;
  14.  
  15. procedure Any(const Anything);
  16. begin
  17.   aFS:=aFS+TFontStyles(Anything);
  18. end;
  19.  
  20. procedure DoIt;
  21. begin
  22.   //Any([fsItalic, fsBold]);  //unit1.pas(31,25) Error: Variable identifier expected
  23.   Any(Cardinal([fsItalic, fsBold]));  //ok
  24. end;
  25.  
  26. begin
  27.   writeln(Cardinal(aFS));
  28.   DoIt;
  29.   writeln(Cardinal(aFS));
  30. end.
Output:
Code: [Select]
[v1@nb-msi testset]$ fpc test.pas
Free Pascal Compiler version 3.3.1 [2020/09/04] for x86_64
Copyright (c) 1993-2020 by Florian Klaempfl and others
Target OS: Linux for x86-64
Compiling test.pas
Linking test
29 lines compiled, 0.2 sec
[v1@nb-msi testset]$ ./test
0
3
[v1@nb-msi testset]$

The code you posted above compiles with 3.0.4, 3.2.0 and 3.3.1 and if I enable the other call to Any it fails in all three. So it's not a regression compared to 3.0.4 though of course one might argue whether it's correct that the second call works while the first does not.

Feel free to open a bug report maybe Jonas or Florian know why it behaves differently or if it's indeed supposed to be this way.
Title: Re: Is there a conditional Set that uses a boolean in a single function call ?
Post by: Blaazen on September 22, 2020, 02:06:23 pm
I opened https://bugs.freepascal.org/view.php?id=37796 (https://bugs.freepascal.org/view.php?id=37796).
TinyPortal © 2005-2018