Search code examples
delphidelphi-xe

TDictionary with index access in delphi


I need a list that I can access by a key or by index. Is there any object in Delphi XE that already handles this for me?

I'm trying to use TDictionary, but there isn't an access by index.

I don't get the same order, using ToArray, for example:

procedure TForm1.Button1Click(Sender: TObject);
var list: TDictionary<string, string>;
    arr: TArray<string>;
    b,a: Integer;
    value: string;
begin
  list := TDictionary<string, string>.Create();
  for a:=0 to 10 do
    begin
       b:=random(1000);
       value := 'index: ' + inttostr(a) + ' - value: ' + inttostr(b);
       list.Add(inttostr(b), value);
       memo1.Lines.Add(value);
    end;
  arr := list.Values.ToArray();
  for value in arr do
  begin
    memo2.Lines.Add(value);
  end;
end;

result:

index: 0 - value: 830
index: 1 - value: 265
index: 2 - value: 964
index: 3 - value: 765
index: 4 - value: 917
index: 5 - value: 826
index: 6 - value: 353
index: 7 - value: 431
index: 8 - value: 837
index: 9 - value: 373
index: 10 - value: 805

and

index: 7 - value: 431
index: 8 - value: 837
index: 9 - value: 373
index: 10 - value: 805
index: 4 - value: 917
index: 5 - value: 826
index: 0 - value: 830
index: 2 - value: 964
index: 3 - value: 765
index: 6 - value: 353
index: 1 - value: 265

Solution

  • You'll probably have to create your own T/IOrderedDictionary class/interface

    Here's what the interface might look like:

    Based on Spring4D - IDictionary/IList

    unit OrderedDictionary;
    
    interface
    
    uses
      Spring,
      Generics.Collections,
      Generics.Defaults,
      Spring.Collections,
      Spring.Collections.Dictionaries,
      Spring.Collections.Lists;
    
    type
      IOrderedDictionary<K,V> = interface(IDictionary<K,V>)
        function Add(const item: TPair<K,V>): Integer;
    
      {$REGION 'Property Accessors'}
        function GetCapacity: Integer;
        function GetCount: Integer;
        function GetItem(index: Integer): TPair<K,V>;
        procedure SetCapacity(value: Integer);
        procedure SetCount(value: Integer);
        procedure SetItem(index: Integer; const item: TPair<K,V>);
      {$ENDREGION}
    
        /// <summary>
        ///   Inserts an item to the IList&lt;TPair<K,V>&gt; at the specified index.
        /// </summary>
        /// <param name="index">
        ///   The zero-based index at which item should be inserted.
        /// </param>
        /// <param name="item">
        ///   The element to insert into the IList&lt;TPair<K,V>&gt;.
        /// </param>
        procedure Insert(index: Integer; const item: TPair<K,V>);
        procedure InsertRange(index: Integer; const values: array of TPair<K,V>); overload;
        procedure InsertRange(index: Integer; const collection: IEnumerable<TPair<K,V>>); overload;
    
        /// <summary>
        ///   Removes the item at the specified index.
        /// </summary>
        /// <param name="index">
        ///   The zero-based index of the item to remove.
        /// </param>
        /// <exception cref="ArgumentOutOfRangeException">
        ///   <i>index</i> is not a valid index in the IList&lt;TPair<K,V>&gt;.
        /// </exception>
        procedure Delete(index: Integer);
        procedure DeleteRange(index, count: Integer);
    
        /// <summary>
        ///   Extracts the item at the specified index.
        /// </summary>
        /// <param name="index">
        ///   The zero-based index of the item to extract.
        /// </param>
        /// <exception cref="ArgumentOutOfRangeException">
        ///   <i>index</i> is not a valid index in the IList&lt;TPair<K,V>&gt;.
        /// </exception>
        function ExtractAt(index: Integer): TPair<K,V>;
        function ExtractRange(index, count: Integer): TArray<TPair<K,V>>; overload;
    
        /// <summary>
        ///   Creates a new list that contains a range of the elements in the
        ///   original list.
        /// </summary>
        /// <param name="index">
        ///   The zero-based index at which the range starts.
        /// </param>
        /// <param name="count">
        ///   The number of elements in the range.
        /// </param>
        /// <remarks>
        ///   If the list contains reference types the elements in the returned
        ///   list point to the same instance as the elements in the original list.
        ///   Also if the original list is a <see cref="Spring.Collections.Lists|TObjectList&lt;TPair<K,V>&gt;" />
        ///    it still owns the objects.
        /// </remarks>
        function GetRange(index, count: Integer): IList<TPair<K,V>>;
    
        procedure Exchange(index1, index2: Integer);
        procedure Move(currentIndex, newIndex: Integer);
    
        procedure Reverse; overload;
        procedure Reverse(index, count: Integer); overload;
    
        procedure Sort; overload;
        procedure Sort(const comparer: IComparer<K>); overload;
        procedure Sort(const comparer: TComparison<K>); overload;
        procedure Sort(const comparer: IComparer<K>; index, count: Integer); overload;
        procedure Sort(const comparer: TComparison<K>; index, count: Integer); overload;
    
        /// <summary>
        ///   Determines the index of a specific item in the IList&lt;TPair<K,V>&gt;.
        /// </summary>
        /// <param name="item">
        ///   The element to locate in the IList&lt;TPair<K,V>&gt;.
        /// </param>
        /// <returns>
        ///   The index of <i>item</i> if found in the list; otherwise, -1.
        /// </returns>
        /// <remarks>
        ///   If an element occurs multiple times in the list, the IndexOf method
        ///   always returns the first instance found.
        /// </remarks>
        function IndexOf(const Key: K): Integer; overload;
        function IndexOf(const Key: K; index: Integer): Integer; overload;
        function IndexOf(const Key: K; index, count: Integer): Integer; overload;
    
        function AsList: IList;
    
        /// <summary>
        ///   Returns the list as read-only list.
        /// </summary>
        /// <remarks>
        ///   This method will not perform a copy but will return the same instance
        ///   as IReadOnlyList&lt;TPair<K,V>&gt;.
        /// </remarks>
        function AsReadOnlyList: IReadOnlyList<TPair<K,V>>;
        procedure TrimExcess;
    
        property Capacity: Integer read GetCapacity write SetCapacity;
        property Count: Integer read GetCount write SetCount;
        property Items[index: Integer]: TPair<K,V> read GetItem write SetItem; default;
      end;
    

    If you know (or can control) how the Dictionary stores its items, you might make the List a TArray<Integer> holding the index number of the storage that the dictionary uses or a TArray<pointer to TPair<K,V>>.
    If K and V are small it might be better to just duplicate the list.

    A better option might be to use a B+Tree to store your items, semantically it sits somewhere between the dictionary and the list.