9

From a certain point I got tired of writing set conditions (and, or), because for more conditions or longer variable names it begins to be clumsy and annoying to write all over again. So I started writing helpers so I could write ASet.ContainsOne([ceValue1, ceValue2]) instead of (ceValue1 in ASet) or (ceValue2 in ASet).

type
  TCustomEnum = (ceValue1, ceValue2, ceValue3);
  TCustomSet = set of TCustomEnum;
  TCustomSetHelper = record helper for TCustomSet 
    function ContainsOne(ASet: TCustomSet): Boolean;
    function ContainsAll(ASet: TCustomSet): Boolean;
  end;

implementation

function TCustomSetHelper.ContainsOne(ASet: TCustomSet): Boolean;
var
  lValue : TCustomEnum;
begin
  for lValue in ASet do
  begin
    if lValue in Self then
      Exit(True);
  end;
  Result := False;
end;

function TCustomSetHelper.ContainsAll(ASet: TCustomSet): Boolean;
var
  lValue : TCustomEnum;
begin
  Result := True;
  for lValue in ASet do
  begin
    if not (lValue in Self) then
      Exit(False);
  end;
end;

Unfortunately, this is not the most effective solution and it's against the DRY principle. To my surprise, I didn't find anyone ever dealing with the same problem, so I wonder if there is any better (generic) solution?

Triber
  • 1,525
  • 2
  • 21
  • 38

2 Answers2

16

The set operators help you implement these functions

For ContainsOne we use the * operator which is the set intersection operator.

function TCustomSetHelper.ContainsOne(ASet: TCustomSet): Boolean;
begin
  Result := ASet * Self <> [];
end;

For ContainsAll we would use <= which is the subset operator.

function TCustomSetHelper.ContainsAll(ASet: TCustomSet): Boolean;
begin
  Result := ASet <= Self;
end;

Given how simple these expressions are, I question whether or not you need the helper type at all.

The documentation gives the full list of available set operators.

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • Thanks you and @MBo, I somehow missed that, I would expect at least a link to that table here [Structured Types - Sets](http://docwiki.embarcadero.com/RADStudio/Tokyo/en/Structured_Types_(Delphi)#Sets). You are right that in this case the helpers are useless. – Triber Sep 19 '18 at 13:51
  • There is indeed a link to that topic. Read all the way to the bottom. – David Heffernan Sep 19 '18 at 13:56
5

You can use the set intersection operator

For ContainsOne analog check if intersection is not empty set, for ContainsAll check that intersection coincides with argument set

type
  TCustomEnum = (ceValue1, ceValue2, ceValue3);
  TCustomSet = set of TCustomEnum;
var
  ASet: TCustomSet;
begin
  ASet := [ceValue1, ceValue3];

  if ([ceValue1, ceValue2] *  ASet) <> [] then
     Memo1.Lines.Add('Somebody here');

  if ([ceValue1, ceValue3] *  ASet) = [ceValue1, ceValue3] then
     Memo1.Lines.Add('All are in home');
MBo
  • 77,366
  • 5
  • 53
  • 86
  • Wrong to implement `ContainsAll` like that. You should use the subset operator as my answer demonstrates. – David Heffernan Sep 19 '18 at 13:46
  • @David Heffernan What's wrong? We could interfere in editing, but I don't see in my corrections wrong implementation of ContainsAll. Just checked in Delphi. – MBo Sep 19 '18 at 13:52
  • I mean, it works, but it's inelegant to write `s1 * s2 = s1` rather than `s1 <= s2`. I was going to make my edits to your answer since it is essentially sound, but then we started crossing edits, so I wrote another one. – David Heffernan Sep 19 '18 at 13:56