tags:

views:

105

answers:

3

I want to delete an element of a hash(of any depth) which has the first key as $key[0], the second key as $key[1] etc, until @key is finished.

For example, if @key=(23,56,78) then i want to manipulate $hash{23}{56}{78}.
I don't know beforehand how many elements @key has.

I have been trying to use the following:

my %the_path;
my $temp=\%the_path;
for(my $cline=0;$cline<=$#keys;$cline++){
     my $cfolder=$keys[$cline];
     $temp->{$cfolder}={};
     $temp=$temp->{$cfolder};
}

But, I'm not sure how to manipulate the element at here. How do I do that?

+1  A: 

You need to traverse the tree of hashrefs down using the next key value in the list as a key to traverse to, till you hit the second to last key in the list; then delete the hashref value associated with the last key.

my $hash_ptr = $my_hash_ref;
foreach my $key_num (0..$#keys) {
    my $key = $keys[$key_num];
    if (exists $hash_ptr->{$key}) {
        if ($key_num == $#keys) {
            delete $hash_ptr->{$key};
        } else {
            $hash_ptr = $hash_ptr->{$key}; # Go down 1 level
        }
    } else {
        last;
    }
}

Note: this does NOT delete any elements of the hashref tree above the last one, even if they don't contain anymore keys. In other words it deletes 1 node, NOT an entire path - it does not, in Evan's words, prune the tree. If that is not what you meant, please clarify.

DVK
Why the downvote?
DVK
Just retested the code - it works. Not sure what the downvote was for
DVK
@DVK: you have a bug; no delete should occur for `{a=>{c=>42}}` with keys a,b,c (but I didn't downvote)
ysth
It's simpler if you do the delete after the loop, e.g. the tersified: `my $hp = $root || {}; $hp = $hp->{$_} || {} for @keys[[email protected]]; delete $hp->{ $keys[-1] };`
ysth
+1  A: 

Here is an example with recursion:

use strict;
use warnings;

my $hash = { foo => { here => 'there', bar => { baz => 100 } } };

## mutates input
sub delete_hash {
  my ( $hash, $keys ) = @_;
  my $key = shift @$keys;
  die "Stopped recursing $key doesn't exist"
    unless exists $hash->{$key}
  ;
  scalar @$keys
    ? delete_hash( $hash->{$key}, $keys )
    : delete $hash->{$key}
  ;
}

delete_hash( $hash, [qw/foo bar/] );

use XXX;
YYY $hash;

The stack does grow and function calls have a price. You can aparently mitigate that with perl's version of TCO with this code:

if (scalar @$keys) {
  @_=($hash->{$key}, $keys);
  goto &delete_hash;
}
else {
  delete $hash->{$key}
}

And, it should also noted that none of this code prunes the tree: if you delete [qw/foo bar baz/] then bar will be an empty hash ref.

foo:
  bar: {}
  here: there
Evan Carroll
@Evan - you COULD, of course, simply unroll the recursion into a loop if you have performance concerns (in which case you get my answer :) ). Good wording on "prune" - i'll steal it, sorry :)
DVK
You can always eliminate sub-calls, depends on your goal. Maybe I'll write a benchmark with the goto variant.
Evan Carroll
+5  A: 

Data::Diver exists for exactly this purpose.

my $last_hash = Data::Diver::Dive( \%hash, @keys[0..$#keys-1] );
if ($last_hash) { delete $last_hash->{ $keys[-1] } }
ysth
Don't you mean:my $hash_ref=\%hash;my $last_hash = Data::Diver::Dive( $hash_ref, @keys[0..$#keys-1] );if ($last_hash) { delete $last_hash->{ $keys[-1] }; }?
Hermann Ingjaldsson
@Hermann Ingjaldsson: thanks
ysth