Beefy Boxes and Bandwidth Generously Provided by pair Networks
Your skill will accomplish
what the force of many cannot
 
PerlMonks  

Tied array to track last-accessed element

by davido (Cardinal)
on Sep 23, 2004 at 02:11 UTC ( [id://393101]=CUFP: print w/replies, xml ) Need Help??

Perl makes it really easy to iterate over elements of an array. Examples include:

foreach my $element ( @array ) { ...... }

...and...

my @newlist = map { ....... } @array;

But there are times when you find yourself in the middle of one of those looping constructs, just wishing that there were a way to know the index of the element that is the subject of the current loop iteration. The most common Perl idioms when this is necessary are:

foreach my $idx ( 0 .. $#array ) { #do something with $array[$idx]; }

...and...

my @newlist = map { # do something with $array[$_] } 0 .. $#array;

But this wouldn't be Perl if there weren't Another Way. What follows is a class called IndexedArray, and an accompanying test snippet. The class should be saved as IndexedArray.pm and put somewhere accessible by use IndexedArray;. To use it, you must tie your array to this class. The class mimicks an ordinary Perl array in every way I know of, but it adds one object method called which(). Whenever it makes sense, which() returns the index of the most recently accessed element of the tied array.

which()'s return value wouldn't really make sense when it follows a splice(), unshift(), pop(), and a few other array actions, and perhaps I should cause it to return undef under such circumstances. The current implementation is that whenever you do something with the array that doesn't result in a clearly defined 'last element accessed', which()'s internal tracking variable is just reset to zero. This is sort of in keeping with the same idea of scalar keys; resetting the each() iterator for hashes.

I'd like to hear what others think of this. If it generates any interest, I may add a what() method (it would return the value of the last element accessed).(added) And if this actually seems useful to anyone, I might gear it up for CPAN as Tie::Array::CurrentIndex, or something along those lines.

First, the module: IndexedArray.pm, and second, the test snippet. Enjoy, and let me know what you think!

package IndexedArray; use strict; our $VERSION = '0.50'; sub TIEARRAY { my $class = shift; bless { ARRAY => [], INDEX => 0 }, $class; } sub FETCHSIZE { my $self = shift; $self->{INDEX} = 0; scalar @{ $self->{ARRAY} }; } sub STORESIZE { my( $self, $size ) = ( $_[0], $_[1]-1 ); $self->{INDEX} = 0; $#{ $self->{ARRAY} } = $size; } sub STORE { my( $self, $index, $value ) = @_; $self->{INDEX} = $index; $self->{ARRAY}[$index] = $value; } sub FETCH { my( $self, $index ) = @_; $self->{INDEX} = $index; $self->{ARRAY}[$index]; } sub CLEAR { my( $self ) = shift; $self->{INDEX} = 0; @{ $self->{ARRAY} } = (); } sub POP { my $self = shift; $self->{INDEX} = 0; pop( @{ $self->{ARRAY} } ); } sub PUSH { my $self = shift; $self->{INDEX} = $#{ $self->{ARRAY} } + scalar( @_ ); push( @{ $self->{ARRAY} }, @_ ); } sub SHIFT { my $self = shift; $self->{INDEX} = 0; shift( @{ $self->{ARRAY} } ); } sub UNSHIFT { my $self = shift; $self->{INDEX} = 0; unshift( @{ $self->{ARRAY} }, @_ ); } sub EXISTS { my( $self, $index ) = @_; $self->{INDEX} = $index; exists( $self->{ARRAY}[ $index ] ); } sub DELETE { my( $self, $index ) = @_; $self->{INDEX} = 0; delete( $self->{ARRAY}[ $index ] ); } sub SPLICE { my( $self ) = shift; my $size = $self->FETCHSIZE(); my $offset = @_ ? shift : 0; $offset += $size if $offset < 0; my $length = @_ ? shift : $size - $offset; $self->{INDEX} = 0; return splice( @{ $self->{ARRAY} }, $offset, $length, @_ ); } sub which { my $self = shift; return $self->{INDEX}; } sub what { my $self = shift; return $self->{ARRAY}[ $self->{INDEX} ]; } sub DESTROY { } sub EXTEND { } 1;

And now a test snippet...


use strict; use warnings; use IndexedArray; my $obj = tie my @array, 'IndexedArray'; print "First test: Store A .. F to \@array and print \@array.\n"; @array = ( 'A' .. 'F' ); print "@array\n\n"; print "Second test: Fetch \$array[3] and discover which " . "element it was.\n" . "$array[3] came from ", $obj->which(), ".\n\n"; print "Third test: Iterate over \@array, and discover which\n" . "element is being accessed in each iteration.\n" . "Use foreach in this test.\n"; iteration_test( \@array ); print "\nFourth test. Sort \@array in reverse order, and repeat " . "the third test.\n"; @array = reverse @array; iteration_test( \@array ); sub iteration_test { foreach my $element ( @{ $_[0] } ) { print "$element came from \$array[" . $obj->which() . "].\n"; } }

Replies are listed 'Best First'.
Re: Tied array to track last-accessed element
by japhy (Canon) on Sep 23, 2004 at 19:48 UTC
    My only comment is to purvey the methods as functions, which is what I do in most of my tie-related modules:
    package Tie::Array::LastIndex; # ... use Carp 'croak'; use Exporter; @EXPORT = qw( which what ); # ... sub which (\@) { my $aref = shift; my $obj = tied @$aref; croak "which() only accepts arrays tied to " . __PACKAGE__ unless $obj and $obj->isa(__PACKAGE__); return $obj->{INDEX}; } sub what (\@) { my $aref = shift; my $obj = tied @$aref; croak "what() only accepts arrays tied to " . __PACKAGE__ unless $obj and $obj->isa(__PACKAGE__); return $obj->{ARRAY}[ $obj->{INDEX} ]; }
    Like so.
    _____________________________________________________
    Jeff japhy Pinyan, P.L., P.M., P.O.D, X.S.: Perl, regex, and perl hacker
    How can we ever be the sold short or the cheated, we who for every service have long ago been overpaid? ~~ Meister Eckhart

      The way you've got them set up, which() and what() can only be used as functions, not as methods, right? I have mixed feelings on the suggestion. I'll express what I see as the pros and cons, and maybe you can let me know what I'm leaving out.

      Pros to exporting functions for this tied array class:

      • Simpler interface -- Doesn't require Object Oriented understanding.
      • Faster -- Object oriented solutions have extra overhead (but a tied array is an object anyway, and the functional which() and what() do even more extra work in discovering the object of the tie).

      Pros to using the OO interface:

      • which() and what() are not exactly uncommon words. Reserving them as object methods keeps them from mucking up global namespace.
      • which() and what(), as names, make more sense to me when they're directed to a tied object, than to an array reference. $tied_array->which() means more to me than which(@array), because it tells me (the casual reader) that which() is a method of this class, rather than just some exported funtion from who-know-which package.

      But I am not necessarily seeing the whole picture, and I value your opinion, so please do let me know what arguments would best support exporting which() and what() as functions. ...If I were to go ahead an implement that suggestion, I might export them with more explicit names, such as which_index() and what_value()... or some-such.

      I like your use of Carp croak(). I'll look at working that into the code in the meantime.


      Dave

        The main reason I create functions instead of methods for my tied data types is because tying variables is supposed to be a transparent act, and calling a function with that array should be just as transparent.
        _____________________________________________________
        Jeff japhy Pinyan, P.L., P.M., P.O.D, X.S.: Perl, regex, and perl hacker
        How can we ever be the sold short or the cheated, we who for every service have long ago been overpaid? ~~ Meister Eckhart
Re: Tied array to track last-accessed element
by Aristotle (Chancellor) on Sep 26, 2004 at 12:22 UTC

    You could be so much lazier.

    #!/usr/bin/perl use strict; use warnings; use Tie::Array; package Tie::Array::LastAccessed; use base qw( Tie::StdArray ); my %last_accessed; for my $methname ( qw( STORE FETCH ) ) { my $supername = "SUPER::$methname"; my $glob = do { no strict 'refs'; \*{ $methname } }; *$glob = sub { my $self = shift; my ( $idx ) = @_; $last_accessed{ 0 + $self } = $idx; $self->$supername( @_ ); }; } sub which { my $self = shift; $last_accessed{ 0 + $self }; } sub what { my $self = shift; $self->FETCH( $last_accessed{ 0 + $self } ); } sub DESTROY { my $self = shift; delete $last_accessed{ 0 + self }; $self->SUPER::DESTROY(); } 1;

    Makeshifts last the longest.

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: CUFP [id://393101]
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others about the Monastery: (4)
As of 2024-04-25 20:55 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found