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

Hello Monks, I tried to reverse sort below array but couldn't manage it.

ID12-ABC-5.1 ID9-ABC-5.1 ID3-ABC-6.1 ABC-5.1 ID12-ABC-5.1.5 ID15-ABC-6.1 ABC-6.1 ID5-ABC-5.1 ID5-ABC-5.1.5 ABC-5.1.5
Desired output would be
ID15-ABC-6.1 ID3-ABC-6.1 ID12-ABC-5.1.5 ID5-ABC-5.1.5 ID12-ABC-5.1 ID9-ABC-5.1 ID5-ABC-5.1 ABC-6.1 ABC-5.1.5 ABC-5.1
Any help much appreciated.

Replies are listed 'Best First'.
Re: Sorting array
by davido (Cardinal) on Sep 22, 2011 at 22:03 UTC

    That's a potentially nasty desired sort order. It seems like it could consist of the following components:

    • Sort ASCIIbetically the first field, which may be ID, or may be empty.
    • Sort numerically the digits of the first part of what looks like a v-string.
    • Sort numerically the optional (maybe) digits of the second part of what looks like a v-string.
    • Sort numerically the optional (obviously) digits of the third part of what looks like a v-string.
    • Sort numerically the digits that follow the optional "ID" characters. (This part of the construct is optional).
    • Sort ASCIIbetically the field that looks like ABC, which seems never to be optional, but is preceded by a field that is optional.

    That's a mess. But once you break the input into its individual components the sorting becomes pretty simple.

    my @data = qw/ ID12-ABC-5.1 ID9-ABC-5.1 ID3-ABC-6.1 ABC-5.1 ID12-ABC-5.1.5 ID15-ABC-6.1 ABC-6.1 ID5-ABC-5.1 ID5-ABC-5.1.5 ABC-5.1.5 /; my @sorted = do{ no warnings 'uninitialized'; map { $_->[-1] } sort{ $b->[0] cmp $a->[0] or # 'ID' component. $b->[3] <=> $a->[3] or # v-string part1. $b->[4] <=> $a->[4] or # v-string part2. $b->[5] <=> $a->[5] or # v-string part3. $b->[1] <=> $a->[1] or # Numeric following 'ID'. $b->[2] cmp $a->[2] # 'ABC' component. } map { [ m/^ (?:(\p{Alphabetic}+)(\d+)-)? # "ID", then digits. (\p{Alphabetic}+)- # "ABC" component. (\d+) # v-string part1. (?:\.(\d+))? # v-string part2. (?:\.?(\d+))? # v-string part3. $/x, $_ ] } @data; }; say for @sorted;

    And the output...

    ID15-ABC-6.1 ID3-ABC-6.1 ID12-ABC-5.1.5 ID5-ABC-5.1.5 ID12-ABC-5.1 ID9-ABC-5.1 ID5-ABC-5.1 ABC-6.1 ABC-5.1.5 ABC-5.1

    ...which I think matches your specification.

    To me this is a case where the Schwartzian Transform actually makes the code easier to follow. Imagine trying to do all the matching inline, within the sort routine. The biggest problem was trying to figure out how to format the code. lol


    Dave

      I was tired of trying so I couldn't put much explanation. Sorry for this. And Dave explained what I've been trying to do. Thanks for explanation and for the advice.
Re: Sorting array
by ww (Archbishop) on Sep 22, 2011 at 21:37 UTC
Re: Sorting array
by Kc12349 (Monk) on Sep 22, 2011 at 21:47 UTC

    This will produce your desired output, but given the meager description you have given, I can't say really if it is a solution to your issue. Please give further information and let us know if this helps.

    chomp(my @unsorted = (<DATA>)); my @sorted = sort { @{[(split('-',$b))]}[0] =~ s/\d+// cmp @{[(split('-',$a))]}[0] =~ s +/\d+// || (split('-',$b))[-1] cmp (split('-',$a))[-1] } @unsorted; say for @sorted; __DATA__ ID12-ABC-5.1 ID9-ABC-5.1 ID3-ABC-6.1 ABC-5.1 ID12-ABC-5.1.5 ID15-ABC-6.1 ABC-6.1 ID5-ABC-5.1 ID5-ABC-5.1.5 ABC-5.1.5

      I have no idea if thats how it should sort , but this is how you would cache your solution (also known as schwartzian transform) to save on redundant split and s// (save CPU by using more memory)

      #!/usr/bin/perl -- use strict; use warnings; use Data::Dumper; my @unsorted = split /[\r\n]+/, <<'__THE_DATA__'; ID12-ABC-5.1 ID9-ABC-5.1 ID3-ABC-6.1 ABC-5.1 ID12-ABC-5.1.5 ID15-ABC-6.1 ABC-6.1 ID5-ABC-5.1 ID5-ABC-5.1.5 ABC-5.1.5 __THE_DATA__ print Dumper( \@unsorted ); my @sorted = sort { @{[(split('-',$b))]}[0] =~ s/\d+// cmp @{[(split('-',$a))]}[0] =~ s +/\d+// || (split('-',$b))[-1] cmp (split('-',$a))[-1] } @unsorted; print Dumper( \@sorted ); @sorted = map { $_->[0] } sort { $b->[1] cmp $a->[1] || $b->[2] cmp $a->[2] } map { my(@f) = split /-/, $_; [ $_, scalar( $f[0] =~ s/\d+// ), $f[-1], ]; } @unsorted; print Dumper( \@sorted ); __END__ $VAR1 = [ 'ID12-ABC-5.1', 'ID9-ABC-5.1', 'ID3-ABC-6.1', 'ABC-5.1', 'ID12-ABC-5.1.5', 'ID15-ABC-6.1', 'ABC-6.1', 'ID5-ABC-5.1', 'ID5-ABC-5.1.5', 'ABC-5.1.5' ]; $VAR1 = [ 'ID3-ABC-6.1', 'ID15-ABC-6.1', 'ID12-ABC-5.1.5', 'ID5-ABC-5.1.5', 'ID12-ABC-5.1', 'ID9-ABC-5.1', 'ID5-ABC-5.1', 'ABC-6.1', 'ABC-5.1.5', 'ABC-5.1' ]; $VAR1 = [ 'ID3-ABC-6.1', 'ID15-ABC-6.1', 'ID12-ABC-5.1.5', 'ID5-ABC-5.1.5', 'ID12-ABC-5.1', 'ID9-ABC-5.1', 'ID5-ABC-5.1', 'ABC-6.1', 'ABC-5.1.5', 'ABC-5.1' ];

        Thanks. That is interesting. I'll have to digest it a bit.

        On closer inspection, this doesn't seem to be quite a complete solution. The below section of the approach I think is expected to return the replaced value, in this case 'ID'.

        It instead returns a 1 if a replacement is made or an empty string if it is not. It happens to produce the correct sort in this case because of the narrow data set used.

        my $string = 'ID13'; say scalar( $string =~ s/\d+// );

        Does anyone else know of a way to return the correct match in place of this line? The below is a very poor solution I expect.

        my $string = 'ID13'; say join('', grep( /[A-Za-z]/, split('',$string) ) );

        Accidental node duplication. Please ignore.

Re: Sorting array
by onelesd (Pilgrim) on Sep 22, 2011 at 21:32 UTC
    Let's see what you tried - show us some code.