Jump to content
Dave Novo

Get Index of enumeration in spring4D

Recommended Posts

Hello,

 

Does spring4D have a similar method as the Python enumerate() method? I feel this should be possible, if the "standard enumerator" that is returned now was wrapped inside an enumerator that returned a record that contained the index and the original <T> itself. In fact, this special record could implement record based conversion methods to convert automatically to both an integer and T. 

 

I imagine coding something like this

 

var  myColl:=TCollections.CreateList<TSomeClass>;
  // add some stuff to the list
  
 for var curItem in myColl.EnumerateWithIndices do
 begin
  	// at this point, curItem is really a record, with fields Index, TSomeClass
    someArrayOfInt[i]:=curItem;  // implicit conversion to integer
    SomeClassList.Add(curItem); // adds the TSomeClass that is returned to another list, implicit conversion to TSomeClass
 end;

Does something like this exist?

Edited by Dave Novo

Share this post


Link to post

As a follow up, another key nice addition would be able to do something like this

 

for var curItem in myColl.EnumerateWithIndices([1,5,7,9])

which would return the "indexed enumerator record" for only indexes 1,5,7,9 

For this example, you should be able to pass in any IEnumerator/IEnumerable<integer>

 

or something like

 

for var curItem in myColl.EnumerateWithIndices(
  function (zIdx:integer):Boolean
  begin
    result:=IsOdd(zIdx)
  end
  );

which would return an "indexed enumerator record" for only odd numbered indexes

 

I will see if I can get a working example over the holidays....

 

 

Share this post


Link to post

Hi Stefan,

 

Sorry, I did not notice the .Where gets an index. But .Where does not seem to satisfy the first requirement. I guess I would have to create my own enumerator and capture it in the .Where method.

Share this post


Link to post

First of all, you can already achieve what you asked for in two different ways. (As I said, I will look into adding a similar method as .NET 9 did, but it's not as easy due to Delphi's limitations - it most likely will be a static method on TEnumerable and not on IEnumerable because it returns a differently typed IEnumerable, and that causes the Delphi compiler to complain with E2604.)

 

Apart from the obvious use of a classic for-to loop if you already have an indexable collection such as IList where that new method IMHO would make no sense and just add overhead you can do this:

  var indexedColl := TEnumerable.Zip<Integer,TMyClass>(TEnumerable.Range(0, myColl.Count), myColl);
  for var curItem in indexedColl do
    Writeln('index: ', curItem.Value1, ' - item: ', curItem.Value2.ToString);

If you want more control over index generation you can write this:

  var indexedColl := TEnumerable.Select<TMyClass, Tuple<Integer,TMyClass>>(myColl,
    function(const item: TMyClass; const index: Integer): Tuple<Integer,TMyClass>
    begin
      Result := Tuple<integer,TMyClass>.Create(index, item);
    end);
  for var curItem in indexedColl do
    Writeln('index: ', curItem.Value1, ' - item: ', curItem.Value2.ToString);

If you then want to filter only certain indexes you just call Where on indexedColl:

  var oddIndexes := indexedColl.Where(
    function(const tuple: Tuple<Integer,TMyClass>): Boolean
    begin
      Result := Odd(tuple.Value1);
    end);
  for var curItem in oddIndexes do
    Writeln('index: ', curItem.Value1, ' - item: ', curItem.Value2.ToString);

 

Edited by Stefan Glienke

Share this post


Link to post

Hi Stefan,

 

Firstly, I would like to say I think the Spring4D library is amazing, and you have done an amazing service for the Delphi community.  Please take anything I write merely as a suggestion, not criticism in any way!!!!!! There seems to be little you cannot do in Spring4D if you are clever.

 

I have been coding in Delphi for decades and recently had occasion to start programming a bit in Python. What I am amazed by is how little code I have to type in Python for common things. Of course, there are pros and cons to other aspects of Python (speed etc - that can be dealt with in a variety of other ways) but for the point I am making here it is just about code simplicity and clarity. I love IShared<T> because it removes hundreds of lines of boilerplate try..finally blocks that on one hand are simple, but the other clutter up the code with useless crap that distracts you from what you are trying to do.

 

The python syntax of 

 

new_list = [expression for item in iterable if condition]

 

where new_list is in and of itself an enumerable object that can be plugged into other lists makes the code so simple and clear.

 

Comparing the code

 

var indexedColl := 
    TEnumerable.Select<TMyClass, Tuple<Integer,TMyClass>>
    (myColl,
    function(const item: TMyClass; const index: Integer): Tuple<Integer,TMyClass>
    begin
      Result := Tuple<integer,TMyClass>.Create(index, item);
    end)
    .Where(
    function(const tuple: Tuple<Integer,TMyClass>): Boolean
    begin
      Result := Odd(tuple.Value1);
    end);

 

to the Python Version

 

indexedColl = [y for x,y in enumerate(myColl) if x%2 != 0]

Explanation for non-python people. x is the index, y is the element in myColl at that index.  the if statement at the end filters for odd numbers.

 

Of note, indexedColl can be passed as the enumerator directly into anything that accepts an enumerator.

 

I 100% understand that of course Delphi syntax cannot be as simple as Python we have to be specify the types in the generic specialization somehow, but any way that can be moved towards a more simplistic syntax (at least for common use cases) would be helpful. 

 

Working with Python has been a real eye opener in the fact that you don't have to break your brain with generics to do everything. (you do have to break your brain in other ways, if people don't use type annotations for example and you spend 1 hour figuring out what type a method is expecting - but that is a different story).

 

Even knowing what you are trying to do in the example above, I would really have to study the code diligently to understand exactly how all the pieces fit together to make it work. 

 

 

Share this post


Link to post

I guess I could wrap the above code into something like

 

TEnumerateHelper<T>=class

    function CreateIndexEnumerator(list:IList<T>):<result is whatever the type of the .Select statement is>

 

also, define a type TIndexedEnumItem<T>=Tuple<Integer,T>

 

and consolidate that all to make somewhat simpler code to use on a day to day basis.

 

I think different logic is needed though if the .Where predicate has an integer enumerator passed in.

Share this post


Link to post

By the way, I tried the code, based on @Stefan Glienke example above

 

  var foo:=TCollections.CreateList<string>;

  var indexedColl := TEnumerable.Select<string, Tuple<Integer,string>>(foo,
    function(const item: string ;const index: Integer): Tuple<Integer,string>
    begin
      Result := Tuple<integer,string>.Create(index, item);
    end);


and it does not compile. I think the problem is that TEnumerable.Select expects a TFunc<T,TResult>

 

The following seems to be okay

 

  var foo:=TCollections.CreateList<string>;
  var idx:=0;
  var indexedColl := TEnumerable.Select<string, Tuple<Integer,string>>(foo,
    function(item: string): Tuple<Integer,string>
    begin
      Result := Tuple<integer,string>.Create(idx, item);
      inc(idx);
    end);

 

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×