Michael Vincent
Michael Vincent

Reputation: 1660

Delphi function for testing string present at position x

In a recent application that involved receiving strings over a serial link I found myself writing code like:

if (pos('needle', haystack) = 1) then ...

in order to check if a particular substring is at the begging of a string.

It struck me that the pos function is not ideal for this as it has no idea which location I'm looking for the substring to be in.

Is there a good function that does this?

Is there a more generalised function like IsSubStringAt(needle, haystack, position)?

I did think of using something like this:

function IsSubstrAt(const needle, haystack: string; position: Integer): Boolean;
var
  ii: integer;
begin
  result := true;
  for ii := 1 to length(needle) de begin
    if (haystack[poition + ii -1] <> needle[ii]) then begin
      result := false;
      break;
    end;
  end;
end;

with some error checking.

I was hoping to find a ready rolled answer.

Upvotes: 2

Views: 3722

Answers (4)

adlabac
adlabac

Reputation: 416

Here is a very fast way to do this, written in assembly language. I made it by modifying the original Delphi's Pos function:

Function PosS (Substr:string; S:string; Position:integer) : integer;
  Asm
    TEST    EAX,EAX
    JE      @@NoWork

    TEST    EDX,EDX
    JE      @@StringEmpty

    PUSH    EBX
    PUSH    ESI
    PUSH    EDI

    MOV     ESI, EAX                         //  Pointer to Substr
    MOV     EDI, EDX                         //  Pointer to S
    MOV     EBX, ECX                         //  Position
    DEC     EBX

    MOV     ECX, [EDI-4]                     // Length (S)
    SUB     ECX, EBX

    PUSH    EDI
    ADD     EDI, EBX

    MOV     EDX, [ESI-4]                     // Length (Substr)

    DEC     EDX
    JS      @@Fail
    MOV     AL, [ESI]
    INC     ESI

    SUB     ECX, EDX                         // = Length (S) - Length (Substr) + 1
    JLE     @@Fail
@@Loop:
    REPNE   SCASB
    JNE     @@Fail
    MOV     EBX, ECX
    PUSH    ESI
    PUSH    EDI

    MOV     ECX, EDX
    REPE    CMPSB
    POP     EDI
    POP     ESI
    JE      @@Found
    MOV     ECX, EBX
    JMP     @@Loop

@@Fail:
    POP     EDX
    XOR     EAX, EAX
    JMP     @@Exit

@@StringEmpty:
    XOR     EAX, EAX
    JMP     @@NoWork

@@Found:
    POP     EDX
    MOV     EAX, EDI
    SUB     EAX, EDX
@@Exit:
    POP     EDI
    POP     ESI
    POP     EBX
@@NoWork:
  End;

Upvotes: 2

Deltics
Deltics

Reputation: 23036

You could use CompareMem() to directly compare the string contents:

  function IsSubStringAt(const aNeedle, aHaystack: String; aPosition: Integer): Boolean;
  var
    needleLen: Integer;
  begin
    needleLen := Length(aNeedle);
    result    := (needleLen + aPosition - 1) <= Length(aHaystack);

    if result then
      result := CompareMem(Pointer(aNeedle), @aHaystack[aPosition], needleLen * sizeof(Char));
  end;

Note that we short-circuit the need to do any comparison if the haystack is too short to contain the needle at the specified position.

Using the CompareMem() API ensures that the implementation is portable and will also work with a Unicode String type (should you ever migrate or use this code in a Unicode version of Delphi) as long as the size of the Char type is taken into account, as is done here.

This approach however assumes that strings have already been normalised to any extent required such that the byte content of the strings are directly comparable.

Upvotes: 3

David Heffernan
David Heffernan

Reputation: 612954

Since you only want to look at one position, you can just form the substring and test that. Like this:

function IsSubStringAt(const needle, haystack: string; position: Integer): Boolean;
var
  substr: string;
begin
  substr := Copy(haystack, position, Length(needle));
  Result := substr = needle;
end;

If performance was really critical then then you would want to perform the comparison in-place without creating a copy, and thereby performing heap allocation. You could use AnsiStrLComp for this.

function IsSubStringAt(const needle, haystack: string; position: Integer): Boolean;
begin
  if Length(haystack) - position + 1 >= Length(needle) then begin
    Result := AnsiStrLComp(
      PChar(needle), 
      PChar(haystack) + position - 1, 
      Length(needle)
    ) = 0;
  end else begin
    Result := False;
  end;
end;

If you want to check without senstivity to case, replace = with SameText in the first version, and replace AnsiStrLComp with AnsiStrLIComp in the second version.

Upvotes: 5

Uwe Raabe
Uwe Raabe

Reputation: 47704

Since XE7 you can use (assuming position is 1-based):

function IsSubStringAt(const needle, haystack: string; position: Integer): Boolean;
begin
  result := string.Compare(hayStack, position-1, needle, 0, needle.Length) = 0;
end;

Upvotes: 2

Related Questions