Beefy Boxes and Bandwidth Generously Provided by pair Networks
The stupid question is the question not asked
 
PerlMonks  

Numeric Sorting on Characters

by halecommarachel (Sexton)
on Aug 14, 2013 at 19:10 UTC ( #1049462=perlquestion: print w/ replies, xml ) Need Help??
halecommarachel has asked for the wisdom of the Perl Monks concerning the following question:

Hello Monks,
I am sorting a hash by value like so: foreach my $tc (sort {$passes->{$a}->{'cpu'} <=> $passes->{$b}->{'cpu'}} keys %{$passes}) {
Some of the values are "-", so I get this error:
Argument "-" isn't numeric in numeric comparison (<=>) at /home/fisusr/bin/rdi_report_wrapper.pl line 183
How can I avoid this error? Thanks, Rachel

Comment on Numeric Sorting on Characters
Select or Download Code
Re: Numeric Sorting on Characters
by choroba (Abbot) on Aug 14, 2013 at 19:18 UTC
    This is not an error, it is a warning. You can:
    1. Ignore it.
    2. Turn off this warning. Insert no warnings 'numeric'; into the sort code.
      sub by_cpu { no warnings 'numeric'; $passes->{$a}{cpu} <=> $passes->{$b}{cpu}; } for my $tc (sort by_cpu keys %$passes) { # ...
    3. Filter out the non-numeric values:
      my @dashed; my @sorted = sort { $passes->{$a}{cpu} <=> $passes->{$b}{cpu} } grep { '-' ne $passes->{$_}{cpu} or push @dashed, $_ and 0 } keys %$passes; for my $tc (@dashed, @sorted) { # ...

    Update: Ouch. no cannot go to the sort code. Changed option 2 and added option 3.

    لսႽ ᥲᥒ⚪⟊Ⴙᘓᖇ Ꮅᘓᖇ⎱ Ⴙᥲ𝇋ƙᘓᖇ

      Hi choroba,
      Can you explain what is happening when @sorted is generated? I'm not sure how you were able to grep after sort. I do understand that the grep checks if the value equals "-", otherwise is pushes it to @dashed, but why "and 0"?
      Thanks!

        When functions are chained, they are called from right to left. So, that's what happens: Keys are taken. For each key, if it is not a dash, it is left in the list that is later sorted. If it is a dash, it is pushed to @dashed and 0 is returned so grep removes it from the list to be sorted.
        لսႽ ᥲᥒ⚪⟊Ⴙᘓᖇ Ꮅᘓᖇ⎱ Ⴙᥲ𝇋ƙᘓᖇ
Re: Numeric Sorting on Characters
by McA (Curate) on Aug 14, 2013 at 19:19 UTC

    Probably the ternary operator to the rescue:

    sort { ($passes->{$a}->{'cpu'} ne '-' ? $passes->{$a}->{'cpu'} : 0) <=> ($passes->{$b}->{'cpu'} ne '-' ? $passes->{$b}->{'cpu'} : 0) } keys %{$passes}

    UPDATE: In addition to choroba's solution you're able to assign a different value in case of '-'.

    McA

      Another ternary approach that gathers  '-' elements to the lowest indices of the output array regardless of the numeric value of any other elements to be sorted, e.g., negative values.

      >perl -wMstrict -le "my @unsorted = (1, 10, '-', 2, -3, 20, '-', '-', 3, 0, 2, 11, 8, 7); ;; my @sorted = sort numeric_ascending_with_dash_leftmost @unsorted; print qq{@sorted}; ;; @sorted = sort { -numeric_ascending_with_dash_leftmost() } @unsorted; print qq{reversed: @sorted}; ;; print qq{still unsorted: @unsorted}; ;; sub numeric_ascending_with_dash_leftmost { return $a eq '-' ? -1 : $b eq '-' ? 1 : $a <=> $b ; } " - - - -3 0 1 2 2 3 7 8 10 11 20 reversed: 20 11 10 8 7 3 2 2 1 0 -3 - - - still unsorted: 1 10 - 2 -3 20 - - 3 0 2 11 8 7
Re: Numeric Sorting on Characters
by sundialsvc4 (Monsignor) on Aug 14, 2013 at 20:54 UTC

    Hmmm... the presence of a single “-” seems quite suspicious to me ... have you looked at the corresponding record in the input data, and if so, is it really just a dash?   Does this look like a possible bug in the program that produced the file?   (Hey, it happens ... a lot.)

    “Making the immediate bug go-away” sometimes leaves you in a situation that is much worse.   Why is that dash there?   Is it really there (or is your parsing algorithm insufficient?).   Is it really correct to code your program to detect and to accept it?   Maybe the proper answer to all 3 questions is yes.   But then again, the software might be trying to tell you something.

      If the cpu time is not available, I set the value to "-". It's just a way of having a placeholder than returns true if tested with an if statement.

        Still, it leads to the Zen question "How do you do a numeric sort of non-numeric data?".

Re: Numeric Sorting on Characters
by Laurent_R (Parson) on Aug 14, 2013 at 20:57 UTC

    The problem is that this warning tells you something about your data not being what you expected. It could be important or absolutely a non-issue, you did not tell us anything about the functional context.

    Ignoring it altogether is not a very good idea IMHO, because the data resulting from the sort will probably still be plagued with these unexpected values.

    Filtering out the non-standard values with a grep before the sort is already better, if that fits your goals, i.e. if removing these values does not harm the rest of your process. Another possibility is to decide that these non standard values must always come before (or after) any other values, which means changing your sorting procedure or having a preliminary step before the sort. Another preprocessing strategy might be to replace temporarily the '-' by a very large or very small positive or negative value, or a null value, whatever might set these values asides without having the warnings.

    But you alone can decide what to do so long as we don't have any idea of what your are sorting.

Re: Numeric Sorting on Characters
by Kenosis (Priest) on Aug 14, 2013 at 21:16 UTC

    If you're using Perl v5.14+, you could use the r (for return the modified string) modifier in a substitution:

    use strict; use warnings; my %hash = map { $_ => 1 } qw/987-665-888 456-123-000 000-000-000 123- +456-789/; print "$_\n" for sort { $a =~ s/-//gr <=> $b =~ s/-//gr } keys %hash;

    Output:

    000-000-000 123-456-789 456-123-000 987-665-888

    Hope this helps!

      halecommarachel supplies no example data, so this point is a bit unclear, but I assume that the real data has elements that are a  '-' (dash) rather than that contain dashes. E.g.,
          qw(1 10  -  2 -3 20  - -  3 0 2 11 8 7);
      rather than
          qw/987-665-888 456-123-000 000-000-000 123-456-789/

      Caveat Programmor.

        /me nods ...

        In which case, if careful research into the problem confirms that,:

        • Yes, that dash is legitimate data, and
        • there is no bug either in this program or in the upstream source, and
        • the downstream program(s) will know what to do, and therefore,
        • here is how we wish to sort it,

        ... (heh) ...

        Then, as you know, the Perl sort verb relies upon a subroutine to do the sort-comparison, “and this is why.”   Although it is most frequently a “one-liner,” it can be a much more elaborate thing which receives two parameters, $a and $b.   (And, if you do several sorts the same way, it can be a separate, not-inline, subroutine that all of them use.)   All of which is well and good ... but I think that I’m in the majority thinking that a bug .. somewhere upstream .. has been uncovered here.

Re: Numeric Sorting on Characters
by johngg (Abbot) on Aug 14, 2013 at 21:31 UTC

    Another way is to use a Schwartzian Transform passing the key, an indicator whether the cpu is "-" (1 - TRUE or 0 - FALSE) and the cpu value in an array ref. Sorting can then be done on the indicator first then on cpu value but the second sort term is a ternary so that comparing two items with a "-" cpu value immediately returns zero.

    $ perl -Mstrict -Mwarnings -E ' my $passes = { t1 => { cpu => 73, xx => 3 }, t2 => { cpu => q{-}, xx => 7 }, t3 => { cpu => 17, xx => 0 }, t4 => { cpu => 49, xx => 6 }, t5 => { cpu => q{-}, xx => 4 }, t6 => { cpu => 11, xx => 5 }, }; say for map { $_->[ 0 ] } sort { $a->[ 1 ] <=> $b->[ 1 ] || ( $a->[ 1 ] ? 0 : $a->[ 2 ] <=> $b->[ 2 ] ) } map { [ $_, $passes->{ $_ }->{ cpu } eq q{-}, $passes->{ $_ }->{ cpu }, ] } keys %$passes;' t6 t3 t4 t1 t2 t5 $

    I hope this is of interest.

    Update: Ignore this, it doesn't seem to be sorting correctly :-(

    Update 2: I was bitten on the bum by a silly precedence problem that should have occurred to me sooner. Replacing

    sort { $a->[ 1 ] <=> $b->[ 1 ] || $a->[ 1 ] ? 0 : $a->[ 2 ] <=> $b->[ 2 ] }

    with

    sort { $a->[ 1 ] <=> $b->[ 1 ] || ( $a->[ 1 ] ? 0 : $a->[ 2 ] <=> $b->[ 2 ] ) }

    solved the problem.

    Cheers,

    JohnGG

Log In?
Username:
Password:

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

How do I use this? | Other CB clients
Other Users?
Others rifling through the Monastery: (12)
As of 2014-09-19 08:52 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    How do you remember the number of days in each month?











    Results (133 votes), past polls