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

I'm trying to sort the following array elements numerically based on the first 2 digits:
'04=critical' '02=informational' '01=unknown' '10=test' '03=warning' '08=foo'
I'm using the code below to do my sort, but it doesn't seem to be working
@values = sort { substr($a,1) <=> substr($b,1) } @values;
Any Ideas?

Replies are listed 'Best First'.
Re: substr sort ??
by davorg (Chancellor) on Aug 22, 2003 at 11:50 UTC

    Actually you can just lose the substr. It isn't gaining you anything. Perl will convert the strings to numbers and just Do The Right Thing.

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

    Update: broquaint reminds me that this will generate warnings under "use warnings". It will still give the right answer tho' :)

    --
    <http://www.dave.org.uk>

    "The first rule of Perl club is you do not talk about Perl club."
    -- Chip Salzenberg

      @values = sort { no warnings; $a <=> $b } @values;

      *shrugs*

      ------
      We are the carpenters and bricklayers of the Information Age.

      The idea is a little like C++ templates, except not quite so brain-meltingly complicated. -- TheDamian, Exegesis 6

      Please remember that I'm crufty and crochety. All opinions are purely mine and all code is untested, unless otherwise specified.

Re: substr sort ??
by guha (Priest) on Aug 22, 2003 at 11:33 UTC

    I think you want substr($a, 0, 2) instead of substr($a,1)

    Check out the syntax of substr here
Re: substr sort ??
by CombatSquirrel (Hermit) on Aug 22, 2003 at 12:02 UTC
    You could simply use @values = sort @values, since this checks the first two digits before the rest, yielding your ordering.
    And if you are interested, it is also more efficient:
    #!perl use strict; use warnings; use Benchmark; my @array = ( '04=critical', '02=informational', '01=unknown', '10=test', '03=warning', '08=foo', ); sub substr_num { @_ = sort { substr($a, 0, 2) <=> substr($b, 0, 2) } @array; } sub substr_str { @_ = sort { substr($a, 0, 2) cmp substr($b, 0, 2) } @array; } sub standard_str { @_ = sort { $a cmp $b } @array; } sub standard { @_ = sort @array; } timethese ( 100000, {'Numeric with Substring' => '&substr_num', 'String Cmp with Substring' => '&substr_str', 'Standard String Cmp' => '&standard_str', 'Standard' => '&standard'} );
    Yields the output:
    Benchmark: timing 100000 iterations of Numeric with Substring, Standar +d, Standard String Cmp, String Cmp with Substring... Numeric with Substring: 2 wallclock secs ( 2.47 usr + 0.00 sys = 2. +47 CPU) @40502.23/s (n=100000) Standard: 2 wallclock secs ( 1.59 usr + 0.00 sys = 1.59 CPU) @ 6277 +4.64/s (n=100000) Standard String Cmp: 2 wallclock secs ( 1.59 usr + 0.00 sys = 1.59 +CPU) @ 62735.26/s (n=100000) String Cmp with Substring: 2 wallclock secs ( 2.31 usr + 0.00 sys = + 2.31 CPU) @ 43233.90/s (n=100000)
    And therefore the Ranking
    • Standard
    • Standard String Cmp
    • String Cmp with Substring
    • Numeric with Substring
    Sometimes it's as simple as it seems :).
    Cheers, CombatSquirrel.
      You could simply use @values = sort @values, since this checks the first two digits

      They are not exactly the same thing.

      Your method will only work if every element had the same number of digits. If you were to compare "1abc" and "10abc", it would give you the wrong order.

      perl -e '@array=(qw(1abc 10abc));@sorted=sort @array; print "@sorted\n +";' 10abc 1abc perl -e '@array=(qw(1abc 10abc)); @sorted = sort {$a<=>$b} @array; print "@sorted\n";' 1abc 10abc
        I know, but the example had zero-padded fixed-width integer identifiers, which suggested that the numbers were always fixed width. I thought about a Schwartzian transform at first (like flounder99 did in Re: substr sort ??), but decided it was overkill and due to the multiple mappings slower than the direct comparison approach. If the identifiers were integers with different numbers of digits, though, my code wouldn't work, as you pointed out.
Re: substr sort ??
by flounder99 (Friar) on Aug 22, 2003 at 12:21 UTC
    I know it is not important in this case since @values is so small but on larger arrays a Schwartzian Transform might be in order. This example will take care of cases where the number of digits is not the same.
    my @values = ( '123=more digits', '04=critical', '02=informational', '01=unknown', '10=test', '03=warning', '08=foo', ); @values = map { $_->[1] } sort { $a->[0] <=> $b->[0] } map { [/(\d+)/, $_]} @values; print join "\n", @values; __OUTPUT__ 01=unknown 02=informational 03=warning 04=critical 08=foo 10=test 123=more digits

    --

    flounder

      Since the input data appear to be delimitted by the "=", using a split to generate the sort key might be more robust:
      #!/usr/bin/perl use warnings; use strict; my @values = ( '123=more digits', '04=critical', '02=informational', '01=unknown', '10=test', '03=warning', '08=foo', ); @values = map { $_->[1] } sort { $a->[0] <=> $b->[0] } map { [ ( split /=/ )[0], $_ ] } @values; print "@values\n";
      output = 01=unknown 02=informational 03=warning 04=critical 08=foo 10=test 123=more digits
Re: substr sort ??
by Zeroth (Beadle) on Aug 22, 2003 at 11:42 UTC
    You would have spotted this error easy if you had used use strict and use warnings.