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

Hi Monks, What's a cool perlish way to sort these two data structures numerically (i,e. POST100 comes after POST5):
my @array = ('POST100','POST5','POST10','POST1') #etc my %array = ('POST100' => 1, 'POST5' => 1, ...) #etc
something like...
foreach my $postNum (sort @array) { #process post #X }
thanks, Michael

Replies are listed 'Best First'.
Re: perl sorting
by ColonelPanic (Friar) on May 06, 2004 at 21:02 UTC
    my @sorted = sort {substr($a,4) <=> substr($b,4)} @array; foreach (sort {substr($a,4) <=> substr($b,4)} keys %hash_actually) { ... }


    When's the last time you used duct tape on a duct? --Larry Wall
      you guys are amazing.
Re: perl sorting
by Ovid (Cardinal) on May 06, 2004 at 21:03 UTC

    The "simple" way to sort @array numerically is to use a Schwartzian Transform:

    @array = map { $_->[0] } sort { $a->[1] <=> $b->[1] } map { [$_ => get_num($_)] } @array; sub get_num { my $string = shift; my ($num) = $string =~ /(\d+)/; return $num; }

    As for the hash, hashes are inherently unsorted, so what you need to do depends upon what you want to do.

    Update:: ColonelPanic's solution is simpler if you can guarantee that the numbers always start in the same position in the string.

    Cheers,
    Ovid

    New address of my CGI Course.

Re: perl sorting (a bit more stupid -- er, efficient :)
by Ovid (Cardinal) on May 06, 2004 at 21:22 UTC

    Of course, if you want to have some fun ...

    #!/usr/local/bin/perl use warnings; use strict; use Data::Dumper; use Inline 'C'; + my @array = ('POST100','POST5','POST10','POST1'); + my @sorted = sort { $a <=> $b } map { make_bimodal($_) } @array; + print Dumper \@sorted; + sub make_bimodal { my $val = shift; my $num = substr $val => 4; my $dual; set_both($dual, $val, $num+0); return $dual; } + __DATA__ __C__ + void set_both(SV* variable, SV* string, SV* numeric) { SvPV(string, PL_na); if(!SvPOKp(string) || (!SvNOKp(numeric) && !SvIOKp(numeric)) ) { croak("Usage: set_both variable,string,numeric"); } + sv_setsv(variable,string); if(SvNOKp(numeric)) { sv_setnv(variable,SvNV(numeric)); } else { sv_setiv(variable,SvIV(numeric)); } SvPOK_on(variable); }

    No, I'm not serious, but yes, it will work :)

    Cheers,
    Ovid

    New address of my CGI Course.

Re: perl sorting
by pizza_milkshake (Monk) on May 06, 2004 at 20:58 UTC
    #!perl -wl # this will sort numerically by the first number found; and alphabetic +ally # if necessary # and of course it's schwartzian-transform-ized, because hey why not use strict; my @array = qw(POST100 XX1Z XX1A XX2B POST5 POST10 POST1 BOB1); foreach ( map { $_->[0] } sort { $b->[1] <=> $a->[1] || $a->[0] cmp $b->[0] } map { [ $_ , do { /(\d+)/; $1 } ] } @array ) { print; }

    perl -e"\$_=qq/nwdd\x7F^n\x7Flm{{llql0}qs\x14/;s/./chr(ord$&^30)/ge;print"

Re: perl sorting (natural)
by tye (Sage) on May 06, 2004 at 22:13 UTC

    Searching for Natural sort finds a few favorite ways of mine and others'.

    - tye        

Re: perl sorting
by QM (Parson) on May 06, 2004 at 23:43 UTC
    Trying for the generic mixed case nightmare, in "obvious" mode:
    #!/your/perl/here # demonstrate sorting numerically within a string # # for example, abc2xyz sorts before abc14xyz # use strict; use warnings; my @list = map { $_ . "\n" } qw( a1 a2 a10 a1a a2a a10a a01a a10b a1b2 +c a1b10c aa1 ); print sort mixed_sort @list; { # closure for caching, etc. my %seen; sub mixed_sort { return $seen{$a,$b} if exists( $seen{$a,$b} ); $seen{$a,$b} = 0; my $re_num = qr/\d+/; my $re_both = qr/^(\d+|\D+)+$/; # split into alpha and numeric fields my @x = $a =~ /$re_both/go; my @y = $b =~ /$re_both/go; my $longest = @x > @y ? @x-1 : @y-1; foreach my $i ( 0..$longest ) { if ( defined( $x[$i] ) and defined( $y[$i] ) ) { if ( ( $x[$i] =~ /^$re_num$/o ) and ( $y[$i] =~ /^$re_num$/o ) ) { $seen{$a,$b} = $x[$i] <=> $y[$i]; # both are numbe +rs } else { $seen{$a,$b} = $x[$i] cmp $y[$i]; } } elsif ( defined( $x[$i] ) ) { $seen{$a,$b} = +1; } elsif ( defined( $y[$i] ) ) { $seen{$a,$b} = -1; } # else they're both undef, and nothing changes yet return $seen{$a,$b} if $seen{$a,$b}; } return $seen{$a,$b} = $a cmp $b; # if all else fails } # end of sub mix_sort } # end of closure for sub mixed_sort __END__
    Perhaps someone can add to it for generic distinctions (numbers are not just \d+), more than 2 categories, etc.

    -QM
    --
    Quantum Mechanics: The dreams stuff is made of

Re: perl sorting
by ysth (Canon) on May 07, 2004 at 00:41 UTC
    Not sure how you "sort" a hash (other than somehow using the list output by sort {...} keys %hash).

    Completely untested, just an idea:

    @sorted = map { join "", @$_ } sort { no warnings qw/numeric uninitialized/; (grep $_, map { $a->[$_] <=> $b->[$_] || $a->[$_] cmp $b->[$_] } 0..($#$a+@$b) )[0] || 0 } map { [ split /(?<=\d)(?=\D)|(?<=\D)(?=\d)/, $_ ] } @array;
    Update: oops, left the array indexes off $a and $b