Saturday, March 10, 2007

Fun with enumerators, part 6 - generators

[Part 1 - Introduction, Part 2 - Additional enumerators, Part 3 - Parameterized enumerators, Part 4 - External enumerators, Part 5 - Class helper enumerators.]

This was a long and interesting trip but it has to end some day. And it will end today. Just one last small topic to cover ...

in Part 4 I mentioned that we could abuse enumerators to the point where they are not working on any external structure. My example for such generator which is providing its own enumeration data was

for i in Fibonacci(10) do
Writeln(i);


and I promised to show you how to write it.



Again, we will start with a 'standard' enumerator factory, the one that we used a lot in parts 4 and 5.

  IEnumFibonacciFactory = interface
function GetEnumerator: TEnumFibonacci;
end;

TEnumFibonacciFactory = class(TInterfacedObject, IEnumFibonacciFactory)
private
FUpperBound: integer;
public
constructor Create(upperBound: integer);
function GetEnumerator: TEnumFibonacci;
end;

function Fibonacci(upperBound: integer): IEnumFibonacciFactory;


Enumerator is slightly trickier this time - it prepares requested data in the constructor and uses MoveNext to move over this data.

  TEnumFibonacci = class
private
FFibArray: array of integer;
FIndex: integer;
public
constructor Create(upperBound: integer);
function GetCurrent: integer;
function MoveNext: boolean;
property Current: integer read GetCurrent;
end;

constructor TEnumFibonacci.Create(upperBound: integer);
var
i: integer;
begin
SetLength(FFibArray, upperBound);
if upperBound >= 1 then
FFibArray[0] := 1;
if upperBound >= 2 then
FFibArray[1] := 1;
for i := 2 to upperBound - 1 do
FFibArray[i] := FFibArray[i-1] + FFibArray[i-2];
FIndex := -1;
end;

function TEnumFibonacci.GetCurrent: integer;
begin
Result := FFibArray[FIndex];
end;

function TEnumFibonacci.MoveNext: boolean;
begin
Result := FIndex < High(FFibArray);
if Result then
Inc(FIndex);
end;


Test code follows the well-learned pattern.

procedure TfrmFunWithEnumerators.btnFibonacciClick(Sender: TObject);
var
i : integer;
ln: string;
begin
ln := '';
for i in Fibonacci(10) do begin
if ln <> '' then
ln := ln + ', ';
ln := ln + IntToStr(i);
end;
lbLog.Items.Add('Generator: ' + ln);
end;


And now it really is a time to say goodbye. At least to this series - I intend to write many more blog entries. It was an interesting experience for me and I hope an interesting reading for you, dear reader. If you liked it, tell that to others so that they may enjoy it too.



[A full source code of the demo program including all enumerators is available at http://17slon.com/blogs/gabr/files/FunWithEnumerators.zip]





Technorati tags: , ,

12 comments:

  1. Anonymous23:10

    It would be nice to a have a article which contains all blog article in one article

    ReplyDelete
  2. Are you sure? It would be rather lenghty.

    Truth is, I'm a magazine writer in first place and blogger only in second. If 'Fun with enumerators' would be a magazine article, each post with be written under one subheading of this article. But on the web, short form is better - or at least I think it is.

    What if I combine all posts into one PDF file?

    What do other readers think?

    ReplyDelete
  3. Usually I avoid heavy PDF when Googling for technical information.
    But I aggree that navigation in a blog is not always optimal.

    May be publishing somme part in i.e "Torry's Delphi Pages tips" or other sites may help to advertise your very didactic topic on enumerator.

    ReplyDelete
  4. On the article debate - personally, I think the blog form is perfect for this sort of thing.

    That said, just on the code - in the current sort of case, do you actually need a separate factory class? At least, would the following work? (I don't have D2006/7 to check):

    IFibonacciEnum = interface
    function GetCurrent: Integer;
    function MoveNext: Boolean;
    property Current: Integer read GetCurrent;
    end;

    IGetFibonacciEnum = interface
    function GetEnumerator: IFibonacciEnum;
    end;

    TFibonacciEnum = class(TInterfacedObject, IFibonacciEnum, IGetFibonacciEnum)
    private
    FFibArray: array of Integer;
    FIndex: Integer;
    protected
    function GetEnumerator: IFibonacciEnum;
    function GetCurrent: Integer;
    function MoveNext: Boolean;
    public
    constructor Create(UpperBound: Integer);
    end;

    function TFibonacciEnum.GetEnumerator: IFibonacciEnum;
    begin
    Result := Self;
    end;

    //...rest of your code for Create, GetCurrent and MoveNext

    function Fibonacci(UpperBound: Integer): IGetFibonacciEnum;
    begin
    Result := TFibonacciEnum.Create(UpperBound);
    end;

    ReplyDelete
  5. Good idea! Of course the enumerator can be its own factory.

    I tested your code in BDS 2006 and it works fine.

    ReplyDelete
  6. Anonymous13:47

    Hi,

    I've read all of your posting here with enumerators and I like it very much. I was not aware that for..in..do is possible.
    Anyway, may I ask for help? I've existing code which I would like to enhance using your tips but before starting to rebuild everything I would like to get some opinion. I've shorten the coding and remove some of the properties but the main purpose is visible, I guess:

    TPWCNoticeItem = class(TCollectionItem)
    private
    public
    property NoticeID : TPWCId read FId write FId;

    TPWCNoticeList = class(TCollection)
    private
    function Get(IDX : Integer): TPWCNoticeItem;
    procedure Put(IDX: Integer; const ITEM : TPWCNoticeItem);
    public
    constructor Create;
    function Add : TPWCNoticeItem;
    property Info[IDX : Integer] : TPWCNoticeItem read Get write Put; default;
    destructor Destroy; override;
    end;

    So where would you start to rebuild this coding or better is it even possible?
    Perhaps you could give me a starting point? It must not be a complete coding but some insights would be really helpful.

    Thanks,
    Michael

    ReplyDelete
  7. I assume you want to iterate over TPWCNoticeList and return TPWCNoticeItems? IOW, you want to do this:

    var
    item: TPWCNoticeItem;
    list: TPWCNoticeList;

    // create and fill the 'list' somehow
    for item in list do
    // do something with item

    ?

    First you have to add a public GetEnumerator function to the TPWCNoticeList class. It must return some class responsible for enumeration (TPWCNoticeListEnumerator, for example).

    Then you have to create this TPWCNoticeListEnumerator class and implement MoveNext, GetCurrent and Current in it - just like in all my examples.

    GetEnumerator would create a new instance of the TPWCNoticeListEnumerator class while passing 'Self' to the constructor. TPWCNoticeListEnumerator would store this reference to the TPWCNoticeList class so it can be used for enumeration.

    You can check how the enumerator for TGpIntegerList is implemented in my GpLists unit.

    ReplyDelete
  8. Anonymous11:18

    Thanks for this reply and I got this running w/o any further problems.

    Do you know if I can use enumarations/iterations this way, too:
    for i := NoticeList.Items.Count -1 downto 0 do [something]

    As the help does not mention it, I guess it is not possible at all, right?

    Cu,
    Michael

    ReplyDelete
  9. If I understand correctly, you want to write enumerator that will walk through your list in reverse order?

    No problems here - just initialize your enumeration class (TPWCNoticeListEnumerator) to the end of the list and then proceed towards beginning in MoveNext. I did something very similar in Part 4.

    If you need both enumerators - one forward and one reverse - implement the latter via additional property, as I did in Part 2.

    ReplyDelete
  10. fast forward to 2010 and I'm wondering if you can't write a generic enumerator. I know I'm tempted :)
    I just wonder if it's as easy to implement as it is to describe... TEnumerator assuming that TMyClass has an array property, find that by RTTI magic and away you go.

    ReplyDelete
  11. It seems I'm late to the party; but better late than never.

    I was actually fully looking forward to you demonstrating how something like a Fibonacci enumerator doesn't even need an underlying collection! But then you went and made your code unnecessarily complicated by creating and constructing a Fibonacci array. :(

    You may want to consider the following implementation of MoveNext instead. The rest should fall nicely into place for a much simpler overall solution with less memory overhead. ;)

    function TEnumFibonacci.MoveNext: Boolean;
    var
    LNext: Integer;
    begin
    LNext := FCurrent + FPrev;
    FPrev := FCurrent;
    FCurrent := LNext;
    Result := FCurrent <= FUpperBound;
    end;

    ReplyDelete
    Replies
    1. You are correct, this would indeed be a better way to do it!

      Delete