package Pcore::Util::Hash::HashArray; use Pcore -const; use Pcore::Util::Scalar qw[refaddr]; const our $HASH => 0; const our $KEYS => 1; const our $VALS => 2; const our $TIED_HASH => 3; const our $TIED_ARRAY => 4; use overload # '%{}' => sub { return $_[0]->$*->[$TIED_HASH]; }, '@{}' => sub { return $_[0]->$*->[$TIED_ARRAY]; }, fallback => 1; sub new ($self) { $self = bless \[ {}, # hash, key => idx [], # array, keys [], # array, values {}, # tied hash [], # tied array ], $self; tie $self->$*->[$TIED_HASH]->%*, 'HashArray::_TIED_HASH', $self->$*; tie $self->$*->[$TIED_ARRAY]->@*, 'HashArray::_TIED_ARRAY', $self->$*; return $self; } sub rand_key ($self) { my $keys = $self->$*->[$KEYS]; return $keys->[ rand $keys->@* ]; } sub rand_val ($self) { my $values = $self->$*->[$VALS]; return $values->[ rand $values->@* ]; } sub key_at ( $self, $idx ) { return $self->$*->[$KEYS]->[$idx]; } sub TO_DUMP ( $self, $dumper, @ ) { my %args = ( path => undef, splice @_, 2, ); my $tags; my $data = $self->$*; return $dumper->_dump( [ map {qq[$data->[$KEYS]->[$_] = $data->[$VALS]->[$_]]} 0 .. $data->[$KEYS]->$#* ] ), $tags; } package HashArray::_TIED_HASH; use Pcore -const; use Pcore::Util::Scalar qw[weaken]; sub TIEHASH ( $self, $data_ref ) { weaken $data_ref; return bless \$data_ref, $self; } sub STORE { my $data = $_[0]->$*; if ( exists $data->[$HASH]->{ $_[1] } ) { $data->[$VALS]->[ $data->[$HASH]->{ $_[1] } ] = $_[2]; } else { push $data->[$KEYS]->@*, $_[1]; push $data->[$VALS]->@*, $_[2]; $data->[$HASH]->{ $_[1] } = $data->[$KEYS]->$#*; } return; } sub EXISTS { return exists $_[0]->$*->[$HASH]->{ $_[1] }; } sub FETCH { my $data = $_[0]->$*; if ( exists $data->[$HASH]->{ $_[1] } ) { return $data->[$VALS]->[ $data->[$HASH]->{ $_[1] } ]; } else { push $data->[$KEYS]->@*, $_[1]; push $data->[$VALS]->@*, undef; $data->[$HASH]->{ $_[1] } = $data->[$KEYS]->$#*; return undef; ## no critic qw[Subroutines::ProhibitExplicitReturnUndef] } } sub DELETE { my $data = $_[0]->$*; my $idx = delete $data->[$HASH]->{ $_[1] }; # key was found and deleted from the hash if ( defined $idx ) { # last element was deleted if ( $idx == $data->[$KEYS]->$#* ) { # delete last key pop $data->[$KEYS]->@*; # delete and return last value return pop $data->[$VALS]->@*; } # hash has other elements else { my $val = $data->[$VALS]->[$idx]; my $last_key = pop $data->[$KEYS]->@*; # move last element to the idx of the deleted element $data->[$HASH]->{$last_key} = $idx; $data->[$KEYS]->[$idx] = $last_key; $data->[$VALS]->[$idx] = pop $data->[$VALS]->@*; return $val; } } else { return undef; ## no critic qw[Subroutines::ProhibitExplicitReturnUndef] } } sub CLEAR { my $data = $_[0]->$*; $data->[$HASH]->%* = (); $data->[$KEYS]->@* = (); $data->[$VALS]->@* = (); return; } sub FIRSTKEY { my $data = $_[0]->$*; # reset iterator scalar keys $data->[$HASH]->%*; return each $data->[$HASH]->%*; } sub NEXTKEY { return each $_[0]->$*->[$HASH]->%*; } sub SCALAR { return scalar $_[0]->$*->[$HASH]->%*; } package HashArray::_TIED_ARRAY; use Pcore; use Pcore::Util::Scalar qw[weaken]; sub TIEARRAY ( $self, $data_ref ) { weaken $data_ref; return bless \$data_ref, $self; } sub EXISTS { return exists $_[0]->$*->[$VALS]->[ $_[1] ]; } sub FETCH { return $_[0]->$*->[$VALS]->[ $_[1] ]; } sub FETCHSIZE { return scalar $_[0]->$*->[$VALS]->@*; } sub DELETE { return delete $_[0]->$*->[$VALS]->[ $_[1] ]; } sub POP { my $data = $_[0]->$*; my $key = pop $data->[$KEYS]->@*; if ( defined $key ) { delete $data->[$HASH]->{$key}; return pop $data->[$VALS]->@*; } else { return undef; ## no critic qw[Subroutines::ProhibitExplicitReturnUndef] } } sub SHIFT { my $data = $_[0]->$*; my $key = shift $data->[$KEYS]->@*; if ( defined $key ) { # delete current key delete $data->[$HASH]->{$key}; # delete current val my $val = shift $data->[$VALS]->@*; # move last key if ( defined( my $last_key = pop $data->[$KEYS]->@* ) ) { $data->[$HASH]->{$last_key} = 0; unshift $data->[$KEYS]->@*, $last_key; unshift $data->[$VALS]->@*, pop $data->[$VALS]->@*; } return $val; } else { return undef; ## no critic qw[Subroutines::ProhibitExplicitReturnUndef] } } sub CLEAR { my $data = $_[0]->$*; $data->[$HASH]->%* = (); $data->[$KEYS]->@* = (); $data->[$VALS]->@* = (); return; } 1; ## -----SOURCE FILTER LOG BEGIN----- ## ## PerlCritic profile "pcore-script" policy violations: ## +------+----------------------+----------------------------------------------------------------------------------------------------------------+ ## | Sev. | Lines | Policy | ## |======+======================+================================================================================================================| ## | 2 | 30, 31 | Miscellanea::ProhibitTies - Tied variable used | ## +------+----------------------+----------------------------------------------------------------------------------------------------------------+ ## ## -----SOURCE FILTER LOG END----- __END__ =pod =encoding utf8 =head1 NAME Pcore::Util::Hash::HashArray - efficiently access hash values using pop, shift or array index. =head1 SYNOPSIS my $hash = Pcore::Util::Hash::HashArray->new; $hash->{1} = 'v1'; $hash->{2} = 'v2'; $hash->{3} = 'v3'; say dump [ keys $hash->%* ]; say dump [ values $hash->%* ]; say $hash->[1]; say shift $hash->@*; say pop $hash->@*; say $hash->rand_key; say $hash->rand_val; =head1 DESCRIPTION Efficiently access hash values using pop, shift or array index. C<$hash-E[$idx]> works much faster, than standard C<( values $hash-E%* )[$idx]>. =head1 ATTRIBUTES =head1 METHODS =head1 SEE ALSO =cut