John M. Dlugosz has asked for the wisdom of the Perl Monks concerning the following question:

It bothers me that I'm repeating the same code changing only two words: a string and the name of a global hash. But any way I think of to put a loop around one copy instead makes it longer than simply repeating it! Anyone got a cool way to do it that makes it shorter as well as having the logic only stated once?

$Data->Sql ("SELECT * FROM ConfigData"); while ($Data->FetchRow ()) { my %x= $Data->DataHash(); $ConfigData{$x{ID}}{$x{DataName}}= $x{DataValue}; } $Data->Sql ("SELECT * FROM DicomDestinations"); while ($Data->FetchRow ()) { my %x= $Data->DataHash(); $DestinationData{$x{ID}}{$x{DataName}}= $x{DataValue}; }

Replies are listed 'Best First'.
Re: reuse worth it for small items?
by Hue-Bond (Priest) on May 05, 2006 at 21:21 UTC

    I'd do a function. Maybe tomorrow you will need the same with another small variation.

    sub foo { my ($table, $hr) = @_; ## TODO: argument checking $Data->Sql ("SELECT * FROM $table"); while ($Data->FetchRow ()) { my %x= $Data->DataHash(); $hr->{$x{ID}}{$x{DataName}}= $x{DataValue}; } } foo "ConfigData", \%ConfigData; foo "DicomDestinations", \%DestinationData;

    --
    David Serrano

Re: reuse worth it for small items?
by Herkum (Parson) on May 06, 2006 at 03:12 UTC

    I saw what some other people have done and to a certain degree they are missing the point of encapsulation and design.

    1. You want a subroutine that prevents redundancy, excellent
    2. There is only one variable changed in the request
    3. There is only one variable changed from the result
    4. Keep your code focused, make it simple and do not do too many things at one time
    sub get_rows_from { my $dbh = shift; my $table = shift; $dbh->Sql ("SELECT * FROM " . $table); my %result; while ($dbh->FetchRow() ) { my %x= $dbh->DataHash(); $result{ $x{ID} } = \%x; } return \%result; } my $config_data_ref = $dbh->get_rows_from( 'ConfigData' ); my $dicom_data_ref = $dbh->get_rows_from( 'DicomDestinations' );

    So what we have a subroutine, with a simple interface. We only have to pass one thing to it (the table name). We will get back a hash reference. Since we are not doing data manipulation in the subroutine, we do not have to guess what the result will be if we passed a hash reference to the subroutine.

      I think you made a good point: one of the changed things is a parameter, but the other is a result and should be handled differently.

      I'd thought about making it a function or a loop body with the other concepts mentioned on this thread: pairs, or a hash mapping the name to the variable. That is enlightening: thanks.

      —John

Re: reuse worth it for small items?
by Zaxo (Archbishop) on May 05, 2006 at 22:08 UTC

    I'll take a stab at it,

    my %Control = ( ConfigData => \%ConfigData, DicomDestinations => \%DestinationData ); for my $set (qw/ConfigData DicomDestinations/) { $Data->Sql ("SELECT * FROM $set"); while ($Data->FetchRow ()) { my %x = $Data->DataHash(); $Control{$set}{$x{ID}}{$x{DataName}}= $x{DataValue}; } }
    That does what you wanted at the cost of another hash.

    Is it worth it? That depends on how likely it is that the two operations will diverge in the future.

    Added: Another way, probably better,

    sub DataClass::doit { my ($self, $set, $hash) = @_; $self->Sql ("SELECT * FROM $set"); while ($self->FetchRow ()) { my %x = $self->DataHash(); $hash->{$x{ID}}{$x{DataName}}= $x{DataValue}; } 1; } $Data->doit('ConfigData',\%ConfigData); $Data->doit('DicomDestinations', \%DestinationData);
    I wrote that as an additional method wedged into $Data's class. It would be more careful to subclass that if possible. I did not wish to create a closure on $Data.

    After Compline,
    Zaxo

Re: reuse worth it for small items?
by hesco (Deacon) on May 06, 2006 at 08:39 UTC
    Absolutely. Code reuse is worth it because with the careful choice of routine and variable names it can make your code easier to read and maintain. It makes it easier to approach that ideal of self-dosumented code. You have a few good options to work with here. So I won't try to optimize these. I'll focus on the question in your subject line. Yes, its worth it to refactor.

    -- Hugh

    if( $lal && $lol ) { $life++; }
Re: reuse worth it for small items?
by jonadab (Parson) on May 05, 2006 at 21:29 UTC

    It is certainly possible to golf this down, and it is an interesting exercise, but in practice it's unlikely to make the code more legible or reusable, so for production purposes I would not recommend it.

    As far as how to golf it down, my first thought would be to reach for map update: like the following...

    map{$Data->Sql("SELECT * FROM $_"); while($x=$Data->fetchrow_hashref){ $ConfigData{$$x{ID}}{$$x{DataName}}=$$x{DataValue}}; }qw(ConfigData DicomDestinations);

    I'm sure someone can shave some more strokes off that.


    Sanity? Oh, yeah, I've got all kinds of sanity. In fact, I've developed whole new kinds of sanity. Why, I've got so much sanity it's driving me crazy.

      The map doesn't buy you much in terms of golf here, and he's also changing the target hash. I'd probably approach it something more like:

      my @table_to_hash = ( [ "ConfigData", \%ConfigData ], [ "DicomDestinations", \%DestinationData ] ); for (@table_to_hash) { my ($table, $hash) = @$_; $Data->Sql("SELECT * FROM $table"); while ($Data->FetchRow ()) { my %x = $Data->DataHash(); $hash->{ $x{ID} }{ $x{DataName} } = $x{DataValue}; } }

      Update: I see this is almost the same as Zaxo's post at Re: reuse worth it for small items?, but note that mine will maintain the order. I'm not sure it matters, but it could.

Re: reuse worth it for small items?
by tinita (Parson) on May 07, 2006 at 01:08 UTC
    just another thing that comes to mind:
    you're doing two SELECTs on tables that maybe have the same scheme. if that is the case perhaps you can do one UNION instead?