The following snippet creates a class: Tie::Hash::PEach which allows you to set the starting point for each iterating. The starting point is set in terms of a zero-based index.

Hashes don't carry any predictable order, but unless a hash changes its keys, order does remain stable. So if you know the order and care about the point at which you begin your iterations, this snippet will allow for custom entry points.

The other primary goal of this snippet is a proof-of-concept to demonstrate (mostly to myself) how one might go about providing for a localizable 'each' iterator. The idea here is that if you localize the instance variable $obj->[1]{ITER}, you can have two separate 'each' iterator mechanisms going on, each in their own scope, without interfering with each other. This seems to be of more use than the ability to set the 'each' index. I wanted to figure out a way to allow an 'each' call in one sub's scope to act independantly of an 'each' call for the same hash in another sub's scope. This snippet accomplishes that.

I'm interested in any comments. This was intended to be a learning experience for me.

The snippet below includes a test to demonstrate the concept of localized each iterators. The class methods contain some unnecessarily explicit constructs just to make things clear. (Clearer than they appear in the Tie::Hash module.)

Update: Just wanted to credit tye for thinking of what to name the class, and Limbic~Region for getting me thinking about this topic.

package Tie::Hash::PEach; use strict; use warnings; sub FIRSTKEY { my( $self ) = shift; my( %hash ) = %{ $self->[0] }; my( $iterator ) = $self->[1]{ITER}; my( @keys ) = keys %hash; $iterator = 0 unless defined $iterator; my( @pair ) = ( $keys[ $iterator ], $hash{ $keys[ $iterator ] } ); $iterator = ( $iterator < $#keys ) ? $iterator + 1 : undef; $self->[1]{ITER} = $iterator; @{$self->[1]{KEYS}} = @keys; return wantarray ? @pair : $pair[0]; } sub NEXTKEY { my( $self ) = shift; my( %hash ) = %{ $self->[0] }; my( $iterator ) = $self->[1]{ITER}; my( @keys ) = @{ $self->[1]{KEYS} }; $iterator = 0 unless defined $iterator; my( @pair ) = ( $keys[ $iterator ], $hash{ $keys[ $iterator ] } ); if ( $iterator < $#keys ) { $iterator++; $self->[1]{ITER} = $iterator; return wantarray ? @pair : $pair[0]; } else { $iterator = undef; $self->[1]{ITER} = $iterator; return undef; } } sub TIEHASH { bless [ {}, { ITER => undef, KEYS => [] } ], shift; } sub STORE { my( $self, $key, $value ) = @_; ${$self->[0]}{$key} = $value; } sub FETCH { my( $self, $key ) = @_; return ${$self->[0]}{$key}; } sub EXISTS { my( $self, $key ) = @_; return exists ${$self->[0]}{$key}; } sub DELETE { my( $self, $key ) = @_; delete ${$self->[0]}{$key}; } sub CLEAR { my $self = shift; %{$self->[0]} = (); } sub SCALAR { my $self = shift; scalar %{$self->[0]}; } 1; package main; use strict; use warnings; use Data::Dumper; my $obj = tie my %hash, "Tie::Hash::PEach"; %hash = qw/one 1 two 2 three 3 four 4 five 5 six 6/; print "\n\nThe order of \%hash:\n"; print Dumper \%hash; { print "\n\nLocalized 'each' instance iterator for \%hash:\n"; # To change 'each' iterator for %hash, set $obj->[1]{ITER}. # Remember, the first element is 0. local $obj->[1]{ITER} = 2; my ( $key, $value ) = each %hash; print "$key => $value\n"; } print "\n\nNonlocalized 'each' instance iterator for \%hash:\n"; my ( $key, $value ) = each %hash; print "$key => $value\n";

Replies are listed 'Best First'.
Re: Localizable / customizable 'each' iterator for hashes
by Limbic~Region (Chancellor) on Jul 19, 2004 at 19:36 UTC
    davido,
    but unless a hash changes its keys, order does remain stable

    From perldoc -f each :
    Since Perl 5.8.1 the ordering is different even between different runs of Perl for security reasons

    In your case, it doesn't matter because each run can have its own iterator.
    You also prefaced that with but unless a hash changes its keys which you shouldn't do under most circumstances.

    The problem is that perldoc -f each also says:
    It is always safe to delete the item most recently returned by "each()"

    So as long as all someone is doing is looking at the hash....

    Cheers - L~R

    This is paraphrased from a /msg conversation between davido and I put here for everyone's benefit

      Yes, you are correct. Since there is only one hash, deleting an element while maintaining multiple iteration sequences is going to cause a problem. While iterating, this snippet expects that the next key is still there.


      Dave

Re: Localizable / customizable 'each' iterator for hashes
by ysth (Canon) on Jul 19, 2004 at 23:28 UTC
    This looks wrong to me:
    sub FIRSTKEY { ... $iterator = 0 unless defined $iterator;
    Tied hash iterators have a strange division of responsibility: perl's actual iterator on the tied HV is responsible for tracking whether FIRSTKEY or NEXTKEY should be called, and the tied class is responsible for determining which key should be returned next. This allows things like void context keys() or values() to reset the iterator without actually calling any tie functions. So FIRSTKEY should always start at the beginning.
      The idea is that if the iterator already has a value, I don't want to change that value automatically blindly. I'm allowing the iterator to be pre-set prior to the first call to FIRSTKEY. The only time I want to set the iterator to zero, is if it has already reached the last key (I do this check elsewhere), or if it has not yet been set at all. Try it out, it works. :)

      By the way, I should mention that this class is pretty much unneeded. Anytime the functionality of this type of class could be needed, it could be accomplished more easily by creating an external keys list with keys, and iterating over that list. I really just wanted to see if I could figure out how to make it work internally via each. It works, but it's ugly and unwieldy.


      Dave

        Sorry, I should have taken time to try it (which I still have not yet done :).