3

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?

Jens Mühlenhoff
  • 14,565
  • 6
  • 56
  • 113
  • I am still wondering what you are trying to achieve with the knowledge if an element is the first or last and it that cannot be solved completely different. – Stefan Glienke Jun 02 '16 at 09:47
  • We are using it for a somewhat unusual form of a [control break](https://en.wikipedia.org/wiki/Control_break). – Jens Mühlenhoff Jun 02 '16 at 14:41
  • As I said there might be another way to do this - for example in the wikipedia article you linked to it speaks of grouping which can be done with TEnumerable.GroupBy. – Stefan Glienke Jun 03 '16 at 08:33

1 Answers1

6

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;
Stefan Glienke
  • 20,860
  • 2
  • 48
  • 102