Beefy Boxes and Bandwidth Generously Provided by pair Networks
Your skill will accomplish
what the force of many cannot
 
PerlMonks  

Bulk Hash Assignment

by tadman (Prior)
on Apr 17, 2002 at 07:33 UTC ( [id://159738] : perlmeditation . print w/replies, xml ) Need Help??

When moving data around, especially from one hash to another, things can tend to get a little bogged down in the details. Programming degrades into a frenzy of cutting and pasting, replacing, and so forth. I keep seeing these heavy heaps of code which, functionally, do almost nothing:
$big_foo_bucket->{camshaft} = $other_foo_bucket{camshaft}; $big_foo_bucket->{gearbox} = $other_foo_bucket{gearbox1}; $big_foo_bucket->{drivetrain} = $other_foo_bucket{the_drivetrain}; $big_foo_bucket->{smokestack} = $other_foo_bucket{my_smokestack}; $big_foo_bucket->{junkyard} = $other_foo_bucket{_junkyard};
Now, you can't just blind assign one to the other, since one is a ref and the other is a real hash, and the names differ slightly. Also, there is stuff on both sides that isn't meant to be touched. Isn't there a better way to specify the mapping?

The first thing that comes to mind is using slices to swap data, and with a bit of polish, you get something like this:
@{$big_foo_bucket}{qw[ camshaft gearbox drivetrain smokestack junkyard ]} = @other_foo_bucket{qw[ camshaft gearbox1 the_drivetrain my_smokestack _junkyard ]};
Yet the results from a structural and scalability perspective aren't entirely satisfactory. The two lists that must be maintained in parallel or Bad Things happen. Further, when you start to add many things to the list, it's either going to scroll or wrap, both of which lead this to be a self-defeating solution.

Ideally, you could use a function to do this, the implementation of which is really quite simple, but at the expense of being non-standard:
schlep(\%other_foo_bucket => $big_foo_bucket, qw[ camshaft camshaft gearbox1 gearbox the_drivetrain drivetrain my_smokestack smokestack _junkyard junkyard ]);
Now this works, but seems highly unorthodox, which would probably mean that it's some Dark form of Laziness that you're supposed to keep to yourself.

Surely there is some kind of gasket function which can make this really clean and simple?

Replies are listed 'Best First'.
Re: Bulk Hash Assignment
by particle (Vicar) on Apr 17, 2002 at 12:33 UTC
    in the beginning, there was foo...

    #!/usr/bin/perl -w use strict; my %foo = ( camshaft => 'lumpy', gearbox => 'shifty', drivetrain => 'speedy', smokestack => 'dirty', lights => 'bright eyes', );
    but foo was alone. so bar was created.

    my %bar; @bar{ qw/camshaft gearbox1 the_drivetrain _other/ } = '';
    but bar was different... and empty. foo wants to understand bar, and a map was made...

    my %map_foo_to_bar = ( camshaft => 'camshaft', gearbox => 'gearbox1', drivetrain => 'the_drivetrain', smokestack => 'my_smokestack', junkyard => '_junkyard', );
    still, bar was empty, so along comes schlep to help...

    sub schlep($$$) { my( $map_ref, $from_ref, $to_ref ) = ( shift, shift, shift ); for( keys %{ $from_ref } ) { $to_ref->{ $map_ref->{$_} } = $from_ref->{$_} if( exists $map_ref->{$_} && exists $to_ref->{ $map_ref->{ +$_} } ) } } schlep(\%map_foo_to_bar, \%foo, \%bar); use Data::Dumper; print Dumper \%bar;
    so it is written, so it shall be done.

    ~Particle ;

      That's surprisingly complicated for what I thought was a simple task:
      sub schlep { my ($p,$q,%p2q) = @_; @{$q}{values %p2q} = @{$p}{keys %p2q}; }
      Of course, this does assume that values and keys will always output in precisely the same order. Not that I have been given any reason to believe that they would do something to the contrary.
        well, this looks simple. i haven't run it, but it looks like it will work if %$p and %$q implement exactly the same keys. my example works on the intersection of keys between %foo and %bar (only the keys both hashes share.) perhaps i solved a more general problem than the original poster requested.

        question: what happens to your code if a key exists in %p2q, but not in %$p? my code will not auto-vivify a key in %$q. will yours?

        by the way, values and keys are guaranteed to output in the same order (in the same hash.) this is by no means guaranteed in different hashes. see each for a little detail.

        ~Particle ;

        That's guaranteed. In the very docs you point to.

        I'm not snide, I'm just bitter that you posted the answer before I did.

Re: Bulk Hash Assignment
by Fletch (Bishop) on Apr 17, 2002 at 12:55 UTC
    schlep(\%other_foo_bucket => $big_foo_bucket, qw[ camshaft camshaft gearbox1 gearbox the_drivetrain drivetrain my_smokestack smokestack _junkyard junkyard ]);

    Rather than qw[] you could use a heredoc and have your routine do the splitting. Then you could have:

    schlep(\%other_foo_bucket => $big_foo_bucket, <<EOT ); camshaft camshaft gearbox1 gearbox the_drivetrain drivetrain my_smokestack smokestack _junkyard junkyard EOT

    I want to say this is mentioned somewhere in the camel, maybe not for this exact problem but for something similar.

      Certainly, that is easy from an input perspective, but if one were to have a mapping already prepared, you'd have to convert it into a string before using it. Yuck. qw, despite how ugly it can look, sure works well.

      Still, using here-doc style declarations for function params is a very interesting idea.
(MeowChow) Re: Bulk Hash Assignment
by MeowChow (Vicar) on Apr 17, 2002 at 11:43 UTC
    schlep(\%other_foo_bucket => $big_foo_bucket,
    Oh damn. This has to be the funniest name I've ever seen for a subroutine. ++tadman, for this alone.
    *kvetch = *Carp::carp; *oyvey = sub { warn @_ }; sub Shlemiel { &Shlemazel } sub Shlemazel { &Shlemiel } # and of course ... use Bone::Easy; pickup $shiksah;
    update: actually, make that:
    pickup @shiksahs;
       MeowChow                                   
                   s aamecha.s a..a\u$&owag.print
Re: Bulk Hash Assignment
by Molt (Chaplain) on Apr 17, 2002 at 09:33 UTC

    Would the following be a vague step in the right direction?

    my @assignList = qw(camshaft gearbox drivetrain smokestack junkyard); @{$big_foo_bucket}[@assignList]=@other_foo_bucket[@assignList];

    Still not ideal, but the assignment of the list at least removes the problem of keeping the two in order. I suppose where you want to copy all of the elements of %other_foo_bucket you could have simply assigned @assignList=keys %other_foo_bucket; and it'd happily leave any items in %big_foo_bucket which aren't in %other_foo_bucket alone.

Re: Bulk Hash Assignment
by seattlejohn (Deacon) on Apr 17, 2002 at 15:19 UTC
    Isn't there a better way to specify the mapping?

    Well, map does come to mind... ;-)

    use strict; # define hash keys that need conversion # (others will be unchanged) my %map_foo_to_bar = ( gearbox => 'gearbox1', drivetrain => 'the_drivetrain', smokestack => 'my_smokestack', junkyard => '_junkyard', ); # our hash data my %foo = (gearbox => 'five-speed', drivetrain => 'automatic', something_else => 'entirely'); # perform the mapping (wrapped in an anonymous hashref) my $new_name; my $bar = { map { defined ($new_name = $map_foo_to_bar{$_}) ? ($new_name => $foo{$_}) : ($_ => $foo{$_}) } (keys %foo) };

    Then from Data::Dumper:

    $VAR1 = { 'the_drivetrain' => 'automatic', 'something_else' => 'entirely', 'gearbox1' => 'five-speed' };

    I've come to think of map a potential solution whenever I want to apply the same operation to every element of an array or hash. In this case the "same operation" just happens to be a conditional -- if we have a new name defined for a given hash key, then use it, otherwise just keep the original hash key.

Re: Bulk Hash Assignment
by premchai21 (Curate) on Apr 17, 2002 at 17:07 UTC

    YAWTDI -- you could use an anonymous function...

    sub { for(map{$_*2} 0..$#_/2) { $bfb->{$_[$_+1]} = $ofb{$_[$_]} }}->(q +w[camshaft camshaft ...]);

    Or, alternatively:

    sub { my%g=@_; for(keys%g) { $bfb->{$g{$_}} = $ofb{$_} }}->(qw[camshaf +t camshaft ...]);