5

Similarly to any way to compare an integer variable to a list of integers in if statement but in Delphi, how can I test whether an integer variable is contained in a static "list" (loosely speaking - a set that is predefined) of integers (maybe a list of consts, or a list of explicit values) in an if statement?

Now I would do something like this:

if (x = 1) or
   (x = 2263) or
   (x = 263553) or
   (x = whatever_int) then
begin
  //do something;
end;

But I'm trying to avoid lenghty sequences of or conditions.

I'm using Delphi XE6, so if there's something new to achieve this in a clean way, please advise me.

Community
  • 1
  • 1
Bozzy
  • 541
  • 4
  • 21
  • How do you currently store this list of integers? How large is the list? Do you care about runtime efficiency more than clarity of code? – David Heffernan May 13 '15 at 10:49
  • 1
    Since you require the compaison to take place in an `if` statement (even marked in bold), I don't dare to suggest a `case` statement, which I would use. – Tom Brunberg May 13 '15 at 10:56
  • @TomBrunberg If that works depends on `whatever_int` being a const or not. – Stefan Glienke May 13 '15 at 11:00
  • @Stefan Yes, I should have mentioned that. – Tom Brunberg May 13 '15 at 11:03
  • @Stefan whatever_int is just a placeholder, not real code, it means "here the list continues". Maybe the question isn't so clear (sorry, I'm not native English speaker). I'm trying to solve the problem of comparing an integer variable to a known "list" (not a Delphi TList) of values (maybe they're consts, maybe they're explicit numbers). – Bozzy May 13 '15 at 12:13
  • BTW, I've edited my question to better explain I'm talking of comparing an integer to a certain number of known values. These values are NOT an array... maybe I have to put them in an array, but then this should be an answer ;-) My question is not a true duplicate of http://stackoverflow.com/questions/29697432/how-do-i-check-whether-an-array-contains-a-particular-value – Bozzy May 13 '15 at 12:19
  • 1
    Unfortunately I can't post as answer, but if, as you say, all values you check against are known at compiletime, you can use a case statement: case x of /n 1, 2263, 263553, declared_const, other_declared_const, 1048000:/n // do domething/n else // optionally do something else/n end;/n,,, The characters /n means new line for normal pascal formatting – Tom Brunberg May 13 '15 at 12:22
  • I did an edit to help demonstrate why this isn't a dupe of the post David pointed to. – GreenAsJade May 13 '15 at 12:41
  • @TomBrunberg Yes maybe this is the way to go. Thanks. David, what about deleting duplicate flag? – Bozzy May 13 '15 at 13:02
  • Perhaps you could make clear, once and for all, in what form you hold this collection of potential integers. Is it a list of values that is known at compile time? It would help if you would answer the three questions that I asked you in the very first comment. – David Heffernan May 13 '15 at 13:06
  • @DavidHeffernan yes, please read the question now that GreenAsJade edited it to be clearer. As for your questions: the list is now "stored" as simple values, as my example clearly demonstrates. The list can be from 2 to 10 or so values. Maybe some situations will require runtime efficiency, maybe others will not, so maybe a couple of suggestions for the two possible situations? Thanks. – Bozzy May 13 '15 at 13:07
  • Please will you do what I ask. Then I will be happy to reopen. You have failed to define "efficient". And I still have not seen a statement from you as to how many values you have. – David Heffernan May 13 '15 at 13:08
  • @DavidHeffernan we're just chasing ourselves, you know, this may happen on the web :-) please read my last (not this!) comment. – Bozzy May 13 '15 at 13:12
  • 1
    @TomBrunberg, would you please post your comment as an answer, if you like to? I'll upvote it. Thanks. – Bozzy May 13 '15 at 13:21
  • @Bozzy No point anymore that David already posted it, he also makes a few good points regarding terminology (efficient). Another time, thanks. – Tom Brunberg May 13 '15 at 13:34

4 Answers4

4

Your current approach is good. The compiler will produce efficient code. If efficiency is of the utmost importance, then you can choose the order of the comparisons so that the most common values are checked first. This requires knowledge of the distribution of your input data.

Another syntactical approach is the case statement. For instance:

case x of
1, 2263, 263553, whatever_int:
  // do other stuff
end;

This results in similar (in fact I suspect identical) code to the if statement. But it is perhaps more concise and easier to read. And in some scenarios the compiler is able to produce a jump table.

If efficiency is of the utmost importance to you then do make sure that you inspect the code that the compiler emits, for release compiler options, and perform profiling.

I do wonder what you really mean by efficient. In programming that term has a specific meaning relating to runtime performance. But I wonder if you are actually more concerned with writing clear and concise code, in which case efficient is the wrong term. The fact that your question talks about avoiding repeated or operators makes me doubt that efficiency is what matters to you.

On the other hand, if runtime performance is what matters then consider giving up on code clarity and implementing an indexed jump table. But don't ever optimise code without first identifying that it is a bottleneck, and profiling any changes you make.

Ken White
  • 123,280
  • 14
  • 225
  • 444
David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • 1
    The `case` [sometimes](http://stackoverflow.com/a/30016222/960757) beats even ordered `if`s... Don't ask me why, I haven't checked the produced code from that answer. – TLama May 13 '15 at 13:32
  • @TLama Compiler sometimes produces jump tables. I think. – David Heffernan May 13 '15 at 13:33
  • 2
    I often prefer the `case` version for tests such as this; in the checks I've done, the generated code is almost identical, and I think the readability and maintainability of the code is considerably improved. – Ken White May 13 '15 at 13:35
  • Sorry for my poor English, actually by efficient I mean clear, readable. So the case solution will probably be what I was searchin for. – Bozzy May 13 '15 at 13:36
2

You can make the code much cleaner to read by writing a little function:

//NOTE: This function is written in a way that would be compatible with any version of Delphi.
//Since you're using XE6, there are some improvement options available to you:
// E.g. You can make the function inline, use "for in", use a record helper, use generics
function ContainsInteger(const AValue: Integer; const AArray: array of Integer): Boolean;
var
  I: Integer;
begin
  Result := False;
  for I := Low(AArray) to High(AArray) do
  begin
    if (AArray[I] = AValue) then
    begin
      Result := True;
      Exit;
    end;
  end;
end;

Now you can call your function as follows:

if ContainsInteger(x, [1, 2263, 263553, whatever_int]) then
begin
  //do something;
end;

NOTE: This would be slower than a case statement as described in David's answer, but it has the potential to be significantly faster for longer lists of numbers.

If it's likely this function will be used frequently with longer lists:

  • You can specify a requirement that the input array be given in ascending order. (Behaviour would be undefined otherwise.)
  • And modify the function to perform a binary search.
  • Then for example, a list of 32 numbers would require at most 5 comparisons to find a match using binary search; whereas it would need an average of 16 for the case statement.

NB: As always when fine-tuning optimisations like this:

  1. Use profiling to confirm whether you need it here.
  2. Use profiling to confirm whether you get the benefit you expect.
Disillusioned
  • 14,635
  • 3
  • 43
  • 77
  • +1 Thanks for sharing this code! I've always missed this in Delphi 7. I would like to suggest to rename it to `IntInList(const whatToFind: Integer; const inThese ...)`. Shorter, more international. *(Programmers with less english knowledge can not always recall the word: contains.)* **PS:** Once we have made measurements about running 4096 cycles of `for-next` compared to a *recursive balanced tree* with "only 12 steps": the first was 20x faster. – SzakiLaci Nov 28 '22 at 18:30
1

given the use case, I would use TList<Integer>, and its methods:

  • procedure AddRange(const Values: array of T);
  • Contains(const Value: T): Boolean;
Didier Cabalé
  • 385
  • 3
  • 10
0

Why not just simple:

x : integer;
begin
  if x in [1,2,5] then
  begin
    showmessage('x in 1,2,5');
  end;
end;
Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
Maui71
  • 1
  • 2
    The `in` operator can only be used for elements from zero to 255. The question exemplifies with values larger than that. – LU RD Jan 17 '20 at 16:34
  • Please explain how this works and why it helps. Since somebody commented what they consider a shortcoming of your solution you might want to show the result make plausible that it works. – Yunnosch Jan 19 '20 at 07:22