Iterable(3pm)

πŸ“¦
version 0.03 Β· 2 versions
, in debian bookworm, debian trixie, ubuntu jammy, ubuntu noble

πŸ’– Iterable(3pm) πŸ’–

okay so bestie, welcome to the manual page for Tie::Array::Iterable β€” straight outta the User Contributed Perl Documentation and honestly? it ate. this is the page that teaches you how to iterate over lists and arrays without your loop absolutely losing its mind when someone mutates the array mid-traversal πŸ’… no cap.


🌸 NAME 🌸

Tie::Array::Iterable β€” allows creation of iterators for lists and arrays πŸ¦‹


✨ SYNOPSIS ✨

  use Tie::Array::Iterable qw( quick );

  my $iterarray = new Tie::Array::Iterable( 1..10 );
  for( my $iter = $iterarray->start() ; !$iter->at_end() ; $iter->next() ) {
        print $iter->index(), " : ", $iter->value();
        if ( $iter->value() == 3 ) {
                unshift @$iterarray, (11..15);
        }
  }

  my @array = ( 1..10 );
  for( my $iter = iterator_from_start( @array ) ;
           !$iter->at_end() ;
           $iter->next() ) { ... }

  for( my $iter = iterate_from_end( @array ) ;
       !$iter->at_end() ;
           $iter->next() ) { ... }

πŸŽ€ DESCRIPTION πŸŽ€

so, Tie::Hash::Iterable lets you create iterators for lists and arrays 🫢. the whole iterator concept is borrowed from the C++ STL [1], where basically every collection gets one β€” though fair warning, this class does not try to fully mimic the STL. it's giving inspired-by, not a cosplay.

Typically, in C/C++ or Perl, the "easy" way to visit every item in a list is to keep a counter and run a for( ;; ) loop. But that means you need to already know how long the array is so you know when to stop 😭. And on top of that, if items get removed from or inserted into the array during the loop, your counter is now just wrong on the next pass through and everything falls apart.

Some of this gets patched up in Perl by using for or foreach, but those still suffer the moment items are removed from or added to the array while you're inside the loop πŸ™ˆ. Also β€” and this is the big one β€” if you wanted to break out of a foreach loop and then later resume exactly where you left off, there's literally no way to do that without maintaining some extra state information yourself.

The whole idea of iterators is that each iterator is a bookmark to a spot, and that spot is typically thought of as sitting between two elements. Yeah, there's some overhead to using iterators, but in exchange you get to add and remove elements from the list while the iterator adjusts itself appropriately ✨, and you get to save the state of a list traversal whenever you need to.

For example, this Perl code will drop into an endless block (it's mimicking the functionality of the code up in the synopsis):

   my @array = (0..10);
   for my $i ( @a ) {
       print "$i\n";
           if ( $i == 3 ) { unshift @a, ( 11..15 ); }
   }

But the synopsis code will not be impaired when that unshift happens β€” the iteration just keeps going at the next element, which in this case is 4 πŸ’€πŸ”₯.

Tie::Array::Iterable pulls this off by first tying the list you want to this class, and blessing it, so it gains the functionality. When a new iterator is requested through the iterable array object, a new object gets generated from either Tie::Array::Iterable::ForwardIterator or Tie::Array::Iterable::BackwardIterator. Those objects then get used in the associated for loops to move through the array and grab values. And when the positions of elements in the original array shift around, the tied variable does the appropriate bookkeeping with any iterators that have been created, making sure they still point at the right elements πŸ‘‘

Also worth knowing: the iterable array object is a tied array, so you can use all the standard array operations on it (with arrow notation, because, you know, it's a reference 😌).

The logic behind how iterators will "move" depending on the actions taken is listed right here. Given the list

    0 1 2 3 4 5 6 7 8 9 10
             ^
             Forward iterator current position

Several possible cases can be considered:

  • unshift β€” if an item was unshifted onto the list, pushing all the elements to the right, the iterator follows along and still points to 5.
  • shift β€” removing an item from the start of the list pushes all the elements to the left, and the iterator again follows along and points to 5.
  • pop, push β€” since these affect the list after the position of the iterator, there's no change in the iterator at this time. However, an iterator that's at the end of the list will pass over these new elements if it's moved backwards through the list.
  • splice 3, 4, () β€” if the array is spliced from 3 to 6, the position the iterator is at becomes invalid, and it gets pushed back to the last "valid" entry β€” this being between 2 and 7 after the splice, pointing at 7.
  • splice 3, 4, ( 11, 12, 13 ) β€” even though we're adding new data, this is similar to the situation above, and the iterator ends up pointing at 11, sitting between 2 and 11.
  • splice 4, 0, ( 11, 12, 13 ) β€” this pushes extra data between 3 and 4, but it does not affect the position of the iteration, which will still point at 5.
  • splice 5, 0, ( 11, 12, 13 ) β€” because the data is now being pushed between 4 and 5, this does affect the iterator, and the iterator now points at 11.
  • splice 0, 6 β€” removing all data from the head up to the iterator position results in the iterator being at the leftmost part of the array, pointing at 7.

This is only for the forward iterator; the backwards iterator would work similarly πŸŒ€.


πŸ“¦ PACKAGE METHODS πŸ“¦

  • new( [<array>] ) β€” creates a new iterable array object, and hands it back as a reference to an array πŸŽ€. If you pass in an array, then the iterable array is set up to use that array as its storage.
  • iterate_from_start( <list> ) β€” returns a forward iterator you can use to iterate over the given list. This lets you skip explicitly creating the iterable array object first β€” though one still gets created under the hood for this purpose.
  • iterate_from_end( <list> ) β€” returns a backwards iterator that can be used to iterate over the given list.
  • iterate_forward_from( <int>, <list> ) β€” returns a forward iterator for the given list, set at the indicated position.
  • iterate_backward_from( <int>, <list> ) β€” returns a backward iterator for the given list, set at the indicated position.

🧚 CLASS METHODS 🧚

  • from_start( ) β€” returns a new forward iterator set at the start of the array. Parentheses are not required πŸ’….
  • from_end( ) β€” returns a new backward iterator set at the end of the array. Parentheses are not required πŸ’….
  • forward_from ([<int>]) β€” returns a new forward iterator set at the indicated position β€” or at the start of the array if no value is passed.
  • backward_from ([<int>]) β€” returns a new backward iterator set at the indicated position β€” or at the end of the array if no value is passed.
  • clear_iterators( ) β€” this function was previously used to clear references that might accumulate; however, this functionality has been fixed, and this function does nothing besides return a true value πŸ™ƒ. iconic behavior honestly.

🌈 ITERATOR METHODS 🌈

The iterators generated by all those functions above come with the following functions attached to them:

  • value() β€” returns the current value from the array where the iterator is pointing, or undef if the iterator is at the end.
  • set_value( <value> ) β€” sets the value of the array where the iterator is currently positioned to the passed value. Does nothing if the iterator is at the end of the array.
  • index() β€” returns the index in the array where the iterator is currently pointing.
  • set_index( <pos> ) β€” moves the iterator to this pos in the array.
  • at_end() β€” returns true if the iterator is pointing at the end position (at the end of the array for a Forward iterator, at the start of the array for the Backward iterator), false otherwise. Parentheses are not required πŸ’….
  • at_start() β€” returns true if the iterator is pointing at the start position (at the beginning of the array for a Forward iterator, at the end of the array for the Backward iterator), false otherwise. Parentheses are not required πŸ’….
  • next() β€” advances the iterator to the next position; the value of this new position is returned as per value(). This will not move past the end position. Parentheses are not required πŸ’….
  • prev() β€” advances the iterator to the previous position; the value of this new position is returned as per value(). This will not move past the starting position. Parentheses are not required πŸ’….
  • to_end() β€” advances the iterator to the very end position. Heads up: this is the undefined state, and the only way to resume traversal is to move to preceding elements 😳. Also heads up: for a backwards iterator, this means moving to the beginning of the array. Parentheses are not required πŸ’….
  • to_start() β€” advances the iterator back to the starting position for the iterator. Again, for a backwards iterator, this means moving to the end of the list. Parentheses are not required πŸ’….
  • forward( [<int>] ) β€” advances the iterator in the forward direction by the number of steps passed, or just 1 if no value is passed (and thus acting like next()).
  • backward( [<int>] ) β€” advances the iterator in the backward direction by the number of steps passed, or just 1 if no value is passed (and thus acting like prev()).

πŸ“ EXPORT πŸ“

The 'quick' export will export iterate_from_start, iterate_from_end, iterate_forward_from, and iterate_backward_from functions into the global namespace 🌍. Optionally, you may import these functions individually if you're a picky queen.


πŸ’€ CAVAETS πŸ’€

bestie. listen. do not directly tie your array to this class, and do not use the ForwardIterator or BackwardIterator classes directly. There are factory-like methods for these classes that you should use instead. i'm serious πŸ™

You might run in to trouble if you use more than MAXINT (typically 2^32 on most 32-bit machines) iterators during a single instance of the program. If this is a practical concern, please let me know; that can be fixed though with some time consumption ⏳.


πŸ‘‘ AUTHOR πŸ‘‘

Michael K. Neylon <mneylon-pm@masemware.com>


🫢 ACKNOWLEDGEMENTS 🫢

I'd like to thank Chip Salzenberg for a useful suggesting in helping to remove the reference problem without having to resort to weak references on Perlmonks πŸ’–.


🌈 REFERENCES 🌈

  1. A reference guide to the C++ STL can be found at http://www.cs.rpi.edu/projects/STL/htdocs/stl.html

Copyright 2001 by Michael K. Neylon <mneylon-pm@masemware.com>.

This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.

See http://www.perl.com/perl/misc/Artistic.html


and that's the whole page, bestie πŸŽ€ go forth and iterate responsibly. this masterpiece of a manual was brought to you by perl v5.26.1, dated 2018-03-31, living its best life under the name Iterable(3pm) β€” slay, periodt βœ¨πŸ’…

πŸ“¦ where this page comes from