diff --git a/components/lazutils/dictionarystringlist.pas b/components/lazutils/dictionarystringlist.pas index 7d2f822cff..13a14cd268 100644 --- a/components/lazutils/dictionarystringlist.pas +++ b/components/lazutils/dictionarystringlist.pas @@ -16,26 +16,20 @@ Abstract: This is an unsorted StringList with a fast lookup feature. - Internally it uses a map container (TStringToPointerTree) to store the string again - as key and string's index as value. - It is then used for Contains, IndexOf and Find methods. + Internally it uses a string->integer map container to store + the string again as key and string's index as value. + It is then used for InserItem, Contains, IndexOf and Find methods. The extra container does not reserve too much memory because the strings are reference counted and not really copied. + The list cannot have duplicates. + Duplicates property values dupIgnore and dupError are fully supported, + unlike in unsorted StringList. + This list cannot be sorted. For a sorted list you should use normal TStringList. - This class is useful only when you must preserve the order in list, - but also need to do fast lookups to see if a string exists. - - "Duplicates" values dupIgnore and dupError are supported, unlike in unsorted StringList. - - Insert, Delete and Exchange are not supported yet. They require the map's - index values to be adjusted. There are at least 3 ways to solve it: - 1. Adjust only the changed indexes after each operation. - 2. Mark the map's index values as "dirty" after those operations. - Adjust all the indexes when IndexOf() or Find() is called for the first time. - 3. Decide that those operations are not needed. - This is a very specialized container after all. + This class is useful only when you must preserve the order in list, but + also need to do fast lookups to see if a string exists, or must prevent duplicates. } unit DictionaryStringList; @@ -53,16 +47,17 @@ type TDictionaryStringList = class(TStringList) private FMap: TStringToPointerTree; + function GetDuplicates: TDuplicates; function GetSorted: Boolean; + procedure SetDuplicates(AValue: TDuplicates); procedure SetSorted(AValue: Boolean); + procedure AdjustMap(StartIndex, Offset: Integer); protected -// procedure InsertItem(Index: Integer; const S: string); override; -// procedure InsertItem(Index: Integer; const S: string; O: TObject); override; + procedure InsertItem(Index: Integer; const S: string); override; + procedure InsertItem(Index: Integer; const S: string; O: TObject); override; public constructor Create; destructor Destroy; override; - function Add(const S: string): Integer; override; - procedure Insert(Index: Integer; const S: string); override; procedure Clear; override; procedure Delete(Index: Integer); override; procedure Exchange(Index1, Index2: Integer); override; @@ -70,7 +65,9 @@ type function Find(const S: string; out Index: Integer): Boolean; override; function IndexOf(const S: string): Integer; override; procedure Sort; override; + procedure ValidateMap; // For debugging purposes only public + property Duplicates: TDuplicates read GetDuplicates write SetDuplicates; property Sorted: Boolean read GetSorted write SetSorted; end; @@ -91,39 +88,20 @@ begin inherited Destroy; end; -function TDictionaryStringList.Add(const S: string): Integer; +procedure TDictionaryStringList.AdjustMap(StartIndex, Offset: Integer); +// Adjust all indexes >= startindex in map. +// This is needed after inserting or deleting an item. var - i: PtrInt; + i: Integer; + Ind: PtrInt; begin - Result := -1; - if Duplicates <> dupAccept then - if IndexOf(S) <> -1 then - case Duplicates of - DupIgnore : Exit; - DupError : raise Exception.Create('TDictionaryStringList.Add: Duplicates are not allowed.'); - end; - Result := inherited Add(S); - i := Result; - FMap[S] := Pointer(i); // Store index to map. + for i := StartIndex to Count-1 do + begin + Ind := PtrInt(FMap[Strings[i]]); + FMap[Strings[i]] := Pointer(Ind + Offset); + end; end; -procedure TDictionaryStringList.Insert(Index: Integer; const S: string); -begin - raise Exception.Create('TDictionaryStringList.Insert is not implemented yet.'); - inherited Insert(Index, S); - // ToDo: adjust all indexes in FMap after the item is inserted. -end; -{ -procedure TDictionaryStringList.InsertItem(Index: Integer; const S: string); -begin - inherited InsertItem(Index, S); -end; - -procedure TDictionaryStringList.InsertItem(Index: Integer; const S: string; O: TObject); -begin - inherited InsertItem(Index, S, O); -end; -} procedure TDictionaryStringList.Clear; begin inherited Clear; @@ -132,16 +110,45 @@ end; procedure TDictionaryStringList.Delete(Index: Integer); begin - raise Exception.Create('TDictionaryStringList.Delete is not implemented yet.'); inherited Delete(Index); - // ToDo: adjust all indexes in FMap after the item is deleted. + // Decrement Indexes in map above the deleted item. + AdjustMap(Index, -1); end; procedure TDictionaryStringList.Exchange(Index1, Index2: Integer); +var + s1, s2: string; begin - raise Exception.Create('TDictionaryStringList.Exchange is not implemented yet.'); + S1 := Strings[Index1]; + S2 := Strings[Index2]; inherited Exchange(Index1, Index2); - // ToDo: adjust all indexes in FMap after Exchange. + // Exchange Indexes in map, too. + FMap[s1] := Pointer(PtrInt(Index2)); + FMap[s2] := Pointer(PtrInt(Index1)); +end; + +procedure TDictionaryStringList.InsertItem(Index: Integer; const S: string); +var + i: PtrInt; +begin + if Duplicates <> dupAccept then + if IndexOf(S) <> -1 then + case Duplicates of + DupIgnore : Exit; + DupError : raise Exception.Create('TDictionaryStringList.InsertItem:' + +' Duplicates are not allowed.'); + end; + inherited InsertItem(Index, S); + i := Index; + FMap[S] := Pointer(i); // Store index to map. + // Increment Indexes in map above the inserted item. + AdjustMap(Index+1, 1); +end; + +procedure TDictionaryStringList.InsertItem(Index: Integer; const S: string; O: TObject); +begin + raise Exception.Create('TDictionaryStringList.InsertItem: is this needed?'); + //inherited InsertItem(Index, S, O); end; function TDictionaryStringList.Contains(const S: string): Boolean; @@ -163,20 +170,48 @@ begin Result := -1 end; +procedure TDictionaryStringList.ValidateMap; +// For debugging purposes only +var + i, Ind: Integer; + s: String; +begin + for i := 0 to Count-1 do + begin + s := Strings[i]; + Ind := integer(PtrInt(FMap[s])); + if Ind <> i then + raise Exception.CreateFmt('Indexes for string "%s" differ, list:%d, map:%d.', + [s, i, Ind]); + end; +end; + procedure TDictionaryStringList.Sort; begin raise Exception.Create('This list is not meant to be sorted. Use TStringList instead.'); end; +function TDictionaryStringList.GetSorted: Boolean; +begin + Result := False; +end; + procedure TDictionaryStringList.SetSorted(AValue: Boolean); begin if AValue then Sort; // Raise an exception here, too. end; -function TDictionaryStringList.GetSorted: Boolean; +function TDictionaryStringList.GetDuplicates: TDuplicates; begin - Result := False; + Result := inherited Duplicates; +end; + +procedure TDictionaryStringList.SetDuplicates(AValue: TDuplicates); +begin + if AValue = dupAccept then + raise Exception.Create('Sorry, TDictionaryStringList does not support duplicates.'); + inherited Duplicates := AValue; end; end.