Search code examples
delphitlistdelphi-xe3

How can read grouped data from a TList<> in Delphi


In C# Linq would make this super-easy but I am new to using Lists in Delphi and I need some advice.

I have a list of objects stored as TList<IMyInterface> with each object essentially being a collection of data, e.g.

1, 1, 2, 2, 2, 2, 3, 4, 4, 4 

I wish to create a new TList<TList<IMyInterface>> where the items in the list are grouped such as:

1, 1 
2, 2, 2, 2, 2 
3 
4, 4, 4 

What would be the most effective way of doing this in Delphi XE3 (starter edition)?


Solution

  • The easiest would be a set of loops. Using the DeHL or Spring frameworks for Delphi might shorten your code a bit, but not much.

    This question about querying generic TList instances using conditions gives a hint on using the Collections from the Spring framework.

    My example uses Integer in stead of your interface type, but it should be easy to adapt.

    Below are two methods for splitting, depending if you want each list to start on a changing value, or only for distinct values (like David said: long live TDictionary<Key, Value>)

    I see the difference, I doubled your example:

    1, 1, 2, 2, 2, 2, 3, 4, 4, 4, 1, 1, 2, 2, 2, 2, 3, 4, 4, 4
    

    The first algorithm will return these:

    1, 1
    2, 2, 2, 2
    3
    4, 4, 4
    1, 1
    2, 2, 2, 2
    3
    4, 4, 4
    

    The second these:

    1, 1, 1, 1
    2, 2, 2, 2, 2, 2, 2, 2
    3, 3
    4, 4, 4, 4, 4, 4
    

    Here is the example program; only printing requires nested loops.

    program SplitListOfIntegersIntoSublists;
    
    {$APPTYPE CONSOLE}
    
    {$R *.res}
    
    uses
      System.SysUtils,
      System.Generics.Collections;
    
    type
      TIntegerList = TList<Integer>;
      TIntegerListList = TList<TIntegerList>;
      TMain = class(TObject)
      private
        class function AddNewIntegerList(IntegerListList: TIntegerListList): TIntegerList;
        class procedure AddValues(AllIntegers: TIntegerList);
        class procedure Fill_NewListForEachValueChange(const AllIntegers: TIntegerList; const IntegerListList: TIntegerListList);
        class procedure Fill_NewListForDistinctValues(const AllIntegers: TIntegerList; const IntegerListList: TIntegerListList);
        class procedure Free(const IntegerListList: TIntegerListList);
        class procedure Print(const IntegerList: TIntegerList); overload;
        class procedure Print(const IntegerListList: TIntegerListList); overload;
      public
        class procedure Run;
      end;
    
    class function TMain.AddNewIntegerList(IntegerListList: TIntegerListList): TIntegerList;
    begin
      Result := TIntegerList.Create;
      IntegerListList.Add(Result);
    end;
    
    class procedure TMain.AddValues(AllIntegers: TIntegerList);
    begin
    // 1, 1, 2, 2, 2, 2, 3, 4, 4, 4
      AllIntegers.Add(1);
      AllIntegers.Add(1);
      AllIntegers.Add(2);
      AllIntegers.Add(2);
      AllIntegers.Add(2);
      AllIntegers.Add(2);
      AllIntegers.Add(3);
      AllIntegers.Add(4);
      AllIntegers.Add(4);
      AllIntegers.Add(4);
    end;
    
    class procedure TMain.Fill_NewListForEachValueChange(const AllIntegers: TIntegerList; const IntegerListList: TIntegerListList);
    var
      IntegerList: TIntegerList;
      Value: Integer;
    begin
      for Value in AllIntegers do
      begin
        if (IntegerListList.Count = 0) or (Value <> IntegerList.First) then
          IntegerList := AddNewIntegerList(IntegerListList);
        IntegerList.Add(Value);
      end;
    end;
    
    class procedure TMain.Fill_NewListForDistinctValues(const AllIntegers: TIntegerList; const IntegerListList:
        TIntegerListList);
    type
      TIntegerListDictionary = TDictionary<Integer, TIntegerList>;
    var
      IntegerListDictionary: TIntegerListDictionary;
      IntegerList: TIntegerList;
      Value: Integer;
    begin
      IntegerListDictionary := TIntegerListDictionary.Create();
      for Value in AllIntegers do
      begin
        if IntegerListDictionary.ContainsKey(Value) then
          IntegerList := IntegerListDictionary[Value]
        else
        begin
          IntegerList := AddNewIntegerList(IntegerListList);
          IntegerListDictionary.Add(Value, IntegerList);
        end;
        IntegerList.Add(Value);
      end;
    end;
    
    class procedure TMain.Free(const IntegerListList: TIntegerListList);
    var
      IntegerList: TIntegerList;
    begin
      for IntegerList in IntegerListList do
        IntegerList.Free;
      IntegerListList.Free;
    end;
    
    class procedure TMain.Print(const IntegerList: TIntegerList);
    var
      Value: Integer;
      First: Boolean;
    begin
      First := True;
      for Value in IntegerList do
      begin
        if not First then
          Write(', ');
        Write(Value);
        First := False;
      end;
      Writeln;
    end;
    
    class procedure TMain.Print(const IntegerListList: TIntegerListList);
    var
      IntegerList: TIntegerList;
    begin
      for IntegerList in IntegerListList do
        Print(IntegerList);
      Writeln;
    end;
    
    class procedure TMain.Run;
    var
      AllIntegers: TIntegerList;
      IntegerListList: TIntegerListList;
    begin
      AllIntegers := TIntegerList.Create();
      try
        AddValues(AllIntegers);
        Print(AllIntegers);
    
        IntegerListList := TIntegerListList.Create();
        try
          Fill_NewListForEachValueChange(AllIntegers, IntegerListList);
          Print(IntegerListList);
        finally
          Free(IntegerListList);
        end;
    
        AddValues(AllIntegers);
        Print(AllIntegers);
    
        IntegerListList := TIntegerListList.Create();
        try
          Fill_NewListForEachValueChange(AllIntegers, IntegerListList);
          Print(IntegerListList);
        finally
          Free(IntegerListList);
        end;
    
        Print(AllIntegers);
    
        IntegerListList := TIntegerListList.Create();
        try
          Fill_NewListForDistinctValues(AllIntegers, IntegerListList);
          Print(IntegerListList);
        finally
          Free(IntegerListList);
        end;
      finally
        AllIntegers.Free;
      end;
    end;
    
    begin
      try
        TMain.Run();
      except
        on E: Exception do
          Writeln(E.ClassName, ': ', E.Message);
      end;
    end.