Rob L
Rob L

Reputation: 3303

How can I get this basic Perl sub program that sorts to work properly?

I am brand new to Perl. Can someone help me out and give me a tip or a solution on how to get this sorting sub program to work. I know it has something to do with how arrays are passed to sub programs. I searched online and did not find an answer that I was satisfied with... I also like the suggestions the helpful S.O. users give me too. I would like to have the program print the sorted array in the main sub program. Currently, it is printing the elements of the array @a in original order. I want the sub program to modify the array so when I print the array it is in sorted order. Any suggestions are appreciated. Of course, I want to see the simplest way to fix this.

sub sort {

    my @array = @_;

    my $i;
    my $j;
    my $iMin;

    for ( $i = 0; $i < @_ - 1; $i++ ) {

        $iMin = $i;

        for ( $j = $i + 1; $j < @_; $j++ ) {

            if ( $array[$j] < $array[$iMin] ) {

                $iMin = $j;
            }
        }

        if ( $iMin != $i ) {

            my $temp = $array[$i];
            $array[$i] = $array[$iMin];
            $array[$iMin] = $temp;
        }
    }
}

Then call from a main sub program:

sub main {

    my @a = (-23,3,234,-45,0,32,12,54,-10000,1);

    &sort(@a);
    my $i;

    for ( $i = 0; $i < @a; $i++ ) {

        print "$a[$i]\n";

    }
}

main;

Upvotes: 0

Views: 97

Answers (2)

Vector Gorgoth
Vector Gorgoth

Reputation: 674

With very few, rare exceptions the simplest (and easiest) way to sort stuff in perl is simply to use the sort builtin.

sort takes an optional argument, either a block or a subname, which can be used to control how sort evaluates which of the two elements it is comparing at any given moment is greater.

See sort on perldoc for further information.

If you require a "natural" sort function, where you get the sequence 0, 1, 2, 3, ... instead of 0, 1, 10, 11, 12, 2, 21, 22, 3, ..., then use the perl module Sort::Naturally which is available on CPAN (and commonly available as a package on most distros).

In your case, if you need a pure numeric sort, the following will be quite sufficient:

use Sort::Naturally; #Assuming Sort::Naturally is installed

sub main {

    my @a = (-23,3,234,-45,0,32,12,54,-10000,1);

    #Choose one of the following
    @a = sort @a; #Sort in "ASCII" ascending order
    @a = sort { $b cmp $a } @a; #Sort in reverse of the above
    @a = nsort @a; #Sort in "natural" order
    @a = sort { ncmp($b, $a) } @a; #Reverse of the above

    print "$_\n" foreach @a; #To see what you actually got

}

It is also worth mentioning the use sort 'stable'; pragma which can be used to ensure that sorting occurs using a stable algorithm, meaning that elements which are equal will not be rearranged relative to one another.

As a bonus, you should be aware that sort can be used to sort data structures as well as simple scalars:

#Assume @a is an array of hashes
@a = sort { $a->{name} cmp $b->{name} } @; #Sort @a by name key

#Sort @a by name in ascending order and date in descending order
@a = sort { $a->{name} cmp $b->{name} || $b->{date} cmp $a->{date} } @a;

#Assume @a is an array of arrays

#Sort @a by the 2nd element of the arrays it contains
@a = sort { $a->[1] cmp $b->[1] } @a;

#Assume @a is an array of VERY LONG strings

#Sort @a alphanumerically, but only care about
#the first 1,000 characters of each string
@a = sort { substr($a, 0, 1000) cmp substr($b, 0, 1000) } @a;


#Assume we want to "sort" an array without modifying it:
#Yes, the names here are confusing. See below.
my @idxs = sort { $a[$a] cmp $a[$b] } (0..$#a);

print "$a[$_]\n" foreach @idxs;
#@idxs contains the indexes to @a, in the order they would have
#to be read from @a in order to get a sorted version of @a

As a final note, please remember that $a and $b are special variables in perl, which are pre-populated in the context of a sorting sub or sort block; the upshot is that if you're working with sort you can always expect $a and $b to contain the next two elements being compared, and should use them accordingly, but do NOT do my $a;, e.g., or use variables with either name in non-sort-related stuff. This also means that naming things %a or @a, or %b or @b, can be confusing -- see the final section of my example above.

Upvotes: 2

Miller
Miller

Reputation: 35198

When your sub does the following assignment my @array = @_, it is creating a copy of the passed contents. Therefore any modifications to the values of @array will not effect @a outside your subroutine.

Following the clarification that this is just a personal learning exercise, there are two solutions.

1) You can return the sorted array and assign it to your original variable

sub mysort {
    my @array = @_;
    ...
    return @array;
}

@a = mysort(@a)

2) Or you can pass a reference to the array, and work on the reference:

sub mysort {
    my $arrayref = shift;
    ...
}

mysort(\@a)

Also, it's probably a good idea to not use a sub named sort since that's that's a builtin function. Duplicating your code using perl's sort:

@a = sort {$a <=> $b} @a;

Also, the for loops inside your sub should be rewritten to utilize the last index of an @array, which is written as $#array, and the range operator .. which is useful for incrementors :

for ( my $j = $i + 1; $j <= $#array; $j++ ) {

# Or simpler:

for my $j ($i+1 .. $#array) {

And finally, because you're new, I should pass on that all your scripts should start with use strict; and use warnings;. For reasons why: Why use strict and warnings?

Upvotes: 5

Related Questions