Data-XHash

 view release on metacpan or  search on metacpan

lib/Data/XHash.pm  view on Meta::CPAN

package Data::XHash;

use 5.006;
use strict;
use warnings;
use base qw/Exporter/;
use subs qw/clear delete exists fetch first_key next_key
  scalar store xhash xhashref/;
use Carp;
use Scalar::Util qw/blessed/;

our @EXPORT_OK = (qw/&xhash &xhashref &xh &xhn &xhr &xhrn/);

# XHash values are stored internally using a ring doubly-linked with
# unweakened references:
# {hash}{$key} => [$previous_link, $next_link, $value, $key]

=head1 NAME

Data::XHash - Extended, ordered hash (commonly known as an associative array
or map) with key-path traversal and automatic index keys

=head1 VERSION

Version 0.09

=cut

our $VERSION = '0.09';

=head1 SYNOPSIS

    use Data::XHash;
    use Data::XHash qw/xhash xhashref/;
    use Data::XHash qw/xh xhn xhr xhrn/;

    $tiedhref = Data::XHash->new(); # A blessed and tied hashref
    # Note: Don't call "tie" yourself!

    # Exports are shortcuts to call Data::XHash->new()->push()
    # or Data::XHash->new()->pushref() for you.
    $tiedhref = xh('auto-indexed', { key => 'value' });
    $tiedhref = xhash('auto-indexed', { key => 'value' });
    $tiedhref = xhashref([ 'auto-indexed', { key => 'value' } ]);
    $tiedhref = xhn('hello', { root => { branch =>
      [ { leaf => 'value' }, 'world' ] } }); # (nested)
    $tiedhref = xhr([ 'auto-indexed', { key => 'value' } ]);
    $tiedhref = xhrn([ 'hello', { root => { branch =>
      [ { leaf => 'value' }, 'world' ] } } ]); # (nested)

    # Note: $xhash means you can use either $tiedhref or the
    # underlying object at tied(%$tiedhref)

    ## Hash-like operations

    # Getting keys or paths
    $value = $tiedhref->{$key};
    $value = $tiedhref->{\@path};
    $value = $xhash->fetch($key);
    $value = $xhash->fetch(\@path);

    # Auto-vivify a Data::XHash at the end of the path
    $tiedhref2 = $tiedhref1->{ [ @path, {} ] };
    $tiedhref->{ [ @path, {} ] }->$some_xh_method(...);
    $tiedhref = $xhash->fetch( [ @path, {} ] );
    $xhash->fetch( [ @path, {} ] )->$some_xh_method(...);

    # Setting keys or paths
    $tiedhref->{$key} = $value;
    $tiedhref->{\@path} = $value;
    $xhash->store($key, $value, %options);
    $xhash->store(\@path, $value, %options);

    # Setting the next auto-index key
    $tiedhref->{[]} = $value; # Recommended syntax

lib/Data/XHash.pm  view on Meta::CPAN

	$path->{container}->store($path->{key}, $value, %options);
    } else {
	# Store locally.
	my $self = tied(%$this) || $this;

	# Get the next index for undef or [].
	$key = defined($self->{max_index})? ($self->{max_index} + 1):
	  $self->next_index() if !defined($key) || $array_key;

	if ($options{nested}) {
	    # Convert nested native structures to XHashes.
	    if (ref($value) eq 'HASH') {
		$value = $self->new()->pushref([$value], %options);
	    } elsif (ref($value) eq 'ARRAY') {
		$value = $self->new()->pushref($value, %options);
	    }
	}

	if (my $entry = $self->{hash}{$key}) {
	    # Replace the value for an existing key.
	    $entry->[2] = $value;
	} else {
	    my $link;
	    if (my $tail = $self->{tail}) {
		my $head = $tail->[1];
		# Link an additional element into a non-empty ring.
		$link = $self->{hash}{$key} =
		  $tail->[1] = $head->[0] = [$tail, $head, $value, $key];
	    } else {
		# Start a new key ring.
		$link = $self->{hash}{$key} = [undef, undef, $value, $key];
		$link->[0] = $link->[1] = $link;
	    }
	    $self->{tail} = $link;
	    $self->{max_index} = $key
	      if ($key =~ /^\d+$/ && (defined($self->{max_index})?
	      ($key > $self->{max_index}): ($key >= $self->next_index())));
	}
    }

    return $this;
}

*store = \&STORE;

=head2 %$tiedref = ()

=head2 $xhash->clear( )

These clear the XHash.

Clear returns the XHash tiedref or object (whichever was used).

=cut

sub CLEAR {
    my ($this) = @_;
    my $self = tied(%$this) || $this;

    if ($self->{hash}) {
	# Blow away unweakened refs before tossing the hash.
	@$_ = () foreach (values %{$self->{hash}});
    }
    $self->{hash} = {};
    delete $self->{tail};
    delete $self->{iter};
    $self->{max_index} = -1;
    return $this;
}

*clear = \&CLEAR;

=head2 delete $tiedref->{$key} # or \@path

=head2 $xhash->delete($key) # or \@path

=head2 $xhash->delete(\%options?, @keys)

These remove the element with the specified key and return its value. They
quietly return C<undef> if the key does not exist.

The method call can also delete (and return) multiple local (not path) keys
at once.

Options:

=over

=item to => $destination

If C<$destination> is an arrayref, hashref, or XHash, each deleted
C<< { $key => $value } >> is added to it and the destination is returned
instead of the most recently deleted value.

=back

=cut

sub DELETE : method {
    my $self = shift;
    my %options = ref($_[0]) eq 'HASH'? %{+shift}: ();
    my $key = $_[0];

    if (ref($key) eq 'ARRAY' && @$key) {
	# Delete across the path.
	my $path = $self->traverse($key, op => 'delete');

	return $path->{container}?
	  $path->{container}->delete($path->{key}): undef;
    }

    # Delete locally.
    my $to = $options{to};
    my $return;
    $self = tied(%$self) || $self;

    while (@_) {
	$key = shift;

        if (my $link = $self->{hash}{$key}) {
	    if (ref($to) eq 'ARRAY') {



( run in 1.436 second using v1.01-cache-2.11-cpan-39bf76dae61 )