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

The title describes what I think I want to do.

I need in a DBI fetch loop to initialise those values of the return from a fetchrow_hashref that are undef. I want the code to mimic:

for (@$ref2) { $_ = '' unless defined }

The only way i can work out how to do this is below, how can i get rid of %tmp??

while (my $ref2 = $sth2->fetchrow_hashref()) { my %tmp = %$ref2; while (my ($k, $v) = each %tmp) { $tmp{$k} = '' if (!defined $tmp{$k}); $k = '';$v = ''; } push @{ $tablist{$user_table} }, \%tmp; }

Replies are listed 'Best First'.
Re: setting values in anonymous hash
by quidity (Pilgrim) on Nov 28, 2000 at 18:17 UTC

    You can use map on the expanded hash to alter any undef'ined value to be equal to '' instead. As you cannot use undef as a hash key (it is turned into a string, so becomes '' (although a literal 'undef' becomes the string 'undef')) then this won't hurt any of the keys:

    %new_hash = map {defined($_) ? $_ : ''} %{$ref2};

    For your example you could replace all the code above with something along the lines of:

    $rh_foo = {a=>undef, b=>'b_value', c=>undef, e=>'e_value'}; push @foo, { map {defined($_) ? $_ : ''} %{$rh_foo} }; foreach (keys %{$foo[0]}) { print "$_ => ${$foo[0]}{$_}\n"; }
      quidity suggests:
      > %new_hash = map {defined($_) ? $_ : ''} %{$ref2};
      In recent versions of Perl, you can also use a simpler version that modifies the hash in place:
      map { $_ = '' unless defined } %$ref2;
      This is comparable in simplicity to the original code that involved an array instead of a hash.
        map { $_ = '' unless defined } %$ref2;
        To eliminate the void map, and not attack the keys (which don't need attacking), I'd go with:
        defined or $_ = "" for @$ref2{keys %$ref2};
        or for 5.6 and later:
        defined or $_ = "" for values %$ref2;

        -- Randal L. Schwartz, Perl hacker

        OK, I'm confused. map wants a list, right? So here you're treating the hash as a list, and setting the undefined values to '' as you find them?

        I think that this method would make more sense to me if it went something like

        map { $_ = '' unless defined } values %$ref2;
        I guess I'm just wondering why referring to hashes in a list context is a good idea.

        Update: merlyn rightly points out this is a void usage of map. Perusing the docs for map led me to some insight. We want to test for values that are undefined and change them -- sounds like a job for grep:

        for (grep {not defined} values %$ref2) {$_ = q()};
        fits the bill -- it actually acts on the returned values from grep, so no more void contexts, and actually reads like the task desired.

Re: setting values in anonymous hash
by snax (Hermit) on Nov 28, 2000 at 18:24 UTC
    There comes a time when one must display one's ignorance without fear. I suspect this is my time :)

    I think I am missing something fundamental. First, I don't see why %tmp is necessary, nor do I I understand resetting the $k and $v before the while loop starts over -- don't they get reset automatically?

    In other words, why wouldn't this work:

    while (my $ref2 = $sth2->fetchrow_hashref()) { while (my ($k, $v) = each %$ref2) { $ref2->{$k} = '' if $v == undef; } }
    ?

      Says snax:
      > why wouldn't this work:
      > $ref2->{$k} = '' if $v == undef;
      It doesn't work because if $v was "hello", then you have just scrubbed it out and replaced it with the empty string.

      You should never compare anything for equality with undef, because 0 == undef is true, and "fish" == undef is also true. This is because undef behaves like 0 in a numeric context, and so do most strings. Even "" eq undef is true, which probably is not what you want.

      The only way to check to see if something is undef is to use the defined operator:

      $ref2->{$k} = '' if not defined $v;
        Oy. That would be bad indeed. I used some test code on the command line and was having trouble getting defined to return false values, so settled on the equality test.

        Context, as always, is important, as you demonstrate most eloquently.

      that way works, ish, but just look at the warnings under -w?

      I had left the variable reset in by mistake!

      PS: works a treat though this way....

      while (my ($k, $v) = each %$ref2) { $ref2->{$k} = '' if (!defined $ref2->{$k}); } push @{ $tablist{$user_table} }, $ref2;

      Cheers..

      PPS: and even better this way, thanks!!!!

      for (keys %$ref2) { $ref2->{$_} = '' if (!defined $ref2->{$_}); } push @{ $tablist{$user_table} }, $ref2;

      PPPS: maybe down to one line if I was using 5.5! (untested this last one)

      $ref2->{$_} = '' unless (defined $ref2->{$_}) for (keys %$ref2);