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.
Re: How can I sort my array numerically on part of the string?
by jdporter (Chancellor) on Dec 01, 2020 at 18:09 UTC
|
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.
| [reply] [Watch: Dir/Any] [d/l] |
|
| [reply] [Watch: Dir/Any] [d/l] [select] |
|
now I get a different error "no" not allowed in expression
| [reply] [Watch: Dir/Any] |
|
|
|
I get 1,cat is not numeric. HUH?
| [reply] [Watch: Dir/Any] |
Re: How can I sort my array numerically on part of the string? (updated)
by haukex (Archbishop) on Dec 01, 2020 at 17:10 UTC
|
@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] }. | [reply] [Watch: Dir/Any] [d/l] [select] |
|
>
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 ?
| [reply] [Watch: Dir/Any] [d/l] [select] |
|
#!/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 :)
| [reply] [Watch: Dir/Any] [d/l] |
|
|
|
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?
| [reply] [Watch: Dir/Any] |
|
|
Re: How can I sort my array numerically on part of the string?
by tybalt89 (Monsignor) on Dec 01, 2020 at 21:15 UTC
|
#!/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
| [reply] [Watch: Dir/Any] [d/l] [select] |
Re: How can I sort my array numerically on part of the string?
by BillKSmith (Monsignor) on Dec 01, 2020 at 21:37 UTC
|
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'
| [reply] [Watch: Dir/Any] [d/l] |
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
| [reply] [Watch: Dir/Any] [d/l] [select] |
Re: How can I sort my array numerically on part of the string? (alias)
by LanX (Saint) 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???
| [reply] [Watch: Dir/Any] [d/l] [select] |
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. | [reply] [Watch: Dir/Any] [d/l] [select] |
|
[ 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]
| [reply] [Watch: Dir/Any] [d/l] [select] |
|
| [reply] [Watch: Dir/Any] |
|
| [reply] [Watch: Dir/Any] |
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.
| [reply] [Watch: Dir/Any] [d/l] |
Re: How can I sort my array numerically on part of the string?
by misterperl (Pilgrim) 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 && $_++;
$_;
}
| [reply] [Watch: Dir/Any] [d/l] |
|
$_=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: <%-{-{-{-<
| [reply] [Watch: Dir/Any] [d/l] [select] |
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);
| [reply] [Watch: Dir/Any] [d/l] |
|
|