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 SkipVought 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
0
0
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