Search code examples
delphidelphi-xe7enumeratorspring4d

How to implement a Enumerator that has IsFirst and IsLast functions?


I am using Spring4D for all collections.

Now there is a situation where I have to know whether the current value of the enumerator is the first (which is easy) or the last (which is hard) in the collection.

program Project1;

{$APPTYPE CONSOLE}
{$R *.res}

uses
  System.SysUtils,
  Spring.Collections;

var
  Enumerable: IEnumerable<Integer>;
  Enumerator: IEnumerator<Integer>;

begin
  Enumerable := TEnumerable.Query<Integer>(TArray<Integer>.Create(1, 2, 3, 4, 5)
    ) as IEnumerable<Integer>;
  Enumerator := Enumerable.GetEnumerator;
  while Enumerator.MoveNext do
  begin
    WriteLn('Value = ', Enumerator.Current);
    WriteLn('First in collection? ', Enumerator.CurrentIsFirst);
    WriteLn('Last in collection? ', Enumerator.CurrentIsLast);
  end;
  ReadLn;

end.

CurrentIsFirst could be implemented using a local Boolean which is reset once the first value has passed.

However I don't know an easy way to implement CurrentIsLast.

It should be able to process lazy collections as they may contain too many values to fit into memory.

How can I implement such a CurrentIsLast function?


Solution

  • Just use a flag during the iteration:

    if Enumerator.MoveNext then
    begin
      flag := True;
      repeat
        WriteLn('Value = ', Enumerator.Current);
        WriteLn('First in collection? ', flag);
        flag := not Enumerator.MoveNext;
        WriteLn('Last in collection? ', flag);
      until flag;
    end;
    

    This is the basic algorithm but you can put that into a decorator for IEnumerator<T> to provide IsFirst/IsLast - you just need to buffer the current element and look one ahead to see if the current one is the last.

    type
      IEnumeratorEx<T> = interface(IEnumerator<T>)
        function IsFirst: Boolean;
        function IsLast: Boolean;
      end;
    
      TEnumeratorState = (Initial, First, Only, Running, Last, Finished);
      TEnumeratorEx<T> = class(TEnumeratorBase<T>, IEnumeratorEx<T>)
      private
        fSource: IEnumerator<T>;
        fCurrent: T;
        fState: TEnumeratorState;
        function IsFirst: Boolean;
        function IsLast: Boolean;
      protected
        function GetCurrent: T; override;
        function MoveNext: Boolean; override;
      public
        constructor Create(const source: IEnumerator<T>);
      end;
    
    constructor TEnumeratorEx<T>.Create(const source: IEnumerator<T>);
    begin
      inherited Create;
      fSource := source;
    end;
    
    function TEnumeratorEx<T>.GetCurrent: T;
    begin
      Result := fCurrent;
    end;
    
    function TEnumeratorEx<T>.IsFirst: Boolean;
    begin
      Result := fState in [First, Only];
    end;
    
    function TEnumeratorEx<T>.IsLast: Boolean;
    begin
      Result := fState in [Only, Last];
    end;
    
    function TEnumeratorEx<T>.MoveNext: Boolean;
    begin
      case fState of
        Initial:
          if fSource.MoveNext then
            fState := First
          else
            fState := Finished;
        First:
          fState := Running;
        Only, Last:
          fState := Finished;
      end;
    
      Result := fState <> Finished;
      if Result then
      begin
        fCurrent := fSource.Current;
        if not fSource.MoveNext then
          Inc(fState);
      end;
    end;