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

This has been a long week, and my brain is fried. I am sure there is a better way of doing this, but I cannot see it. Basically, I have a need to look through a hash, and if there are any entries where the key starts with a certain character suquence, add a new hash entry (with the same value) with a key with that character sequence stripped off.

For example, if there is an entry "redirect_name" with a value of "boondoggle", I want to create a new entry "name" also with a value of boondoggle.

My code so far:

foreach $oldkey (keys %fdat) { if ( ($newkey = $oldkey) =~ s/^redirect_//) { $fdat{$newkey} = $fdat{$oldkey}; } }

At least it works. Any suggestions on ways to improve it?

tia.

Replies are listed 'Best First'.
Re: Regexs on Hash Keys
by merlyn (Sage) on Dec 01, 2000 at 02:28 UTC
Re: Regexs on Hash Keys
by Dominus (Parson) on Dec 01, 2000 at 03:49 UTC
    An alternative would be to create a tied hash class that has the bahavior you want, so that if you look up name, and there is no key name but there is one called redirect_name, you get the value associated with redirect_name transparently. Perhaps something like this:

    package Redirect_Hash; sub TIEHASH { my ($class, $prefix) = @_; $prefix = 'redirect_' unless defined $prefix; my $self = [$prefix, {}]; bless $self => $class; } sub STORE { my ($self, $key, $value) = @_; my ($prefix, $hash) = @$self; if (index($key, $prefix) == 0) { my $truncated = substr($key, length($prefix)); $hash->{$truncated} = $value unless exists $hash->{$truncated}; } $hash->{$key} = $value; } sub FETCH { my ($self, $key) = @_; my ($prefix, $hash) = @$self; $hash->{$key}; }

    To use this, you'd say:

    tie %h => Redirect_Hash; $h{redirect_name} = 'boondoggle'; print $h{name}, "\n"; # prints 'boondoggle' $h{name} = 'Fred'; print $h{name}, "\n"; # still prints 'Fred' print $h{redirect_name}, "\n"; # still prints 'boondoggle'
    Whether the tied hash is an improvement over simply using the code you showed depends on how deeply encapsulated you want the behavior to be. With your code it's out in the open; with the tied hash it just looks like a hash that magically does what you want. If the hash will change infrequently, you're probably better off using the code you showed, because you don't need to do it very often. But if the hash changes a lot and needs to be constantly kept up to date, the tied hash solution might be better because it does all the right work magically, behind the scenes.

    With the implementation I showed above, you can't use delete. It's not hard to come up with an implementation that fixes that, but I'll leave it as an exercise because I've noticed that people tend not to upvote posts that have too much code in them.

Expressing Intent
by dws (Chancellor) on Dec 01, 2000 at 04:43 UTC
    In terms of expressing intent, at least in my biased opinion, the original code fragment wins, and the attempts at "making it better" are increasingly obfuscating. Unless performance is a serious issue, clarity of expression should be the goal.

    Consider picking up the code blind. Which is easier to understand?

       foreach $oldkey (keys %fdat) {
           if ( ($newkey = $oldkey) =~ s/^redirect_//) {
               $fdat{$newkey} = $fdat{$oldkey};
           }
       }
    
    or
       $fdat{$_} = $fdat{"redirect_$_"} 
           for map { /^redirect_(.*)$/ ? $1 : () } keys %fdat;
    
    I consider myself a pretty fair Perl coder, but I have to admit that it took me an order of magnitude longer to grasp the intent of the second example than it did the first. When I catch folks in my team writing code like the second example, we have a talk. The talk includes asking them to consider the plight of the poor soul who has to pick up the code next.

    Clarity, please.

      Says dws:
      > In terms of expressing intent, at least in my
      > biased opinion, the original code fragment wins...

      I agree. My first reaction on seeing the question was to ask "What's wrong with the code you have?"

      That's why I tried to go in a drastically different direction with my answer.

      FWIW, I became intrigued by all the suggestions here and found that Benchmark reveals Maclir's code to be the fastest (by about 30% on my system).

      Philosophy can be made out of anything. Or less -- Jerry A. Fodor

Re (tilly) 1: Regexs on Hash Keys
by tilly (Archbishop) on Dec 01, 2000 at 06:39 UTC
    Put my on the loop variables:
    foreach my $oldkey (keys %fdat) { if ( (my $newkey = $oldkey) =~ s/^redirect_//) { $fdat{$newkey} = $fdat{$oldkey}; } }
    That way you know you won't accidentally conflict with any globals of the same name. And it will pass the handy-dandy spell-checker known as strict.
Re: Regexs on Hash Keys
by rpc (Monk) on Dec 01, 2000 at 02:30 UTC
    Here's one way to do it:
    #!/usr/bin/perl -w use strict; use Data::Dumper; my %hash = ( 'f_foo' => 'bleh', 'f_bar' => 'blarf', 'f_bas' => 'heh', ); foreach(keys %hash) { $hash{$1} = $hash{$_} if(m/f_(.*)/); } print Dumper(\%hash);
Re: Regexs on Hash Keys
by mwp (Hermit) on Dec 01, 2000 at 02:39 UTC
    Here's a stab:
    %fdat = ( 'redirect_sam' => 'boondoggle' ); $fdat{$_} = $fdat{"redirect_$_"} for map /^redirect_(.*)$/, keys %fdat;

    Or, alternately:

    for(keys %fdat) { $fdat{$1} = $fdat{$_} if /^redirect_(.*)$/; }

    Don't know if you'll gain any speed from it, though. Please note, I used map instead of grep for the first snippet because I just wanted to return the matched part, not the entire list element as grep does.

    HTH

    'kaboo

    Update:
    Sigh... too slow... I like merlyn's tho, it's purtee.

      I don't think your first one works. Here is a modified version:

      $fdat{$_} = $fdat{"redirect_$_"} for map { /^redirect_(.*)$/ ? $1 : () } keys %fdat;

      Update: I did a lot of work for nothing. In a list context, a matching m// returns the list of captures, ($1), in this case. And a non-matching m// returns the empty list, (), not for example, a list containing one false value. This last item has bitten me several times; you'd think I'd have learned by now. ):

      Thanks to chipmunk for reminding me of this.

              - tye (but my friends call me "Tye")
Re: Regexs on Hash Keys
by Maclir (Curate) on Dec 01, 2000 at 02:45 UTC
    Backreferences. Sigh. Of course. Thank you one and all.