Skip to content

Commit 435a1c2

Browse files
committed
improved case insensitive comparer
1 parent f9e49b5 commit 435a1c2

File tree

3 files changed

+104
-60
lines changed

3 files changed

+104
-60
lines changed

Source/Base/Collections/Spring.Collections.pas

Lines changed: 96 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -3493,17 +3493,21 @@ TEnumerable = class
34933493
const resultSelector: Func<TFirst, TSecond, TResult>): IEnumerable<TResult>; overload; static;
34943494
end;
34953495

3496-
TStringComparer = class(TCustomComparer<string>)
3496+
TStringComparer = class(TObject, IComparer<string>, IEqualityComparer<string>)
34973497
private
34983498
fLocaleOptions: TLocaleOptions;
34993499
fIgnoreCase: Boolean;
35003500
class var
35013501
fOrdinal: TStringComparer;
35023502
fOrdinalIgnoreCase: TStringComparer;
35033503
protected
3504-
function Compare(const Left, Right: string): Integer; override;
3505-
function Equals(const Left, Right: string): Boolean; override;
3506-
function GetHashCode(const Value: string): Integer; override;
3504+
function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
3505+
function _AddRef: Integer; stdcall;
3506+
function _Release: Integer; stdcall;
3507+
3508+
function Compare(const left, right: string): Integer; reintroduce;
3509+
function Equals(const left, right: string): Boolean; reintroduce;
3510+
function GetHashCode(const value: string): Integer; reintroduce;
35073511
public
35083512
constructor Create(localeOptions: TLocaleOptions; ignoreCase: Boolean);
35093513
class constructor Create;
@@ -3553,9 +3557,6 @@ implementation
35533557

35543558
uses
35553559
Character,
3556-
{$IFDEF DELPHIXE8_UP}
3557-
System.Hash,
3558-
{$ENDIF}
35593560
Rtti,
35603561
Spring.Collections.Base,
35613562
Spring.Collections.Dictionaries,
@@ -3567,6 +3568,7 @@ implementation
35673568
Spring.Collections.Queues,
35683569
Spring.Collections.Sets,
35693570
Spring.Collections.Stacks,
3571+
Spring.Comparers,
35703572
Spring.ResourceStrings;
35713573

35723574

@@ -7580,68 +7582,89 @@ constructor TStringComparer.Create(localeOptions: TLocaleOptions;
75807582
FreeAndNil(fOrdinalIgnoreCase);
75817583
end;
75827584

7583-
function TStringComparer.Compare(const Left, Right: string): Integer;
7584-
var
7585-
L, R: string;
7585+
function TStringComparer.Compare(const left, right: string): Integer;
75867586
begin
75877587
if fIgnoreCase then
7588-
begin
7589-
{$IFNDEF DELPHIXE4_UP}
7590-
L := TCharacter.ToUpper(Left);
7591-
R := TCharacter.ToUpper(Right);
7592-
{$ELSE}
7593-
L := Char.ToUpper(Left);
7594-
R := Char.ToUpper(Right);
7595-
{$ENDIF}
7596-
end else
7597-
begin
7598-
L := Left;
7599-
R := Right;
7600-
end;
7601-
7602-
Result := CompareStr(L, R, fLocaleOptions);
7588+
Result := AnsiCompareText(left, right)
7589+
else
7590+
Result := AnsiCompareStr(left, right);
76037591
end;
76047592

7605-
function TStringComparer.Equals(const Left, Right: string): Boolean;
7606-
var
7607-
L, R: string;
7593+
function TStringComparer.Equals(const left, right: string): Boolean;
76087594
begin
76097595
if fIgnoreCase then
7596+
Result := AnsiSameText(left, right)
7597+
else
7598+
Result := AnsiSameStr(left, right);
7599+
end;
7600+
7601+
function TStringComparer.GetHashCode(const value: string): Integer;
7602+
const
7603+
FNV_Prime = 16777619;
7604+
FNV_OffsetBasis = Integer($811C9DC5); // 2166136261
7605+
NotAsciiMask = $FF80FF80;
7606+
LowerCaseMask = $00200020;
7607+
7608+
// for inlining when compiled as package - System.Length does not in that case
7609+
function Length(const s: string): NativeInt; inline;
76107610
begin
7611-
{$IFNDEF DELPHIXE4_UP}
7612-
L := TCharacter.ToUpper(Left);
7613-
R := TCharacter.ToUpper(Right);
7614-
{$ELSE}
7615-
L := Char.ToUpper(Left);
7616-
R := Char.ToUpper(Right);
7617-
{$ENDIF}
7618-
end else
7619-
begin
7620-
L := Left;
7621-
R := Right;
7611+
Result := IntPtr(s);
7612+
if Result <> 0 then
7613+
Result := PInteger(@PByte(Result)[-4])^;
76227614
end;
76237615

7624-
Result := SameStr(L, R, fLocaleOptions);
7625-
end;
7616+
function GetHashCodeIgnoreCaseSlow(const value: string): Integer;
7617+
var
7618+
s: string;
7619+
len, i: NativeInt;
7620+
c: Integer;
7621+
begin
7622+
s := AnsiUpperCase(value);
7623+
len := Length(s);
7624+
Result := FNV_OffsetBasis;
7625+
i := 0;
7626+
while len > 0 do
7627+
begin
7628+
c := PIntegerArray(s)[i];
7629+
c := c or LowerCaseMask;
7630+
Result := Result xor c;
7631+
{$Q-}
7632+
Result := Result * FNV_PRIME;
7633+
{$IFDEF OVERFLOWCHECKS_ON}{$Q+}{$ENDIF}
7634+
Inc(i);
7635+
Dec(len, 2);
7636+
end;
7637+
end;
76267638

7627-
function TStringComparer.GetHashCode(const Value: string): Integer;
7639+
label
7640+
NotAscii;
76287641
var
7629-
s: string;
7642+
len, i: NativeInt;
7643+
hashCode, c: Integer;
76307644
begin
7645+
len := Length(value);
76317646
if fIgnoreCase then
7632-
{$IFNDEF DELPHIXE4_UP}
7633-
S := TCharacter.ToUpper(Value)
7634-
{$ELSE}
7635-
S := Char.ToUpper(Value)
7636-
{$ENDIF}
7647+
begin
7648+
hashCode := FNV_OffsetBasis;
7649+
i := 0;
7650+
while len > 0 do
7651+
begin
7652+
c := PIntegerArray(value)[i];
7653+
if c and NotAsciiMask <> 0 then goto NotAscii;
7654+
c := c or LowerCaseMask;
7655+
hashCode := hashCode xor c;
7656+
{$Q-}
7657+
hashCode := hashCode * FNV_PRIME;
7658+
{$IFDEF OVERFLOWCHECKS_ON}{$Q+}{$ENDIF}
7659+
Inc(i);
7660+
Dec(len, 2);
7661+
end;
7662+
Exit(hashCode);
7663+
NotAscii:
7664+
Result := GetHashCodeIgnoreCaseSlow(value);
7665+
end
76377666
else
7638-
S := Value;
7639-
7640-
{$IFDEF DELPHIXE8_UP}
7641-
Result := THashBobJenkins.GetHashValue(S);
7642-
{$ELSE}
7643-
Result := BobJenkinsHash(PChar(S)^, SizeOf(Char) * Length(S), 0);
7644-
{$ENDIF}
7667+
Result := DefaultHashFunction(Pointer(value)^, len * SizeOf(Char));
76457668
end;
76467669

76477670
class function TStringComparer.Ordinal: TStringComparer;
@@ -7654,6 +7677,24 @@ class function TStringComparer.OrdinalIgnoreCase: TStringComparer;
76547677
Result := fOrdinalIgnoreCase;
76557678
end;
76567679

7680+
function TStringComparer.QueryInterface(const IID: TGUID; out Obj): HResult;
7681+
begin
7682+
if GetInterface(IID, Obj) then
7683+
Result := S_OK
7684+
else
7685+
Result := E_NOINTERFACE;
7686+
end;
7687+
7688+
function TStringComparer._AddRef: Integer;
7689+
begin
7690+
Result := -1;
7691+
end;
7692+
7693+
function TStringComparer._Release: Integer;
7694+
begin
7695+
Result := -1;
7696+
end;
7697+
76577698
{$ENDREGION}
76587699

76597700

Source/Base/Spring.Comparers.pas

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -823,10 +823,13 @@ function Equals_UString(const inst: Pointer; const left, right: string): Boolean
823823
{$ENDIF}
824824

825825
function GetHashCode_UString(const inst: Pointer; const value: string): Integer;
826+
var
827+
hashCode: NativeInt;
826828
begin
827-
Result := NativeInt(value);
828-
if Result <> 0 then
829-
Result := DefaultHashFunction(PPointer(value)^, PInteger(PByte(value) - 4)^ * SizeOf(Char));
829+
hashCode := NativeInt(value);
830+
if hashCode <> 0 then
831+
hashCode := DefaultHashFunction(PPointer(hashCode)^, PInteger(@PByte(value)[-4])^ * SizeOf(Char));
832+
Result := hashCode;
830833
end;
831834

832835
function Compare_Variant_Complex(checkEquality: Boolean; const left, right: PVariant): Integer;

Source/Base/Spring.HashTable.pas

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -589,8 +589,8 @@ function THashTable<T>.FindWithoutComparer(const key: T; options: Byte): Pointer
589589
end;
590590
tkUString:
591591
begin
592-
hashCode := PNativeInt(@key)^;
593-
if hashCode <> 0 then
592+
hashCode := 0;
593+
if PPointer(@key)^ <> nil then
594594
{$R-}
595595
hashCode := DefaultHashFunction(PPointer(@key)^^, PInteger(PByte((@key)^) - 4)^ * SizeOf(Char)) and not RemovedFlag;
596596
{$IFDEF RANGECHECKS_ON}{$R+}{$ENDIF}

0 commit comments

Comments
 (0)