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

I am trying populate a text file with version information. Since the program that reads this file keys off of the #DBVersion value, every time I update the parts of the version, the next lines' #DBVersion must be changed to match what has just been changed. Here is part of the text I am trying to display:
Update aht set major = 9 #DBVersion=8.3.6 Update aht set minor = 4 #DBVersion=9.3.6 Update aht set revision = 0 #DBVersion=9.4.6
My trouble is that any part of the version could be updated so I might have to change any combination of major, minor, or revision and the corresponding numbers in the #DBVersion.
In another part of my program I initialize the $UpdatedMajor, $UpdatedMinor, etc. to "" and do a comparison between the versions and if any of the elements are different I populate $Updated with that difference. So, if for example $UpdatedMajor is populated then major has to be changed to the updated value. Here is what I have so far.
if (($UpdatedMajor != "") && ($UpdatedMinor != "") && ($UpdatedRevisio +n != "")){ printf WRITETOFILE 'Update aht set major = '."$UpdatedMajor"."\t\t\ +t".'#DBVersion='."$OldMajor.$OldMinor.$OldRevision\n"; printf WRITETOFILE 'Update aht set minor = '."$UpdatedMinor"."\t\t\ +t".'#DBVersion='."$UpdatedMajor.$OldMinor.$OldRevision\n"; printf WRITETOFILE 'Update aht set revision = '."$UpdatedRevision". +"\t\t\t".'#DBVersion='."$UpdatedMajor.$UpdatedMinor.$OldRevision\n"; + }
Right now I am thinking I will have to do 9 if's to cover all the possible scenarios. Is there any way to avoid doing this 9 times?

Replies are listed 'Best First'.
Re: Trying to avoid 9 conditionals
by GrandFather (Saint) on Oct 15, 2008 at 21:37 UTC

    Use a hash to store the original version values and another to store the edits then you can:

    use strict; use warnings; my @valueNames = qw/major minor revision/; my %oldVersions = ( aht => {major => 8, minor => 3, revision => 6}, dog => {major => 3, minor => 3, revision => 1}, ); my %versionEdits = ( aht => {major => 9, minor => 4, revision => 0}, dog => {revision => 2}, ); for my $version (sort keys %versionEdits) { my %values; $values{$_} = $oldVersions{$version}{$_} for @valueNames; for my $edit (sort keys %{$versionEdits{$version}}) { my $newValue = $versionEdits{$version}{$edit}; print "Update $version set $edit = $newValue #DBVersion "; print join '.', @values{@valueNames}; print "\n"; $values{$edit} = $newValue; } }

    Prints:

    Update aht set major = 9 #DBVersion 8.3.6 Update aht set minor = 4 #DBVersion 9.3.6 Update aht set revision = 0 #DBVersion 9.4.6 Update dog set revision = 2 #DBVersion 3.3.1

    which scales nicely as you add more things you are tracking versions of. The general rule is - if there is more than one, put it in a data structure. If you find you are using a proliferation of variables you have probably gone about it in the wrong way. If you find yourself writing the same piece of code over again you have probably gone about it the wrong way.


    Perl reduces RSI - it saves typing
      Thanks for the response. I implemented and it works great! Is there anyway to only print out the ones that changed? For example if the old version was 836 and the new version was 840 then only print out
      Update aht set minor = 4 #DBVersion 8.3.6 Update aht set revision = 0 #DBVersion 8.4.6
      Or if the old was 836 and the new was 837 only print out
      Update aht set revision = 7 #DBVersion 8.3.6

        The intent of %versionEdits was that it would only contain the edits that you want to be reported. I don't know how you are obtaining the version change information that you were using to populate $UpdatedMinor etc so I can't advise on how best to populate %versionEdits. If you have an original version number and a current version number you could do something like:

        my @valueNames = qw/major minor revision/; my @versions = ( [qw(aht 8.3.6 9.4.0)], [qw(dog 3.3.1 3.3.2)], ); for my $version (@versions) { my ($app, $oldStr, $newStr) = @$version; my %old; my %new; @old{@valueNames} = split /\./, $oldStr; @new{@valueNames} = split /\./, $newStr; for my $name (@valueNames) { $oldVersions{$app}{$name} = $old{$name}; next if $new{$name} == $old{$name}; $versionEdits{$app}{$name} = $new{$name}; } }

        Perl reduces RSI - it saves typing
Re: Trying to avoid 9 conditionals
by TGI (Parson) on Oct 15, 2008 at 22:38 UTC

    Any time you find yourself making an ordered series of scalars, for example: $item_1, $item_2, ... you should consider using an array.

    If you store your major, minor, and revision elements in an array you can operate on them as a group.

    use strict; # always use warnings; # always use diagnostics; # a pretty good idea # Since I chose to use a couple of arrays to hold my # version info and update status, I'll make some # constants to make accessing them more hashlike. use constant MAJOR => 0; use constant MINOR => 1; use constant REV => 2; use constant KEYWORDS => qw( major minor revision ); use constant VERSION_ELEMENTS => ( MAJOR, MINOR, REV ); # test data my $old_version = '8.3.1'; my $new_version = '9.3.1'; # get an array with true values that indicate changes. my @updated = compare_versions( $old_version, $new_version ); # break new version string into an array my @new_version = split_version( $new_version ); # Generate a list of commands to print # If you have other processing to do as you make # your command list a foreach loop may make more sense # than using map. my @commands = map { $updated[$_] # did we update this field? ? update_aht_command( # if so, generate a command (KEYWORDS)[$_], $new_version[$_], "#DBVersion=$new_version", ) : (); # otherwise put nothing in the command lis +t. } VERSION_ELEMENTS; print @commands; # make version strings into arrays. sub split_version { my $version = shift; return split /\./, $version; } # compare version strings. # generate array of changes. sub compare_versions { my $old = shift; my $new = shift; # convert version strings to arrays my @old = split_version($old); my @new = split_version($new); # handle a length mismatch. my $last = $#old > $#new ? $#old : $#new; # make a list with true values where changes have occurred. my @updated = map { $old[$_] != $new[$_] } (0..$last); # return the list. return @updated; } sub update_aht_command { my $keyword = shift; my $new_value = shift; my $comment = shift; return join '', "Update aht set $keyword = $new_value", defined $comment ? ( "\t"x3, $comment ) : ( ) ,"\n"; }

    Based on what I see in your sample code, I'd suggest you take a look at print, printf, and the qoute and quote-like operators section of perlop. Reading map should help with understanding my solution.


    TGI says moo

      Setting:

      my $new_version = '9.4.0';

      Prints:

      Update aht set major = 9 #DBVersion=9.4.0 Update aht set minor = 4 #DBVersion=9.4.0 Update aht set revision = 0 #DBVersion=9.4.0

      which is not quite what the OP seemed to be after.

      Update: changed 9.4.6 to 9.4.0 to be consistent with OP's sample.


      Perl reduces RSI - it saves typing

        You are right. I missed the fact that each element in the comment (is it really a comment?) ticks over indvidually.

        The good news is that it's easy enough to modify my code to support that behavior.

        my @current_version = split_version($old_version); my @commands = map { if( $updated[$_]) { # did we update this field? my $current_version = join '.', @current_version; $current_version[$_] = $new_version[$_]; update_aht_command( # if so, generate a command (KEYWORDS)[$_], $new_version[$_], "#DBVersion=$current_version", ) } else { # otherwise put nothing in the command list. (); } } VERSION_ELEMENTS;

        The bad news is that I've now got code with side-effects in my map block. Yuck.

        So in this case I'd probably refactor and use a foreach to iterate my arrays.

        my @current_version = split_version($old_version); my @commands; foreach my $part ( VERSION_ELEMENTS ) { if( $updated[$part]) { # did we update this field? my $current_version = join '.', @current_version; $current_version[$part] = $new_version[$part]; push @commands, update_aht_command( (KEYWORDS)[$part], $new_version[$part], "#DBVersion=$current_version", ) } }

        Warning: I edited this code in the text box on this page and I haven't tested it, it probably has typos, fleas and other bugs.


        TGI says moo