Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations Mike Lewis on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

Sort version numbers 1

Status
Not open for further replies.

whn

Programmer
Oct 14, 2007
265
US
My company has multiple product lines and the version numbers are in different format.

For instance, one product line's version numbers may look like this: 5, 5.2, 5.11. The highest version in this example is 5.11. Therefore, we cannot simply sort them numerically.

Another product line's version numbers may look like this: 6.1.11, 6.1.22, 6.2.3, 6.2.11. This would make sorting more difficult.

And right now, the longest version number looks like this: i.j.k.l.

I want to write a piece of perl code to sort these version numbers out and it should be designed to handle future version numbers in a generic format: i.j.k. ... .m. ... .n.

I came up a solution which can handle the current situations. But it's semi-hard coded. I am hoping someone here could make it more robust.

My codes are listed below:
Code:
#!/usr/bin/perl

use strict;
use warnings;
use Data::Dumper;

my $tail = 't';
my @ver = (6.1, '6.1.2', '6.1.11'); [COLOR=#3465A4][b]# this line is for 1st run[/b][/color]
#@ver = (5.2, 5, 5.3, 5.12); [COLOR=#3465A4][b]# enable this line in 2nd run[/b][/color]
my $info = &a2h(\@ver);
print Dumper($info);

&pickHighest($info);

[COLOR=#F57900][b]# Method pickHighest() is new and is somewhat hard coded.
# 1) I am not sure if the implementation is on the right track?
# 2) If it's on the right track, how to make it more robust?[/b][/color]
sub pickHighest {
  my $verInfo = $_[0];
  my @v1 = sort numeric (keys(%{$verInfo}));
  print "Highest Major Release: $v1[$#v1]\n";
  if(ref($verInfo->{$v1[$#v1]}) eq 'HASH') {
    my @v2 = sort numeric (keys(%{$verInfo->{$v1[$#v1]}}));
    print "Highest Sub-Major Release: $v2[$#v2]\n";
    if(ref($verInfo->{$v1[$#v1]}->{$v2[$#v2]}) eq 'HASH') {
      my @v3 = sort numeric (keys(%{$verInfo->{$v1[$#v1]}->{$v2[$#v2]}}));
      print "Highest Minor Release: $v3[$#v3]\n";
      if(ref($verInfo->{$v1[$#v1]}->{$v2[$#v2]}->{$v3[$#v3]}) eq 'HASH') {
        [COLOR=#EF2929][b]# To be implemented...
        # If there was a version number like this: i.j.k. ... .m.n
        # then this kind of if/else block would go ridiculously long
        # and I'd call it semi-hardcode, which I hate it.[/b][/color]
      }
      else {
        print "This is the highest release: $v1[$#v1]\.$v2[$#v2]\.$v3[$#v3]\n";
      }
    }
    else {
      print "This is the highest release: $v1[$#v1]\.$v2[$#v2]\n";
    }
  }
  else {
    print "This is the highest release: $v1[$#v1]\n";
  }
}

exit;

[COLOR=#F57900][b]# Method a2h() was discussed yesterday in this [url=http://www.tek-tips.com/viewthread.cfm?qid=1707676]thread[/url]
# This method can handle a version number like this: i.j.k. ... .m.n[/b][/color]
sub a2h {
  my $a = $_[0];
  my %h = ();
  foreach my $x (@{$a}) {
    my @field = split(/\./, $x);
    my $hRef = \%h;
    my $hPrev;
    my $l = scalar @field;
    for (my $i = 0; $i < $l; $i++) {
      $hPrev->{$field[$i - 1]} = $hRef = {} unless ref $hRef eq 'HASH';
      $hRef->{$field[$i]} = $i < $l - 1 ? {} : $tail unless exists $hRef->{$field[$i]};
      $hPrev = $hRef;
      $hRef = $hRef->{$field[$i]};
    }
  }
  return \%h;
} 

sub numeric { $a <=> $b; }

First sample run:
Code:
% ./sortVer.pl
$VAR1 = {
          '6' => {
                   '1' => {
                            '11' => 't',
                            '2' => 't'
                          }
                 }
        };
Highest Major Release: 6
Highest Sub-Major Release: 1
Highest Minor Release: 11
This is the highest release: 6.1.11

And the 2nd sample run:
Code:
% ./sortVer.pl
$VAR1 = {
          '5' => {
                   '3' => 't',
                   '12' => 't',
                   '2' => 't'
                 }
        };
Highest Major Release: 5
Highest Sub-Major Release: 12
This is the highest release: 5.12

In summary, the results are ok. Just the implementation is not robust at all.

Thank you for your time and help.
 
I think they way I would approach this would be to use a custom sort function.

Perl:
sub soft_ver_sort {
	my @a_parts = split /\./, $a;
	my @b_parts = split /\./, $b;
	my $max_idx = ($#a_parts >= $#b_parts ? $#a_parts : $#b_parts);
	
	for (my $i = 0; $i <= $max_idx; $i++) {
		unless (defined $a_parts[$i]) { return -1;}
		unless (defined $b_parts[$i]) { return 1;}
		
		if ($a_parts[$i] < $b_parts[$i]) {
			return -1;
		} elsif ($a_parts[$i] > $b_parts[$i]) {
			return 1;
		} else {
			if ($i == $max_idx) {
				return 0;
			}
		}
	}
}

And here's a function for the printing that you were doing, in case you needed that:

Perl:
sub print_ver_info {
	my $version = shift;
	my @ver_parts = split /\./, $version;
	
	print "Highest Major Release: " . $ver_parts[0] . "\n";
	print("Highest Sub-Major Release: " . $ver_parts[1] . "\n") if $ver_parts[1];
	print("Highest Minor Release: " . $ver_parts[2] . "\n") if $ver_parts[2];
	print "This is the highest version number: " . $version . "\n";
}

Using that custom sort function, finding the highest version becomes fairly trivial.

Perl:
my @unsorted = qw/5.0.1.1 6.0.1 5.2 4.3.1.2.5 4.2.3.2.2.2.5.8.85 5.0.1 4.3.1 6.1 6.0.0.1 5  4.2.3.2.2.2.5.8.16/;
my @sorted = sort {[b]&soft_ver_sort[/b]} @unsorted;
print "Sorting lots of versions:\n", join("\n", @sorted), "\n";
[b]print_ver_info($sorted[-1]);[/b]

And with your examples:

Perl:
@unsorted = qw/6.1 6.1.2 6.1.11/;
print_ver_info((sort {[b]&soft_ver_sort[/b]} @unsorted)[-1]);

print_ver_info((sort {[b]&soft_ver_sort[/b]} qw/5.2 5 5.3 5.12/)[-1]);

The sub used for printing would need to be modified if you ended up needing to print more of the sub versions; sorting should work regardless of how many sub version numbers are included.
 
Wow, it's amazing. But I have to admit that I need a bit more time to digest it.

Thank you so much, rharsh!!

p.s. Sorry for late reply. It's a long weekend in the States. Again, thank you and best regards to you.
 
You're welcome, glad it works for you.

A few thoughts: while the custom sort is a fairly clean/simple way to approach this problem, using custom sort functions can be computationally expensive. If you're sorting huge lists of versions and performance becomes a problem, an approach similar to your original one might be the best way to go. I would probably employ recursion to 'walk through' the hash that was created with a2h() -- which would save you from having to hard code all the nested levels. But recursion is a bit more complex and possibly harder to support.
 
A different, simpler, likely faster, approach. Of course it assumes that the highest possible version number is 99, but can be changed for 999, 9999, ...
Code:
my @unsorted = qw/5.0.1.1 6.0.1 5.2 4.3.1.2.5 4.2.3.2.2.2.5.8.85 5.0.1 4.3.1 6.1 6.0.0.1 5  4.2.3.2.2.2.5.8.16/;
for(@unsorted){$_=join('.',map{sprintf"%02d",$_}split(/\./,$_))}
my @sorted = sort@unsorted;
print"@sorted\n";

: Online engineering calculations
: Magnetic brakes for fun rides
: Air bearing pads
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top