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

I'm trying to understand how a particular line of an example script works. The full script is:
#!/usr/bin/perl @table = qw{ 4:a:400 1:b:300 3:c:200 2:d:100 }; print "Original table...\n\n"; foreach (@table) { print "$_\n"; } print "\n"; $, = "\n"; print "Sorted table...\n\n"; print sort { (split ":", $a)[0] <=> (split ":", $b)[0] } @table; print "\n";
The part I can't seem to understand is:
print { (split ":", $a)[0] <=> (split ":", $b)[0] }
I was unable to conceive of a successful line of code when I tried to sort the array myself.Would someone please help me with the interpretation of how this line successfully sorts the array by the leading number of each element?

Thanks!

Replies are listed 'Best First'.
Re: sorting an array with mixed elements...
by FunkyMonk (Bishop) on Jun 09, 2007 at 22:03 UTC
    I presume you meant

    print sort { (split ":", $a)[0] <=> (split ":", $b)[0] } @table;

    sort {...} @table will sort the contents of the array @table according to the contents of {...}

    sort works by comparing pairs of elements, calling them $a and $b within the {...}.

    split ":", $a will split $a into a list using ":" as the separator

    (...)[0] will return the first element of that list

    So, given input like:

    111:A:B;C 2:D:E:F

    This will sort by looking at the first field only, namely "111" & "2", treating them as numbers because of the use of <=>.

    update: Oops, I forgot to mention <=>, the numeric comparison operator, usually called the spaceship operator.

      I believe I'm confused by the occurrence and use of split(), sort() and print(), all, on one line. Is there a way to run these three functions separately, but sequentially?

      Thanks!

        print sort { (split ":", $a)[0] <=> (split ":", $b)[0] } @table;
        Is the same as:
        my @sorted = sort { my @A = split ':', $a; my @B = split ':', $b; $A[0] <=> $B[0]; } @table; print @sorted;

        You can't split it up completely, but you could write like so

        sub compare { (split ":", $a)[0] <=> (split ":", $b)[0] } my @sorted = sort compare @table; print @sorted;

        It would also benefit from applying a Schwartzian Transform (ST):

        print map { $_->[1] } sort { $a->[0] <=> $b->[0] } map { [ (split /:/, $_)[0], $_ ] } @table;

        A ST is read from the bottom up

        map { [ (split /:/, $_)[0], $_ ] } @table;

        creates an anonymous array with the thing you want to sort on as the first element ((split /:/, $_)[0]), and the original line as the second($_)

        sort { $a->[0] <=> $b->[0] }

        does the sort, using the first element of the arrays you just created

        map { $_->[1] }

        returns the original line from the second element of your arrays.

        There's loads of material on the interweb on the ST if you want to know more.

        update: fixed a couple of typos

Re: sorting an array with mixed elements...
by friedo (Prior) on Jun 09, 2007 at 21:57 UTC

    sort takes a block of code in which $a and $b get set to various elements of your list and compared to one another to find the sort order. (See the "spaceship operator" in perlop for how <=> works.)

    Inside the sort block, split is used to turn each colon-separated string into a list. The first element of each list (subscript zero) is then examined for the sort.

    Update: Turns out that perlop doesn't actually call it the "spaceship operator." :( You have to search for the far more mundane "equality operators" section.

Re: sorting an array with mixed elements...
by Cristoforo (Curate) on Jun 10, 2007 at 03:16 UTC
    Or, to simplify the sort, the code could be...
    print "Sorted table...\n\n"; { no warnings 'numeric'; print sort { $a <=> $b } @table; }
    Perl will sort numerically if the leading characters are 0-9 and issue a warning if the remaining characters are not numeric, as in this example. Warnings in this block are temporarlily turned off.
    Chris

    Update: playing around with the above code, I discovered that if I put another print sort { $a <=> $b } @table; following the block, warnings weren't given, even though I believed they should be - after all, that was the reason for putting the no warnings... code in its own scope (block).
    If I commented out the call to print in the enclosed block, then warnings were given for the call to print following the block.

    Update2: Warnings indeed do come back on (if a second list, @list2, also of mixed alphanumeric values) is sorted following the block.

      To use the method proposed by Cris...
      print "Sorted table...\n\n"; { no warnings 'numeric'; print sort { $a <=> $b } @table; }
      ...when each element starts with an alphabetic character, and an alphabetic sort is desired (targeting each element's initial alpha character) this would,presumably, then become:
      print "Sorted table...\n\n"; { # no warnings 'numeric'; print sort { $a cmp $b } @table; }
      Is a "no warnings" statement also warranted?

      Thanks!

        print sort { $a cmp $b } @table;

        That is the default and you'd better not specify it at all.

        Is a "no warnings" statement also warranted?

        No except perhaps if any element of @table is undef and you want it to be and perl not to complain. Do you?