The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

NAME

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

VERSION

Version 0.09

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
    $tiedhref->{+undef} = $value;
    $tiedhref->{[ undef ]} = $value; # Any path key may be undef
    $xhash->store([], $value, %options);
    $xhash->store(undef, $value, %options);
    $xhash->store([ undef ], $value, %options);

    # Clear the xhash
    %$tiedhref = ();
    $xhash->clear();

    # Delete a key and get its value
    $value = delete $tiedhref->{$key}; # or \@path
    $value = $xhash->delete($key); # or \@path
    $value = $xhash->delete(\%options?, @local_keys);

    # Does a key exist?
    $boolean = exists $tiedhref->{$key}; # or \@path
    $boolean = $xhash->exists($key); # or \@path

    # Keys and lists of keys
    @keys = keys %$tiedhref; # All keys using iterator
    @keys = $xhash->keys(%options); # Faster, without iterator
    $key = $xhash->FIRSTKEY(); # Uses iterator
    $key = $xhash->first_key();
    $key1 = $xhash->previous_key($key2);
    $key = $xhash->NEXTKEY(); # Uses iterator
    $key2 = $xhash->next_key($key1);
    $key = $xhash->last_key();
    $key = $xhash->next_index(); # The next auto-index key

    # Values
    @all_values = values %$tiedhref; # Uses iterator
    @all_values = $xhash->values(); # Faster, without iterator
    @some_values = @{%$tiedhref}{@keys}; # or pathrefs
    @some_values = $xhash->values(\@keys); # or pathrefs

    ($key, $value) = each(%$tiedhref); # Keys/values using iterator

    # Call coderef with ($xhash, $key, $value, @more_args) for
    # each key/value pair and then undef/undef.
    @results = $xhash->foreach(\&coderef, @more_args);

    # Does the hash contain any key/value pairs?
    $boolean = scalar(%$tiedhref);
    $boolean = $xhash->scalar();

    ## Array-like operations

    $value = $xhash->pop(); # last value
    ($key, $value) = $xhash->pop(); # last key/value
    $value = $xhash->shift(); # first value
    ($key, $value) = $xhash->shift(); # first key/value

    # Append values or { keys => values }
    $xhash->push(@elements);
    $xhash->pushref(\@elements, %options);

    # Insert values or { keys => values }
    $xhash->unshift(@elements);
    $xhash->unshiftref(\@elements, %options);

    # Merge in other XHashes (recursively)
    $xhash->merge(\%options?, @xhashes);

    # Export in array-like fashion
    @list = $xhash->as_array(%options);
    $list = $xhash->as_arrayref(%options);

    # Export in hash-like fasion
    @list = $xhash->as_hash(%options);
    $list = $xhash->as_hashref(%options);

    # Reorder elements
    $xhash->reorder($reference, @keys); # [] = sorted index_only

    # Remap elements
    $xhash->remap(%mapping); # or \%mapping
    $xhash->renumber(%options);

    ## TIEHASH methods - see perltie

    # TIEHASH, FETCH, STORE, CLEAR, DELETE, EXISTS
    # FIRSTKEY, NEXTKEY, UNTIE, DESTROY

DESCRIPTION

Data::XHash provides an object-oriented interface to tied, ordered hashes. Hash elements may be assigned keys explicitly or automatically in mix-and-match fashion like arrays in PHP.

It also includes support for trees of nested XHashes, tree traversal, and conversion to and from native Perl data structures.

Suggested uses include structured configuration information or HTTP query parameters in which order may at least sometimes be significant, for passing mixed positional and named parameters, sparse arrays, or porting PHP code.

EXPORTS

You may export any of the shortcut functions. None are exported by default.

FUNCTIONS

$tiedref = xh(@elements)

$tiedref = xhash(@elements)

$tiedref = xhashref(\@elements, %options)

$tiedref = xhn(@elements)

$tiedref = xhr(\@elements, %options)

$tiedref = xhrn(\@elements, %options)

These convenience functions call Data::XHash->new() and then pushref() the specified elements. The "r" and "ref" versions take an arrayref of elements; the others take a list. The "n" versions are shortcuts for the nested => 1 option of pushref().

    $tiedref = xh('hello', {root=>xh({leaf=>'value'}),
      {list=>xh(1, 2, 3)});
    $tiedref = xhn('hello', {root=>{leaf=>'value'}},
      {list=>[1, 2, 3]});

METHODS

Data::XHash->new( )

$xhash->new( )

These create a new Data::XHash object and tie it to a new, empty hash. They bless the hash as well and return a reference to the hash ($tiedref).

Do not use tie %some_hash, 'Data::XHash'; - it will croak!

$tiedref->{$key}

$tiedref->{\@path}

$xhash->fetch($key)

$xhash->fetch(\@path)

These return the value for the specified hash key, or undef if the key does not exist.

If the key parameter is reference to a non-empty array, its elements are traversed as a path through nested XHashes.

If the last path element is a hashref, the path will be auto-vivified (Perl-speak for "created when referenced") and made to be an XHash if necessary (think "fetch a path to a hash"). Otherwise, any missing element along the path will cause undef to be returned.

    $xhash->{[]}; # undef

    $xhash->{[qw/some path/, {}]}->isa('Data::XHash'); # always true
    # Similar to native Perl: $hash->{some}{path} ||= {};

$tiedref->{$key} = $value

$tiedref->{\@path} = $value

$xhash->store($key, $value, %options)

$xhash->store(\@path, $value, %options)

These store the value for the specified key in the XHash. Any existing value for the key is overwritten. New keys are stored at the end of the XHash.

If the key parameter is a reference to a non-empty array, its elements are traversed as a path through nested XHashes. Path elements will be auto-vivified as necessary and intermediate ones will be forced to XHashes.

If the key is an empty path or the undef value, or any path key is the undef value, the next available non-negative integer index in the corresponding XHash is used instead.

These return the XHash tiedref or object (whichever was used).

Options:

nested => $boolean

If this option is true, arrayref and hashref values will be converted into XHashes.

%$tiedref = ()

$xhash->clear( )

These clear the XHash.

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

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

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

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

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

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

Options:

to => $destination

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

exists $tiedref->{$key} # or \@path

$xhash->exists($key) # or \@path

These return true if the key (or path) exists.

$xhash->FIRSTKEY( )

This returns the first key (or undef if the XHash is empty) and resets the internal iterator.

$xhash->first_key( )

This returns the first key (or undef if the XHash is empty).

$xhash->previous_key($key)

This returns the key before $key, or undef if $key is the first key or doesn't exist.

$xhash->NEXTKEY( )

This returns the next key using the internal iterator, or undef if there are no more keys.

$xhash->next_key($key)

This returns the key after $key, or undef if $key is the last key or doesn't exist.

Path keys are not supported.

$xhash->last_key( )

This returns the last key, or undef if the XHash is empty.

$xhash->next_index( )

This returns the next numeric insertion index. This is either "0" or one more than the current largest non-negative integer index.

scalar(%$tiedref)

$xhash->scalar( )

This returns true if the XHash is not empty.

$xhash->keys(%options)

This method is equivalent to keys(%$tiedref) but may be called on the object (and is much faster).

Options:

index_only => $boolean

If true, only the integer index keys are returned. If false, all keys are returned,

sorted => $boolean

If index_only mode is true, this option determines whether index keys are returned in ascending order (true) or XHash insertion order (false).

$xhash->values(\@keys?)

This method is equivalent to values(%$tiedref) but may be called on the object (and, if called without specific keys, is much faster too).

You may optionally pass a reference to an array of keys whose values should be returned (equivalent to the slice @{$tiedref}{@keys}). Key paths are allowed, but don't forget that the list of keys/paths must be provided as an array ref ([ $local_key, \@path ]).

$xhash->foreach(\&coderef, @more_args)

This method calls the coderef as follows

    push(@results, &$coderef($xhash, $key, $value, @more_args));

once for each key/value pair in the XHash (if any), followed by a call with both set to undef.

It returns the accumulated list of coderef's return values.

Example:

    # The sum and product across an XHash of numeric values
    %results = $xhash->foreach(sub {
        my ($xhash, $key, $value, $calc) = @_;

        return %$calc unless defined($key);
        $calc->{sum} += $value;
        $calc->{product} *= $value;
        return ();
      }, { sum => 0, product => 1 });

$xhash->pop( )

$xhash->shift( )

These remove the first element (shift) or last element (pop) from the XHash and return its value (in scalar context) or its key and value (in list context). If the XHash was already empty, undef or () is returned instead.

$xhash->push(@elements)

$xhash->pushref(\@elements, %options)

$xhash->unshift(@elements)

$xhash->unshiftref(\@elements, %options)

These append elements at the end of the XHash (push() and pushref()) or insert elements at the beginning of the XHash (unshift() and unshiftref()).

Scalar elements are automatically assigned a numeric index using next_index(). Hashrefs are added as key/value pairs. References to references are dereferenced by one level before being added. (To add a hashref as a hashref rather than key/value pairs, push or unshift a reference to the hashref instead.)

These return the XHash tiedref or object (whichever was used).

Options:

at_key => $key

This will push after $key instead of at the end of the XHash or unshift before $key instead of at the beginning of the XHash. This only applies to the first level of a nested push or unshift.

This must be a local key (not a path), and the operation will croak if the key is not found.

nested => $boolean

If true, values that are arrayrefs (possibly containing hashrefs) or hashrefs will be recursively converted to XHashes.

$xhash->merge(\%options?, @xhashes)

This recursively merges each of the XHash trees in @xhashes into the current XHash tree $xhash as follows:

If a key has both existing and new values and both are XHashes, the elements in the new XHash are added to the existing XHash.

Otherwise, if the new value is an XHash, the value is set to a copy of the new XHash.

Otherwise the value is set to the new value.

Returns the XHash tiedref or object (whichever was used).

Examples:

    # Clone a tree of nested XHashes (preserving index keys)
    $clone = xh()->merge({ indexed_as => 'hash' }, $xhash);

    # Merge $xhash2 (with new keys) into existing XHash $xhash1
    $xhash1->merge($xhash2);

Options:

indexed_as => $type

If $type is array (the default), numerically-indexed items in each merged XHash are renumbered as they are added (like push($xhash->as_array())).

If $type is hash, numerically-indexed items are merged without renumbering (like push($xhash->as_hash())).

$xhash->as_array(%options)

$xhash->as_arrayref(%options)

$xhash->as_hash(%options)

$xhash->as_hashref(%options)

These methods export the contents of the XHash as native Perl arrays or arrayrefs.

The "array" versions return the elements in an "array-like" array or array reference; elements with numerically indexed keys are returned without their keys.

The "hash" versions return the elements in an "hash-like" array or array reference; all elements, including numerically indexed ones, are returned with keys.

    xh( { foo => 'bar' }, 123, \{ key => 'value' } )->as_arrayref();
    # [ { foo => 'bar' }, 123, \{ key => 'value'} ]

    xh( { foo => 'bar' }, 123, \{ key => 'value' } )->as_hash();
    # ( { foo => 'bar' }, { 0 => 123 }, { 1 => { key => 'value' } } )

    xh(xh({ 3 => 'three' }, { 2 => 'two' })->as_array())->as_hash();
    # ( { 0 => 'three' }, { 1 => 'two' } )

    xh( 'old', { key => 'old' } )->push(
    xh( 'new', { key => 'new' } )->as_array())->as_array();
    # ( 'old', { key => 'new' }, 'new' )

    xh( 'old', { key => 'old' } )->push(
    xh( 'new', { key => 'new' } )->as_hash())->as_hash();
    # ( { 0 => 'new' }, { key => 'new' } )

Options:

nested => $boolean

If this option is true, trees of nested XHashes are recursively expanded.

$xhash->reorder($refkey, @keys)

This reorders elements within the XHash relative to the reference element having key $refkey, which must exist and will not be moved.

If the reference key appears in @keys, the elements with keys preceding it will be moved immediately before the reference element. All other elements will be moved immediately following the reference element.

Only the first occurence of any given key in @keys is considered - duplicates are ignored.

If any key is an arrayref, it is replaced with a sorted list of index keys.

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

    # Move some keys to the beginning of the XHash.
    $xhash->reorder($xhash->first_key(), @some_keys,
      $xhash->first_key());

    # Move some keys to the end of the XHash.
    $xhash->reorder($xhash->last_key(), @some_keys);

    # Group numeric index keys in ascending order at the lowest one.
    $xhash->reorder([]);

$xhash->remap(\%mapping)

$xhash->remap(%mapping)

This remaps element keys according to the specified mapping (a hash of $old_key => $new_key). The mapping must map old keys to new keys one-to-one.

The order of elements in the XHash is unchanged.

The XHash tiedref or object is returned (whichever was used).

$xhash->renumber(%options)

This renumbers all elements with an integer index (those returned by $xhash->keys(index_only => 1)). The order of elements is unchanged.

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

Options:

from => $starting_index

Renumber from $starting_index instead of the default zero.

sorted => $boolean

This option is passed to $xhash->keys().

If set to true, keys will be renumbered in sorted sequence. This results in a "relative" renumbering (previously higher index keys will still be higher after renumbering, regardless of order in the XHash).

If false or not set, keys will be renumbered in XHash (or "absolute") order.

$xhash->traverse($path, %options?)

This method traverses key paths across nested XHash trees. The path may be a simple scalar key, or it may be an array reference containing multiple keys along the path.

An undef value along the path will translate to the next available integer index at that level in the path. A {} at the end of the path forces auto-vivification of an XHash at the end of the path if one does not already exist there.

This method returns a reference to an hash containing the elements "container", "key", and "value". If the path does not exist, the container and key values with be undef.

An empty path ([]) is equivalent to a path of undef.

Options:

op

This option specifies the operation for which the traversal is being performed (fetch, store, exists, or delete).

xhash

This forces the path to terminate with an XHash (for "fetch" paths ending in {}).

vivify

This will auto-vivify missing intermediate path elements.

AUTHOR

Brian Katzung, <briank at kappacs.com>

BUG TRACKING

Please report any bugs or feature requests to bug-data-xhash at rt.cpan.org, or through the web interface at http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Data-XHash. I will be notified, and then you'll automatically be notified of progress on your bug as I make changes.

SUPPORT

You can find documentation for this module with the perldoc command.

    perldoc Data::XHash

You can also look for information at:

SEE ALSO

Array::AsHash

An array wrapper to manage elements as key/value pairs.

Array::Assign

Allows you to assign names to array indexes.

Array::OrdHash

Like Array::Assign, but with native Perl syntax.

Data::Omap

An ordered map implementation, currently implementing an array of single-key hashes stored in key-sorting order.

Hash::AsObject

Auto accessors and mutators for hashes and tied hashes.

Hash::Path

A basic hash-of-hash traverser. Discovered by the author after writing Data::XHash.

Tie::IxHash

An ordered hash implementation with a different interface and data structure and without auto-indexed keys and some of Data::XHash's other features.

Tie::IxHash is probably the "standard" ordered hash module. Its simpler interface and underlying array-based implementation allow it to be almost 2.5 times faster than Data::XHash for some operations. However, its Delete, Shift, Splice, and Unshift methods degrade in performance with the size of the hash. Data::XHash uses a doubly-linked list, so its delete, shift, splice, and unshift methods are unaffected by hash size.

Tie::Hash::Array

Hashes stored as arrays in key sorting-order.

Tie::LLHash

A linked-list-based hash like Data::XHash, but it doesn't support the push/pop/shift/unshift array interface and it doesn't have automatic keys.

Tie::StoredOrderHash

Hashes with items stored in least-recently-used order.

LICENSE AND COPYRIGHT

Copyright 2012 Brian Katzung.

This program is free software; you can redistribute it and/or modify it under the terms of either: the GNU General Public License as published by the Free Software Foundation; or the Artistic License.

See http://dev.perl.org/licenses/ for more information.