geektron has asked for the wisdom of the Perl Monks concerning the following question:

I'm not sure if I'm being bitten by auto-vivification here, but the sets that I'm working with are not behaving properly.

I have a hashref of hashrefs that looks something like:

$var = { '1234-567' => { 'key1' => 'stuff' , 'key2' => 'stuff', } '1234-997' => { 'key1' => 'stuff' , 'key2' => 'stuff', } };
when i try to remove items from the set using  delete <code> ( simple call, no?  <code> delete ( $var->{$key} ) within a loop through the hashref keys, it deleted the value, but not the key, so that later when i'm looping through the same hashref to perform another operation, the key for the hashref it still there ...

essentially, i'm doing this .....

foreach my $paymentRow ( @$payments ) { foreach my $salesKey ( keys %$sale_items ) { ### make sure we're applying the right line items ... next if ( $paymentRow->{creditacct} != $sale_items->{$salesKey}{debitacct} ); ## if payment can be applied, ## apply it and delete the row from payments/credits array warn "DELETING ... {$salesKey} " if ( $paymentRow->{amount} == $sale_items->{$salesKey}{amount} and $paymentRow->{ppid} == $sale_items->{$salesKey}{ppid} ); delete $sale_items->{$salesKey} and undef( $paymentRow ) if ( $paymentRow->{amount} == $sale_items->{$salesKey}{amount} and $paymentRow->{ppid} == $sale_items->{$salesKey}{ppid} ); $sale_items->{$salesKey}{amount} -= $paymentRow->{amount} and undef( $paymentRow ) if ( $paymentRow->{amount} < $sale_items->{$salesKey}{amount} and $paymentRow->{ppid} == $sale_items->{$salesKey}{ppid} ); } }
where  $sale_items looks like the example hashref above, and  $payments is an arrayref of hashrefs. (almost the same content ... the end goal is double-ledger accounting stuff .... and it's killing me. i'm not a CPA .... )

i think i'm hitting the autovivification issue, *but* when I've tried to add  next statments to skip over the places i think the hashref key would be autovivified, nothing happens .....

any advice is appreciated

Replies are listed 'Best First'.
Re: deleting key/value from hashref
by ikegami (Patriarch) on Sep 24, 2004 at 18:23 UTC
    foreach my $paymentRow ( @$payments ) { foreach my $salesKey ( keys %$sale_items ) { ### make sure we're applying the right line items ... next if ( $paymentRow->{creditacct} != $sale_items->{$salesKey}{debitacct} ); ## if payment can be applied, ## apply it and delete the row from payments/credits array warn "DELETING ... {$salesKey} " if ( $paymentRow->{amount} == $sale_items->{$salesKey}{amount} and $paymentRow->{ppid} == $sale_items->{$salesKey}{ppid} ); delete $sale_items->{$salesKey} <---- DELETED and undef( $paymentRow ) if ( $paymentRow->{amount} == $sale_items->{$salesKey}{amount} and $paymentRow->{ppid} == $sale_items->{$salesKey}{ppid} ); $sale_items->{$salesKey}{amount} -= $paymentRow->{amount} and undef( $paymentRow ) if ( $paymentRow->{amount} < $sale_items->{$salesKey}{amount} <---- AUTOVIVI and $paymentRow->{ppid} == $sale_items->{$salesKey}{ppid} ); } }

    as demonstrated in

    $salesKey = 'key'; $sales_items = { $salesKey => {} }; delete $sale_items->{$salesKey}; print("[", keys(%$sale_items), "]\n"); # [] doodah() if ( $paymentRow->{amount} < $sale_items->{$salesKey}{amount}); print("[", keys(%$sale_items), "]\n"); # [key]

    To fix, change

    delete $sale_items->{$salesKey} and undef( $paymentRow ) if ( $paymentRow->{amount} == $sale_items->{$salesKey}{amount} and $paymentRow->{ppid} == $sale_items->{$salesKey}{ppid} );

    to

    do { delete $sale_items->{$salesKey}; undef( $paymentRow ); next; } if ( $paymentRow->{amount} == $sale_items->{$salesKey}{amount} and $paymentRow->{ppid} == $sale_items->{$salesKey}{ppid} );

    or start the last if with $sale_items->{$salesKey} and.

      i was using the  next at the beginning of the loop, which is what was throwing me off. thanks.
Re: deleting key/value from hashref
by Zed_Lopez (Chaplain) on Sep 24, 2004 at 18:28 UTC

    You can use Data::Dumper to assure yourself that delete is working.

    use Data::Dumper; my $var = { '1234-567' => { 'key1' => 'stuff' , 'key2' => 'stuff', }, '1234-997' => { 'key1' => 'stuff' , 'key2' => 'stuff', } }; print Dumper($var); delete $var->{'1234-997'}; print Dumper($var);

    produces

    $VAR1 = { '1234-567' => { 'key2' => 'stuff', 'key1' => 'stuff' }, '1234-997' => { 'key2' => 'stuff', 'key1' => 'stuff' } }; $VAR1 = { '1234-567' => { 'key2' => 'stuff', 'key1' => 'stuff' } };

    You're getting bitten by the fact if $var->{a} doesn't exists, then tests on $var->{a}->{b} will autovivify $var->{a} (though the same test on $var->{a} wouldn't.)

    my $var = {}; print if exists $var->{a}; print Dumper($var); print if exists $var->{a}->{b}; print Dumper($var);

    produces

    $VAR1 = {}; $VAR1 = { 'a' => {} };
      I was using Data::Dumper, which is how I know the row was auto-vivifying.

      I wasn't using  exists() in the checks I had tried. i was using  defined ... which was wrong.

        defined is fine too.

        %hash = ( key1 => 'bla', key2 => 0, key3 => undef, ); print( exist($hash{'key0'})?1:0, "\n"); # 0 print(defined($hash{'key0'})?1:0, "\n"); # 0 print( $hash{'key0'} ?1:0, "\n"); # 0 print( exist($hash{'key1'})?1:0, "\n"); # 1 print(defined($hash{'key1'})?1:0, "\n"); # 1 print( $hash{'key1'} ?1:0, "\n"); # 1 # catch: print( exist($hash{'key2'})?1:0, "\n"); # 1 print(defined($hash{'key2'})?1:0, "\n"); # 1 print( $hash{'key2'} ?1:0, "\n"); # 0 # catch: print( exist($hash{'key3'})?1:0, "\n"); # 1 print(defined($hash{'key3'})?1:0, "\n"); # 0 print( $hash{'key3'} ?1:0, "\n"); # 0
Re: deleting key/value from hashref
by Eimi Metamorphoumai (Deacon) on Sep 24, 2004 at 18:30 UTC
    If I'm reading it correctly, your problem is that in your second block you're deleting $sale_items->{$salesKey}, and then in the condition to the third block you're checking $sale_items->{$salesKey}{amount}, which is recreating $sale_items->{$salesKey}. Probably the easiest way would be to next things a little, and reduce some of the repetition (like having exactly the same conditions on the warn and delete. In particular, doing the check for the amounts in an else (or elsif) will make sure you don't try to check something you've already deleted.
    foreach my $paymentRow ( @$payments ) { foreach my $salesKey ( keys %$sale_items ) { ### make sure we're applying the right line items ... next if ( $paymentRow->{creditacct} != $sale_items->{$salesKey}{debitacct} ); next unless $paymentRow->{ppid} == $sale_items->{$salesKey}{ppid}; ## if payment can be applied, ## apply it and delete the row from payments/credits array if ( $paymentRow->{amount} == $sale_items->{$salesKey}{amount}){ warn "DELETING ... {$salesKey} "; delete $sale_items->{$salesKey}; undef( $paymentRow ); } elsif ($paymentRow->{amount} < $sale_items->{$salesKey}{amount}){ $sale_items->{$salesKey}{amount} -= $paymentRow->{amount}; } } }