17

I have an array of arrays that I want to sort. Each element of array A is an array with 3 elements. Array A looks like:

my @A = ([2,3,1], [1,2,3], [1,0,2], [3,1,2], [2,2,4]);

I want to sort A in ascending order. When comparing 2 elements, the first number is used. If there is a tie, the second number is used, and then the third number.

Here is my code. I use a function 'cmpfunc' to compare 2 elements.

sub cmpfunc {
    return ($a->[0] <=> $b->[0]) or 
           ($a->[1] <=> $b->[1]) or
           ($a->[2] <=> $b->[2]);
}
my @B = sort cmpfunc @A;
print "Result:\n";
for my $element (@B) {
    print join(",", @{$element}) . "\n";
}

Result:

1,2,3
1,0,2
2,3,1
2,2,4
3,1,2

The result is somewhat sorted, but not correct. What I expect is:

1,0,2
1,2,3
2,2,4
2,3,1
3,1,2

Is there any error in my comparison function? The strange thing is, when I put the comparison code in block, the result is correctly sorted.

my @C = sort { ($a->[0] <=> $b->[0]) or 
               ($a->[1] <=> $b->[1]) or
               ($a->[2] <=> $b->[2]) } @A;
Daniel Fischer
  • 181,706
  • 17
  • 308
  • 431
jftsai
  • 301
  • 2
  • 9

5 Answers5

23

You are executing

return ($a->[0] <=> $b->[0])

which returns before it gets to any of the "or" clauses.

Either remove the "return" keyword, or add parenthesis around the entire arg list for return:

sub cmpfunc {
    return(($a->[0] <=> $b->[0]) or
           ($a->[1] <=> $b->[1]) or
           ($a->[2] <=> $b->[2]));
}
Cœur
  • 37,241
  • 25
  • 195
  • 267
tadmc
  • 3,714
  • 16
  • 14
11

The reason you observe this "wrong" behavior is the priority of or operator, the lowest possible. In this situation it means that

return ($a->[0] <=> $b->[0]) or 
       ($a->[1] <=> $b->[1]) or
       ($a->[2] <=> $b->[2]);

is interpreted as OR-ing

return ($a->[0] <=> $b->[0])

and the rest of the line -- nonsense in this case, as return never returns. :)

So you should use C's OR:

return ($a->[0] <=> $b->[0]) || 
       ($a->[1] <=> $b->[1]) ||
       ($a->[2] <=> $b->[2]);
pwes
  • 2,040
  • 21
  • 30
7

Needs more parentheses:

sub cmpfunc {
    return (($a->[0] <=> $b->[0]) or
            ($a->[1] <=> $b->[1]) or
            ($a->[2] <=> $b->[2]));
}
Daniel Fischer
  • 181,706
  • 17
  • 308
  • 431
5
    sub cmpfunc {
    return ($a->[0] <=> $b->[0]) or 
           ($a->[1] <=> $b->[1]) or
           ($a->[2] <=> $b->[2]);
}

you can delete 'return' here.

    sub cmpfunc {
     ($a->[0] <=> $b->[0]) or 
     ($a->[1] <=> $b->[1]) or
     ($a->[2] <=> $b->[2]);
}
timest
  • 188
  • 2
  • 9
4

An alternative solution to Daniel's:

sub cmpfunc {
    return ($a->[0] <=> $b->[0]) ||
           ($a->[1] <=> $b->[1]) ||
           ($a->[2] <=> $b->[2]);
}

The problem with or this case is that it has lower precedence than assignment, so your function only returns the result of ($a->[0] <=> $b->[0]), which is -1, 0 or 1 if the left hand side is numerically lower than, equal to or larger than the right hand side respectively. || has higher precedence, so the entire boolean expression is evaluated before returning. As mentioned, you can wrap the expression in parentheses if you prefer that to ||. I personally don't.

flesk
  • 7,439
  • 4
  • 24
  • 33
  • Actually, it returns only the first comparison, no matter what it returns. Try `sub a { return 0 or die "Ough" }`. – TLP Jan 13 '12 at 13:34