How can I compute a difference between two strings? - string

I want to create a function in Delphi that computes different levels of two strings. If two strings are equal (ignoring case), then it should return 0, but if they are not equal, it should return the number of different characters. This function can be very useful for checking spelling.
function GetDiffStringLevel(S1,S2:string):Integer;
begin
if SameText(S1,S2) then Exit(0);
// i want get different chars count
end
samples code:
Diff:=GetDiffStringLevel('Hello','Hello');// Diff:=0;
Diff:=GetDiffStringLevel('Hello','2Hello');// Diff:=1;
Diff:=GetDiffStringLevel('Hello','H2ello');// Diff:=1;
Diff:=GetDiffStringLevel('Hello','Hello W');// Diff:=2;
Diff:=GetDiffStringLevel('Hello','World');// Diff:=6; or 5

Fast and compact implementation.
About 3 times as fast as smasher's implementation with normal strings.
More than 100 times as fast if one of the strings is empty.
Smasher's function is case insensitive though, which can be useful as well.
function LevenshteinDistance(const s, t: string): integer;inline;
var
d : array of array of integer;
n, m, i, j : integer;
begin
n := length(s);
m := length(t);
if n = 0 then Exit(m);
if m = 0 then Exit(n);
SetLength(d, n + 1, m + 1);
for i := 0 to n do d[i, 0] := i;
for j := 0 to m do d[0, j] := j;
for i := 1 to n do
for j := 1 to m do
d[i, j] := Min(Min(d[i-1, j]+1, d[i,j-1]+1), d[i-1,j-1]+Integer(s[i] <> t[j]));
Result := d[n, m];
end;
Note: the inline directive reduces the execution time to less than 70% on my machine, but only for the win32 target platform. If you compile to 64bits (Delphi XE2), inlining actually makes it a tiny bit slower.

What you want is known as the Levenshtein distance (the minimum number of edits to transform one string into the other, where an edit is either a character insertion, character deletion or character substitution). The wikipedia site has a pseudo code implementation.
Delphi implementation:
function LevenshteinDistance(String1 : String; String2 : String) : Integer;
var
Length1, Length2 : Integer;
WorkMatrix : array of array of Integer;
I, J : Integer;
Cost : Integer;
Val1, Val2, Val3 : Integer;
begin
String1 := TCharacter.ToUpper (String1);
String2 := TCharacter.ToUpper (String2);
Length1 := Length (String1);
Length2 := Length (String2);
SetLength (WorkMatrix, Length1+1, Length2+1);
for I := 0 to Length1 do
WorkMatrix [I, 0] := I;
for J := 0 to Length2 do
WorkMatrix [0, J] := J;
for I := 1 to Length1 do
for J := 1 to Length2 do
begin
if (String1 [I] = String2 [J]) then
Cost := 0
else
Cost := 1;
Val1 := WorkMatrix [I-1, J] + 1;
Val2 := WorkMatrix [I, J-1] + 1;
Val3 := WorkMatrix[I-1, J-1] + Cost;
if (Val1 < Val2) then
if (Val1 < Val3) then
WorkMatrix [I, J] := Val1
else
WorkMatrix [I, J] := Val3
else
if (Val2 < Val3) then
WorkMatrix [I, J] := Val2
else
WorkMatrix [I, J] := Val3;
end;
Result := WorkMatrix [Length1, Length2];
end;

Related

Delphi (10.2): fast Integer conversion to string with separator

Let's say we have this Integer 1234567890, we want it converted to a string with a separator = 1.234.567.890, we could do Format('%n',[1234567890.0]); but it's very slow. I wrote a function to speed it up considerably (more than 2x faster). How could I improve it further, or can you come up with a faster routine?
function MyConvertDecToStrWithDot(Const n: UInt64): string;
Var a,b,x: Integer;
z,step: Integer;
l: SmallInt;
begin
Result := IntToStr(n);
if n < 1000 then Exit;
l := Length(Result);
a := l div 3;
b := l mod 3;
step := b+1;
z := 4;
if b <> 0 then begin
Insert('.',Result,step);
Inc(z,step);
end;
for x := 1 to (a-1) do begin
Insert('.',Result,z);
Inc(z,4);
end;
end;
procedure TForm1.Button1Click(Sender: TObject);
Var a: Integer;
s: string;
begin
PerfTimerInit;
for a := 1 to 1000000 do
s := MyConvertDecToStrWithDot(1234567890);
Memo1.lines.Add(PerfTimerStopMS.ToString);
caption := s;
end;
32-bit
Format: ~230ms
My function: ~79ms
64-bit
Format: ~440ms
My function: ~103ms
In my tests, the following is ever so slightly faster:
function ThousandsSepStringOf(Num: UInt64): string;
const
MaxChar = 30; // Probably, 26 is enough: 19 digits + 7 separators
var
Count: Integer;
Rem: UInt64;
Res: array[0..MaxChar] of Char;
WritePtr: PChar;
begin
WritePtr := #Res[MaxChar];
WritePtr^ := #0;
Count := 0;
while Num > 0 do
begin
DivMod(Num, 10, Num, Rem);
Dec(WritePtr);
WritePtr^ := Char(Byte(Rem) + Ord('0'));
Inc(Count);
if Count = 3 then
begin
Dec(WritePtr);
WritePtr^ := '.';
Count := 0;
end;
end;
if WritePtr^ = '.' then
Inc(WritePtr);
Count := MaxChar - ((NativeInt(WritePtr) - NativeInt(#Res)) shr 1);
SetLength(Result, Count);
Move(WritePtr^, PByte(Result)^, Count * SizeOf(Char));
end;
Tested with:
procedure TestHisCode;
Var
a: Integer;
s: string;
SW: TStopwatch;
begin
Writeln('His code');
SW := TStopwatch.StartNew;
for a := 1 to KLoops do
s := MyConvertDecToStrWithDot(1234567890);
Writeln(SW.ElapsedMilliseconds);
Writeln(s);
Writeln;
end;
procedure TestMyCode;
Var
a: Integer;
s: string;
SW: TStopwatch;
begin
Writeln('My code');
SW := TStopwatch.StartNew;
for a := 1 to KLoops do
s := ThousandsSepStringOf(1234567890);
Writeln(SW.ElapsedMilliseconds);
Writeln(s);
Writeln;
end;
and:
TestHisCode;
TestMyCode;
TestMyCode;
TestHisCode;
TestMyCode;
TestHisCode;
TestHisCode;
TestMyCode;
Haven't properly tested the performance of this, however it should be cross-platform and locale independent:
function Thousands(const ASource: string): string;
var
I, LLast: Integer;
begin
Result := ASource;
LLast := Length(Result);
I := LLast;
while I > 0 do
begin
if (LLast - I + 1) mod 3 = 0 then
begin
Insert(FormatSettings.ThousandSeparator, Result, I);
Dec(I, 2);
end
else
Dec(I);
end;
end;
Note: It obviously just works on integers
It's better to insert the separators directly while constructing the string instead of inserting separators later into the converted string because each insertion involves a lot of data movements and performance degradation. Besides avoid the division by 3 may improve performance a bit
This is what I get from my rusty Pascal after decades not using it
uses strutils;
function FormatNumber(n: integer): string;
var digit: integer;
count: integer;
isNegative: boolean;
begin
isNegative := (n < 0);
if isNegative then n := -n;
Result := '';
count := 3;
while n <> 0 do begin
digit := n mod 10;
n := n div 10;
if count = 0 then begin
Result := Result + '.';
count := 3;
end;
Result := Result + chr(ord('0') + digit);
dec(count);
end;
if isNegative then Result := Result + '-';
Result := reversestring(Result);
end;
See it in action: http://ideone.com/6O3e8w
It's also faster to just assign the characters directly instead of using concatenation operator/function like Victoria suggested. This is the improved version with only unsigned types
type string28 = string[28];
function FormatNumber(n: UInt64): string28;
var digit: integer;
length: integer;
count: integer;
c: char;
begin
count := 3;
length := 0;
while n <> 0 do begin
digit := n mod 10;
n := n div 10;
if count = 0 then begin
inc(length);
Result[length] := '.';
count := 3;
end;
inc(length);
Result[length] := chr(ord('0') + digit);
dec(count);
end;
for count := 1 to (length + 1) div 2 do begin
c := Result[count];
Result[count] := Result[length - count + 1];
Result[length - count + 1] := c;
end;
setlength(Result, length);
FormatNumber := Result;
end;
If the operation is done millions of times and is really a bottleneck after profiling, it's better to do in multiple threads along with SIMD

Delphi10. Function to copy the character until this character ':'

Hello guys I want to do a function which stop and copy the string until the first character ':' is meet.
I have the following string '404:Bad Request' and my output is '404:' but i want just '404'.Here is my code:
function CutOff(const s: string; n: integer):string;
var
i, k: integer;
begin
k := 0;
for i := 1 to n do
begin
k := Pos(s, ':', k+1);
if k = 1 then Exit;
end;
Result := Copy(s, 1, k);
end;
It appears that your function is intended to return the string up to the nth colon, contrary to what you say in your description: ... until the first character ':' is met.
A problem with your code is however, that you have the arguments to the Pos() function the wrong way. After correcting that, to omit the colon you can simply subtract 1 from the length to copy:
function CutOff(const s: string; n: integer): string;
var
i, k: integer;
begin
k := 0;
for i := 1 to n do
begin
k := Pos(':', s, k+1);
if k = 1 then Exit;
end;
Result := Copy(s, 1, k-1); // note here k-1
end;
And to find the string up to the first colon you call it
Errorcode := CutOff('404:Bad request', 1);
Alternatively, if you never want the nth colon, only the first
function CutOff(const s: string): string;
var
k: integer;
begin
k := Pos(':', s);
Result := Copy(s, 1, k-1);
end;

How to remove repeated spaces from a string

I need to remove repeated spaces from a string.
The following code, grabbed from internet, works decently except that it duplicate the first char of the string.
Also maybe there is something faster that this.
function DeleteRepeatedSpaces(OldText: string): string;
var
i: integer;
s: string;
begin
if length(OldText) > 0 then
s := OldText[1]
else
s := '';
for i := 1 to length(OldText) do
begin
if OldText[i] = ' ' then
begin
if not (OldText[i - 1] = ' ') then
s := s + ' ';
end
else
begin
s := s + OldText[i];
end;
end;
DelDoubleSpaces := s;
end;
Function based on the simplest state machine (DFA). Minimum of memory reallocations.
State is number of continuous spaces.
J is count of deleted spaces.
function DeleteRepeatedSpaces(const s: string): string;
var
i, j, State: Integer;
begin
SetLength(Result, Length(s));
j := 0;
State := 0;
for i := 1 to Length(s) do begin
if s[i] = ' ' then
Inc(State)
else
State := 0;
if State < 2 then
Result[i - j] := s[i]
else
Inc(j);
end;
if j > 0 then
SetLength(Result, Length(s) - j);
end;
Iterate all members of the string, move the characters to the Result, but skip repeated spaces.
function DeleteRepeatedSpaces(const OldText: string): string;
var
i,j,hi: Integer;
begin
SetLength(Result,Length(OldText));
i := Low(OldText);
j := i;
hi := High(OldText);
while (i <= hi) do begin
Result[j] := OldText[i];
Inc(j);
if (OldText[i] = ' ') then begin
repeat //Skip additional spaces
Inc(i);
until (i > hi) or (OldText[i] <> ' ');
end
else
Inc(i);
end;
SetLength(Result,j-Low(Result)); // Set correct length
end;
The above code is rather fast (faster than any other contribution, so far).
Below is an even more optimized routine:
function DeleteRepeatedSpaces(const OldText: string): string;
var
pO,pR: PChar;
begin
SetLength(Result,Length(OldText));
pR := Pointer(Result);
pO := Pointer(OldText);
while (pO^ <> '') do begin
pR^ := pO^;
Inc(pR);
if (pO^ <> ' ') then begin
Inc(pO);
Continue;
end;
repeat // Skip additional spaces
Inc(pO);
until (pO^ = '') or (pO^ <> ' ');
end;
SetLength(Result,pR-Pointer(Result));
end;
The following isn't wildly efficient, but possibly more so that processiing the string character by character because it doesn't require a new string allocation for each character in the output:
function RemoveDupSpaces(const Input : String) : String;
var
P : Integer;
begin
Result := Input;
repeat
P := Pos(' ', Result); // that's two spaces
if P > 0 then
Delete(Result, P + 1, 1);
until P = 0;
end;
You can use something like this:
function DeleteRepeatedSpaces(const s: string):string;
var
i:integer;
begin
Result := '';
for i := 1 to Length(S) do begin
if not ((s[i]=' ') and (s[i-1]=' ')) then begin
Result := Result + s[i];
end;
end;
end;
Delete two o more spaces contiguous in a string.
This string (without spaces):
The string have groups of spaces inside
return this:
The string have groups of spaces inside
This string (with spaces groups inside):
The string have groups of spaces inside
Return this:
The string have groups of spaces inside

Delphi 2007 AsyncMultiSync Does not work

I have tried to write a parallel threading example using AsyncCalls and the following is to parallel compute the number of prime numbers
program Project3;
{$APPTYPE CONSOLE}
uses
SysUtils,
Math,
Windows,
AsyncCalls in 'AsyncCalls.pas';
const
N = 1000000;
MAXT = 100;
var
threads : array of IAsyncCall;
ans: array of integer;
cnt: DWORD;
i, v, j, k, portion: integer;
function IsPrime(x: integer): boolean;
var
i: integer;
begin
if (x <= 1) then
begin
Result := False;
Exit;
end;
if (x = 2) then
begin
Result := True;
Exit;
end;
for i:= 2 to Ceil(Sqrt(x))do
begin
if (x mod i = 0) then
begin
Result := False;
Exit;
end;
end;
Result := True;
end;
procedure DoWork(left, right: integer; value: PInteger); cdecl;
var
i, cnt: integer;
begin
cnt := 0;
for i := left to right do
begin
if (IsPrime(i)) then
begin
Inc(cnt);
end;
end;
value^ := cnt;
end;
begin
// Paralell
cnt := GetTickCount;
SetLength(ans, MAXT);
SetLength(threads, MAXT);
portion := N div MAXT;
for i := 0 to MAXT - 2 do
begin
// left index
j := i * portion;
// right index
k := (i + 1) * portion - 1;
threads[i] := AsyncCall(#DoWork, [j, k, #ans[i]]);
end;
// last thread
j := (MAXT - 1) * portion;
threads[MAXT - 1] := AsyncCall(#DoWork, [j, N - 1, #ans[MAXT - 1]]);
// Join, doesn't seem to wait all
AsyncMultiSync(threads, True, INFINITE);
// ****Adding a delay to wait for all threads*****
// Sleep(1000);
// Sum the answer
v := 0;
for i := 0 to MAXT - 1 do
begin
Inc(v, ans[i]);
end;
Writeln('Parallel = ', GetTickCount - cnt);
Writeln('Answer = ', v);
// Serial
cnt := GetTickCount;
DoWork(0, N - 1, #v);
Writeln('Serial = ', GetTickCount - cnt);
Writeln('Answer = ', v);
Readln;
end.
Strange behaviour. The AsyncMultiSync does not seem waiting at all. If I do not add Sleep(1000), the output is
Parallel = 172
Answer = 0
Serial = 453
Answer = 78498
If I add Sleep(1000), the output is correct:
Parallel = 1188
Answer = 78498
Serial = 265
Answer = 78498
I tried using threads[i].Sync, and it produces similar results.
Did I miss something here?
The environment is D2007, Windows 10 64-bit Home
Documentation says not to exceed the maximum number of wait objects, 61.
So you will have to reduce the MaxT constant.

IP Address String Routines in Delphi?

I'm looking for a way in Delphi to validate and manipulate IP Addresses. Some of the things it should be able to do is...
Verify that a string is a valid IP address
Verify that a string is a valid subnet mask
Verify that an IP address is within a given Subnet
Some type (record or string or whatever) which is meant for storing an IP address
Basic conversion of an IP address types, such as String or Array[0..3] of Byte
Any other IP address routines that can make IP manipulation easier
The basic reason is that I want to see if these things are already out there before I go ahead and reinvent them.
I also once wrote a IPv4 and IPv6 conversion unit including a custom variant type for both types of IP addresses. This answer shows a few examples of its capabilities. Originally, it was designed to visualize values of various types in scale on some slider control 1). The requirements then were such that default existing libraries weren't sufficient, but I agree with the comments here that you probably will be helped with just Indy (10!) or alike.
Answering your list of questions with a few code snippets from that unit:
Q4: Storage type for IP types:
const
IPv4BitSize = SizeOf(Byte) * 4 * 8;
IPv6BitSize = SizeOf(Word) * 8 * 8;
type
T4 = 0..3;
T8 = 0..7;
TIPv4ByteArray = array[T4] of Byte;
TIPv6WordArray = array[T8] of Word;
TIPv4 = packed record
case Integer of
0: (D, C, B, A: Byte);
1: (Groups: TIPv4ByteArray);
2: (Value: Cardinal);
end;
TIPv6 = packed record
case Integer of
0: (H, G, F, E, D, C, B, A: Word);
1: (Groups: TIPv6WordArray);
end;
Q5: Conversion of IP address strings to these record or array types:
function StrToIPv4(const S: String): TIPv4;
var
SIP: String;
Start: Integer;
I: T4;
Index: Integer;
Count: Integer;
SGroup: String;
G: Integer;
begin
SIP := S + '.';
Start := 1;
for I := High(T4) downto Low(T4) do
begin
Index := PosEx('.', SIP, Start);
if Index = 0 then
IPv4ErrorFmt(SInvalidIPv4Value, S);
Count := Index - Start + 1;
SGroup := Copy(SIP, Start, Count - 1);
if TryStrToInt(SGroup, G) and (G >= Low(Word)) and (G <= High(Word)) then
Result.Groups[I] := G
else
Result.Groups[I] := 0;
Inc(Start, Count);
end;
end;
function StrToIPv6(const S: String): TIPv6;
{ Valid examples for S:
2001:0db8:85a3:0000:0000:8a2e:0370:7334
2001:db8:85a3:0:0:8a2e:370:7334
2001:db8:85a3::8a2e:370:7334
::8a2e:370:7334
2001:db8:85a3::
::1
::
::ffff:c000:280
::ffff:192.0.2.128 }
var
ZeroPos: Integer;
DotPos: Integer;
SIP: String;
Start: Integer;
Index: Integer;
Count: Integer;
SGroup: String;
G: Integer;
procedure NormalNotation;
var
I: T8;
begin
SIP := S + ':';
Start := 1;
for I := High(T8) downto Low(T8) do
begin
Index := PosEx(':', SIP, Start);
if Index = 0 then
IPv6ErrorFmt(SInvalidIPv6Value, S);
Count := Index - Start + 1;
SGroup := '$' + Copy(SIP, Start, Count - 1);
if not TryStrToInt(SGroup, G) or (G > High(Word)) or (G < 0) then
IPv6ErrorFmt(SInvalidIPv6Value, S);
Result.Groups[I] := G;
Inc(Start, Count);
end;
end;
procedure CompressedNotation;
var
I: T8;
A: array of Word;
begin
SIP := S + ':';
Start := 1;
I := High(T8);
while Start < ZeroPos do
begin
Index := PosEx(':', SIP, Start);
if Index = 0 then
IPv6ErrorFmt(SInvalidIPv6Value, S);
Count := Index - Start + 1;
SGroup := '$' + Copy(SIP, Start, Count - 1);
if not TryStrToInt(SGroup, G) or (G > High(Word)) or (G < 0) then
IPv6ErrorFmt(SInvalidIPv6Value, S);
Result.Groups[I] := G;
Inc(Start, Count);
Dec(I);
end;
FillChar(Result.H, (I + 1) * SizeOf(Word), 0);
if ZeroPos < (Length(S) - 1) then
begin
SetLength(A, I + 1);
Start := ZeroPos + 2;
repeat
Index := PosEx(':', SIP, Start);
if Index > 0 then
begin
Count := Index - Start + 1;
SGroup := '$' + Copy(SIP, Start, Count - 1);
if not TryStrToInt(SGroup, G) or (G > High(Word)) or (G < 0) then
IPv6ErrorFmt(SInvalidIPv6Value, S);
A[I] := G;
Inc(Start, Count);
Dec(I);
end;
until Index = 0;
Inc(I);
Count := Length(A) - I;
Move(A[I], Result.H, Count * SizeOf(Word));
end;
end;
procedure DottedQuadNotation;
var
I: T4;
begin
if UpperCase(Copy(S, ZeroPos + 2, 4)) <> 'FFFF' then
IPv6ErrorFmt(SInvalidIPv6Value, S);
FillChar(Result.E, 5 * SizeOf(Word), 0);
Result.F := $FFFF;
SIP := S + '.';
Start := ZeroPos + 7;
for I := Low(T4) to High(T4) do
begin
Index := PosEx('.', SIP, Start);
if Index = 0 then
IPv6ErrorFmt(SInvalidIPv6Value, S);
Count := Index - Start + 1;
SGroup := Copy(SIP, Start, Count - 1);
if not TryStrToInt(SGroup, G) or (G > High(Byte)) or (G < 0) then
IPv6ErrorFmt(SInvalidIPv6Value, S);
case I of
0: Result.G := G shl 8;
1: Inc(Result.G, G);
2: Result.H := G shl 8;
3: Inc(Result.H, G);
end;
Inc(Start, Count);
end;
end;
begin
ZeroPos := Pos('::', S);
if ZeroPos = 0 then
NormalNotation
else
begin
DotPos := Pos('.', S);
if DotPos = 0 then
CompressedNotation
else
DottedQuadNotation;
end;
end;
For Q1 to Q3 you have to derive some routines yourself, but that should not be any problem.
1) For those interested, it's this slider control and this topic served as initiation of this unit.
I have already written all the functions you require but I'm afraid I'm not in a position to share the code.
However, the Synapse library contains quite a few functions in the synaip unit. e.g.
function IsIP(const Value: string): Boolean;
function IsIP6(const Value: string): Boolean;
function IPToID(Host: string): Ansistring;
function StrToIp6(value: string): TIp6Bytes;
function Ip6ToStr(value: TIp6Bytes): string;
function StrToIp(value: string): integer;
function IpToStr(value: integer): string;
function ReverseIP(Value: AnsiString): AnsiString;
function ReverseIP6(Value: AnsiString): AnsiString;
When I tried the functions a few years ago, the IPv6 functions were a bit buggy, especially when dealing with compressed IPv6 addresses.
If you want to roll your own, here a few pointers:
Manipulating IPv4 addresses is fairly simple as they only consist of
32 bits which can be stored in a standard integer type. IPv6
addresses are harder as they need 128 bits and no native type has
that many bits.
Before trying to manipulate an IP address, first convert it into an
integer (IPv4 only of course, IPv6 will require a different storage
method). To do this, split the IP address using '.' as a delimiter.
Check each individual value only contains numbers and falls between 0
and 255 then use these values to generate the final integer.
Once the IP address has been converted into an integer, you can
manipulate it however you like. For example, given an IP address and
a subnet mask you can find the subnet that the IP address belongs to
e.g. IPtoInt(IPAddress) AND NOT(IPToInt(SubnetMask)) = The integer of
the subnet. Now, you can comaare an integer IP address to the integer
subnet to see if the IP falls within the subnet.
Finally, convert the integer IP address back into a string.
If you have any specific questions I can try to answer them too.

Resources