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?

7
  • 1
    scalar @key gives you the length, doesn't it? Commented Aug 27, 2010 at 18:09
  • sounds like homework. have you tried to solve this yourself? Commented Aug 27, 2010 at 18:10
  • this is not homework and i have tried myself, yes. Commented Aug 27, 2010 at 18:12
  • 1
    well what did you try? Perhaps we can show where you went wrong. Commented Aug 27, 2010 at 18:13
  • So what have you tried? What doesn't work? Commented Aug 27, 2010 at 18:13

3 Answers 3

5

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] } }
Sign up to request clarification or add additional context in comments.

1 Comment

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] }; } ?
1

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

2 Comments

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 :)
You can always eliminate sub-calls, depends on your goal. Maybe I'll write a benchmark with the goto variant.
1

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.

3 Comments

Just retested the code - it works. Not sure what the downvote was for
you have a bug; no delete should occur for {a=>{c=>42}} with keys a,b,c (but I didn't downvote)
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] };

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.