Reputation: 1660
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
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
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
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
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