5

I know, I can write

if C in ['#', ';'] then ...

if C is an AnsiChar.

But this

function CheckValid(C: Char; const Invalid: array of Char; OtherParams: TMyParams): Boolean;
begin
    Result := C in Invalid;    // <-- Error because Invalid is an array not a set
    //maybe other tests...
    //Result := Result and OtherTestsOn(OtherParams);
end;

yields E2015: Operator not applicable to this operand type.

Is there an easy way to check if a character is contained in an array of characters (other than iterate through the array)?

Alois Heimer
  • 1,772
  • 1
  • 18
  • 40
  • `['#', ':']` is a _set of Char_, not _array of char_, and won't work correctly for Unicode versions of Delphi (2009+), Use [CharInSet](http://docwiki.embarcadero.com/Libraries/Seattle/en/System.SysUtils.CharInSet) instead, but check out the caveats in http://stackoverflow.com/questions/4237339/charinset-doesnt-work-with-non-english-letters – Gerry Coll Feb 23 '16 at 20:39
  • @GerryColl You are right, I corrected the question accordingly. Thanks for your hint. My code is only an example. But in this particular case I am concerned about `array of char`. – Alois Heimer Feb 23 '16 at 20:49
  • You cannot use the `in` operator with arrays, only with Sets. – Remy Lebeau Feb 23 '16 at 21:14
  • Clarification: `CheckValid()` is not meant as an try to write an `IsCharInArray()`-function, but should only serve as an example for an use case. – Alois Heimer Feb 25 '16 at 11:05

2 Answers2

7

I know you don't want to, but this is one of those cases where iterating through the array really is your best option, for performance reasons:

function CheckValid(C: Char; const Invalid: array of Char): Boolean;
var
  I: Integer;
begin
  Result := False;
  for I := Low(Invalid) to High(Invalid) do begin
    if Invalid[I] = C then begin
      Result = True;
      Exit;
    end;
  end;
end;

Or:

function CheckValid(C: Char; const Invalid: array of Char): Boolean;
var
  Ch: Char;
begin
  Result := False;
  for Ch in Invalid do begin
    if Ch = C then begin
      Result = True;
      Exit;
    end;
  end;
end;

Converting the input data to strings just to search it can cause huge performance bottlenecks, especially if the function is called often, such as in a loop.

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • Shouldn't `CheckValid()` return `False` if the array `Invalid` contains `C`? Alternatively, you could rename the function to a more generic name, e.g. `IsCharInArray()`. – René Hoffmann Feb 24 '16 at 14:47
  • @RenéHoffmann: it depends on how the function is being used. It could return True if C is expected to be in the array and is illegal if not. Or it could return False if C is not expected to be in the array and is illegal if it is. – Remy Lebeau Feb 24 '16 at 16:05
  • So, the functions name is ambigous on its purpose. – René Hoffmann Feb 25 '16 at 08:12
  • @RenéHoffmann My question was not about how to write a function `IsCharInArray()` but about how to quickly check if a char is in an array in the case that I am not willing to define such a function. Therefore I originally choose that name. – Alois Heimer Feb 25 '16 at 10:58
  • @RemyLebeau I agree with René that nameing and return values are confusing. I too would suggest to rename `CheckValid()` in `IsCharInArray()` – Alois Heimer Feb 25 '16 at 11:01
0

If one tries to avoid iterating through the array and if speed is of no concern then IndexOfAny can be helpful:

function CheckValid(C: Char; const Invalid: array of Char; OtherParams: TMyParams): Boolean;
begin
    Result := string(C).IndexOfAny(Invalid) >= 0;
    //maybe other test...
    //....
end;

From the Delphi documentation:

[IndexOfAny r]eturns an integer indicating the position of the first given character found in the 0-based string. [It returns -1 if the character is not found.]

If speed is of concern, this should be avoided as @RemyLebeau explains in the comments:

Casting C to String to call IndexOfAny() will create 1 temp String. [...] if CheckValid() is called often, those conversions can be a BIG performance bottleneck, not to mention a waste of memory.

In this case @RemyLebeau's answer is the better solution.

Alois Heimer
  • 1,772
  • 1
  • 18
  • 40
  • 2
    Try `Pos(C, Invalid) > 0` – Abelisto Feb 23 '16 at 20:30
  • @Abelisto This seems to work too. Why? Is array of Char casted to string automatically? – Alois Heimer Feb 23 '16 at 20:38
  • Yes, arrays of char implicit casted to strings. – Abelisto Feb 23 '16 at 20:41
  • 3
    Passing `C` and `Invalid` to `Pos()` will create 2 temp `String`s. Casting `C` to `String` to call `IndexOfAny()` will create 1 temp `String`. Either way, if `CheckValid()` is called often, those conversions can be a *BIG* performance bottleneck, not to mention a waste of memory. You are best off simply looping through the array directly, looking for an element that matches the `C` value. No conversions will be performed, and the code will execute faster. – Remy Lebeau Feb 23 '16 at 21:15
  • @RemyLebeau I appreaciate your comment and accepted *your* answer. I will leave my modified answer anyway, because I think sometimes it can be useful to have a single-line-code for this. I hope it is okay that I cited your comment in my answer. – Alois Heimer Feb 24 '16 at 11:02