2016-06-01 5 views
3

Я использую Spring4D для всех коллекций.Как реализовать перечислитель с функциями IsFirst и IsLast?

Теперь возникает ситуация, когда я должен знать, является ли текущее значение перечислителя первым (что легко) или последним (что сложно) в коллекции.

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 может быть реализован с использованием локального Boolean, который сбрасывается после того, как первое значение прошло.

Однако я не знаю простого способа реализовать CurrentIsLast.

Он должен иметь возможность обрабатывать ленивые коллекции, так как они могут содержать слишком много значений для вставки в память.

Как я могу реализовать такую ​​функцию CurrentIsLast?

+0

Мне до сих пор интересно, чего вы пытаетесь достичь с помощью знания, если элемент является первым или последним, и он не может быть полностью решен. –

+0

Мы используем его для несколько необычной формы [перерыва управления] (https://en.wikipedia.org/wiki/Control_break). –

+0

Как я уже сказал, может быть другой способ сделать это - например, в связанной с ней статье в википедии говорится о группировке, которую можно сделать с помощью TEnumerable.GroupBy. –

ответ

6

Просто используйте флаг во время итерации:

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; 

Это базовый алгоритм, но вы можете положить, что в декоратор для IEnumerator<T> обеспечить IsFirst/IsLast - вам просто нужно амортизировать текущий элемент и посмотреть один впереди, чтобы увидеть, является ли текущий последний.

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;