Beefy Boxes and Bandwidth Generously Provided by pair Networks
XP is just a number
 
PerlMonks  

How can I sort my array numerically on part of the string?

by misterperl (Scribe)
on Dec 01, 2020 at 16:59 UTC ( #11124461=perlquestion: print w/replies, xml ) Need Help??

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

I have this array:

my @list = ( '1,cat', '2,dog', '22,mouse', '11,eel', '001,elk', '13,mink');

that I'd like to sort numerically based on the number in front of the comma, so I tried:

print join "\n", sort { ($a=~s/,.+//) <=> ( $b =~ s/,.+// ) } @list;

But I seem to get an alpha, non-numeric result, which has the added detriment of dropping off my animals...

1 2 22 11 001
I also tried approaches like  "\A\d+$a" <=> "\A\d+$b" which the interpreter REALLY hated!

TY Wise ones.

Replies are listed 'Best First'.
Re: How can I sort my array numerically on part of the string?
by jdporter (Canon) on Dec 01, 2020 at 18:09 UTC

    You're in luck — the numeric part is at the beginning of the string.

    my @list = ( '1,cat', '2,dog', '22,mouse', '11,eel', '001,elk', '13,mi +nk'); my @sorted = sort { $a <=> $b } @list;
    I reckon we are the only monastery ever to have a dungeon stuffed with 16,000 zombies.
      the numeric part is at the beginning of the string

      True, though I think it's noteworthy this requires a no warnings 'numeric' under warnings, e.g. my @sorted = sort { no warnings 'numeric'; $a <=> $b } @list;.

        now I get a different error "no" not allowed in expression
      I get 1,cat is not numeric. HUH?
Re: How can I sort my array numerically on part of the string? (updated)
by haukex (Bishop) on Dec 01, 2020 at 17:10 UTC

    How about sort { ($a=~/(\d+),/)[0] <=> ($b=~/(\d+),/)[0] or $a cmp $b } @list? (see also return values of regular expressions) Though a Schwartzian transform would be much more performant:

    @list = map { $$_[0] } sort { $$a[1] <=> $$b[1] or $$a[0] cmp $$b[0] } map { /(\d+),/; [$_,$1] } @list;

    Note I added the or cmp so that if the numeric parts are equal (e.g. '1,cat' vs. '001,elk'), the list is still reliably sorted.

    Update: The above doesn't handle cases of the regex not matching. In my second piece of code above you could handle that with an error via e.g. map { /(\d+),/ or die $_; [$_,$1] } or a replacement value via e.g. map { [$_, /(\d+),/ ? $1 : 0] }.

      >  sort { ($a=~/(\d+),/)[0] <=> ($b=~/(\d+),/)[0]

      You need that (...)[0] for the match to return the captures in list context, ( <=> is enforcing scalar context and m// only returns captures in list context otherwise boolean )

      I'm wondering if there is a prettier solution for that.

      The Schwartzian transform doesn't have that limitation.

      ... map { [$_, /(\d+),/] } @list;

      should do already.

      update

      clarification: any better solution than (...)[0] to get list context ?

      Cheers Rolf
      (addicted to the Perl Programming Language :)
      Wikisyntax for the Monastery

        Here's a capture in list context. Prettier?

        #!/usr/bin/perl use strict; # https://perlmonks.org/?node_id=11124461 use warnings; my @list = ( '1,cat', '2,dog', '22,mouse', '11,eel', '001,elk', '13,mi +nk'); my @n; @n[ /(\d+),/ ] .= "$_\n" for @list; print grep defined, @n;

        This is why perl is fun :)

        TY I didnt realise I needed the [0] I'm gonna try that. Although I'm unclear as to what "list" is involved with $a and $b which appear to be scalar?
Re: How can I sort my array numerically on part of the string?
by tybalt89 (Prior) on Dec 01, 2020 at 21:15 UTC

    You were sooo close...

    #!/usr/bin/perl use strict; # https://perlmonks.org/?node_id=11124461 use warnings; my @list = ( '1,cat', '2,dog', '22,mouse', '11,eel', '001,elk', '13,mi +nk'); print join "\n", sort { $a=~s/,.+//r <=> $b =~ s/,.+//r } @list;

    Outputs:

    1,cat 001,elk 2,dog 11,eel 13,mink 22,mouse
Re: How can I sort my array numerically on part of the string?
by BillKSmith (Prior) on Dec 01, 2020 at 21:37 UTC
    Use the function nsort_by in the module List::SomeUtils.
    use strict; use warnings; use List::SomeUtils qw(nsort_by); my @list = ( '1,cat', '2,dog', '22,mouse', '11,eel', '001,elk', '13,mi +nk'); my @sorted = nsort_by {/0*(\d+)\,/;$1} @list; {local $" = q(', '); print qq('@sorted'\n);}

    OUTPUT:

    '1,cat', '001,elk', '2,dog', '11,eel', '13,mink', '22,mouse'
    Bill
Re: How can I sort my array numerically on part of the string?
by GrandFather (Saint) on Dec 01, 2020 at 20:38 UTC

    The "how" has been sorted by others, but no explicit mention of "why". Perl's sort comes in a number of different forms. The short form of sort is essentially the same as sort {$a cpm $b} .... cmp (see perlop for cmp and <=>) compares strings so things that look like numbers are sorted like strings. To sort numerically you need the numeric comparison operator <=>. Sort then looks like sort {$a <=> $b} ....

    Optimising for fewest key strokes only makes sense transmitting to Pluto or beyond
Re: How can I sort my array numerically on part of the string?
by alexander_lunev (Pilgrim) on Dec 01, 2020 at 19:57 UTC

    While you can sort it automagically (as jdporter already says) with just sort { $a <=> $b } @list, you also can do it in two lines (but still they're simpler than those regexps). First of all, prepare your data so you can use simpler tools to process it. For each list member you have two fields of data in one string, and for me it's a hash that looked at me from that @list.

    my @list = ( '1,cat', '2,dog', '22,mouse', '11,eel', '001,elk', '13,mi +nk'); my %unsorted = map { split /,/ } @list ; my @sorted = map { $_.','.$unsorted{$_} } sort { $a <=> $b } keys %uns +orted;

    But maybe we're not so lucky and our data will not be prepended with right numerical indexes. What to do? Just reverse the hash pair!

    my @list = ( 'cat,1', 'dog,2', 'mouse,22', 'eel,11', 'elk,001', 'mink, +13'); my %unsorted = map { reverse split /,/ } @list ; my @sorted = map { $unsorted{$_}.','.$_ } sort { $a <=> $b } keys %uns +orted;

    Now, what if we're totally unlucky and our numerical index not only sit in the end of string, but sometimes equals to another index? We will lose some data in a hash, if we just split index from string and make pairs from them! Could the code still be two lines and still solve the problem and remain readable and understandable?

    my @list = ( 'cat,1', 'dog,1', 'mouse,22', 'eel,22', 'elk,001', 'mink, +13'); # as pointed by choroba, this can be put simpler # my %unsorted = map { $_ => [ reverse split /,/ ]->[0] } @list ; my %unsorted = map { $_ => [ split /,/ ]->[-1] } @list ; my @sorted = sort { $unsorted{$a} <=> $unsorted{$b} } keys %unsorted;

    TIMTOWTDI!

    p.s.: Updated last code as choroba pointed.
      Note that
      [ reverse split /,/ ]->[0]

      can be replaced by shorter

      (split /,/)[-1]

      which is also faster.

      map{substr$_->[0],$_->[1]||0,1}[\*||{},3],[[]],[ref qr-1,-,-1],[{}],[sub{}^*ARGV,3]
        Of course! Thank you!
      TY very helpful I'll try these.

      I'd hoped for an in-situ variety of $a spaceship $b ... But maybe no such luck!

Re: How can I sort my array numerically on part of the string? (alias)
by LanX (Cardinal) on Dec 02, 2020 at 13:27 UTC
    > which has the added detriment of dropping off my animals...

    maybe it has been mentioned before, but I can't seem to find it.

    $a and $b are aliases of @list elements so $a=~s/,.+// will alter the original list.

    That's why tybalt89 added an /r modifier in his solution

    Actually I can't think of a useful application of aliasing in sort because elements are accessed multiple times in unpredictable ways.

    DB<93> $x=0; @list=a..d DB<94> x sort { $x++; ($a.=$x) cmp ($b.=$x) } @list 0 'a13' 1 'b14' 2 'c234' 3 'd2' DB<95> x @list 0 'a13' 1 'b14' 2 'c234' 3 'd2' DB<96>
    any useful application???

    Cheers Rolf
    (addicted to the Perl Programming Language :)
    Wikisyntax for the Monastery

Re: How can I sort my array numerically on part of the string?
by johngg (Canon) on Dec 02, 2020 at 00:17 UTC

    A GRT solution, in the interests of TIMTOWTDI.

    johngg@abouriou:~/perl/Monks$ perl -Mstrict -Mwarnings -E ' my @list = do { no warnings qw{ qw }; qw{ 1,cat 2,dog, 22,mouse frog 11,eel 001,elk horse 002,bear 13,min +k }; }; say for map { unpack q{x54A50} } sort map { pack q{NA50A50}, ( m{^(\d+),(\S+)} ? ( $1, $2 ) : ( 0, q{} ) +), $_ } @list;' frog horse 1,cat 001,elk 002,bear 2,dog, 11,eel 13,mink 22,mouse

    I hope this is of interest.

    Cheers,

    JohnGG

Re: How can I sort my array numerically on part of the string?
by misterperl (Scribe) on Dec 01, 2020 at 21:22 UTC
    I don't like this as much as your hash approach which has appeal, but this seems to work:

    my @sorted = sort byPrefix @s; # sort the keys numerically sub byPrefix { # make copies so originals are preserved my ( $x, $y ) = ( $a, $b ); # get prefixes $x =~ s/:.+//; $y =~ s/:.+//; # RV $_=0; $x < $y && $_--; $x > $y && $_++; $_; }
      $_=0;

      Shouldn't this be
          local $_ = 0;
      to avoid global variable side effects (see local)?

      $_=0; $x < $y && $_--; $x > $y && $_++; $_;

      And how does this code differ in effect from
          $x <=> $y;
      (see <=> in Equality Operators in perlop)?

      (And a small point, but , (comma) is used as the delimiter in the OPed example data instead of : (colon), which you use in the s/:.+// extraction substitutions.)


      Give a man a fish:  <%-{-{-{-<

Re: How can I sort my array numerically on part of the string?
by perlfan (Vicar) on Dec 03, 2020 at 18:00 UTC
    You're one line away from the standard hash sorting idioms:
    use strict; use warnings; use Data::Dumper (); my @list = ( '1,cat', '2,dog', '22,mouse', '11,eel', '001,elk', '13,mi +nk'); my %list = map { split /,/,$_ } @list; #<-- dis print Data::Dumper::Dumper(\%list);

Log In?
Username:
Password:

What's my password?
Create A New User
Node Status?
node history
Node Type: perlquestion [id://11124461]
Approved by marto
Front-paged by Corion
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others romping around the Monastery: (5)
As of 2021-01-26 19:25 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?
    Notices?