Sorting an array by two functions

5 views
Skip to first unread message

Henry Law

unread,
Nov 27, 2021, 11:34:44 AM11/27/21
to
I have an array of arrayrefs with this structure:

@array = ( [ '20100403', 'text', 'account code' ],
[ '19990503', 'text', 'other account code' ],
... etc )

I want to sort by account code and then by date. I read in various
places that the following will do the trick:

@array = sort { $a->[2] cmp $b->[2] || $a->[0] <=> $b->[0] } @array;

But I'm not getting the sort order I want. I know that the "sort"
function requires a three-valued return from the block, -1, 0 or +1; but
the "||" operator won't do that; it returns TRUE or FALSE.

Is that understanding correct? Is that the reason that I'm not getting
the order I want?

--
Henry Law n e w s @ l a w s h o u s e . o r g
Manchester, England

Keith Thompson

unread,
Nov 27, 2021, 4:45:49 PM11/27/21
to
Henry Law <ne...@lawshouse.org> writes:
> I have an array of arrayrefs with this structure:
>
> @array = ( [ '20100403', 'text', 'account code' ],
> [ '19990503', 'text', 'other account code' ],
> ... etc )
>
> I want to sort by account code and then by date.

Do you want elements with the same account code and different dates
grouped together, or vice versa?

> I read in various
> places that the following will do the trick:
>
> @array = sort { $a->[2] cmp $b->[2] || $a->[0] <=> $b->[0] } @array;
>
> But I'm not getting the sort order I want. I know that the "sort"
> function requires a three-valued return from the block, -1, 0 or +1;

The function has to return a value that's less than, equal to, or
greater than zero. It doesn't have to be -1, 0, or +1 (though of course
it can be, and "<=>" and "cmp" do so unless you use "<=>" with NaNs).
See "perldoc perlop".

> but
> the "||" operator won't do that; it returns TRUE or FALSE.
>
> Is that understanding correct? Is that the reason that I'm not getting
> the order I want?

No. Quoting "perldoc perlop":

The "||", "//" and "&&" operators return the last value evaluated
(unlike C's "||" and "&&", which return 0 or 1).

(And note that Perl doesn't have TRUE and FALSE constants.)

I don't know what the problem is. I'd probably use "or" rather than
"||", or add parentheses, just for greater clarity, but "cmp" and "<=>"
bind more tightly than either "||" or "or", so that's not the issue.

If you don't get an answer from someone else, try showing us some sample
data, the order you expect, and the order you're getting.

--
Keith Thompson (The_Other_Keith) Keith.S.T...@gmail.com
Working, but not speaking, for Philips
void Void(void) { Void(); } /* The recursive call of the void */

Rainer Weikusat

unread,
Nov 29, 2021, 12:53:04 PM11/29/21
to
Henry Law <ne...@lawshouse.org> writes:
> I have an array of arrayrefs with this structure:
>
> @array = ( [ '20100403', 'text', 'account code' ],
> [ '19990503', 'text', 'other account code' ],
> ... etc )
>
> I want to sort by account code and then by date. I read in various
> places that the following will do the trick:
>
> @array = sort { $a->[2] cmp $b->[2] || $a->[0] <=> $b->[0] } @array;
>
> But I'm not getting the sort order I want. I know that the "sort"
> function requires a three-valued return from the block, -1, 0 or +1; but
> the "||" operator won't do that; it returns TRUE or FALSE.
>
> Is that understanding correct? Is that the reason that I'm not getting
> the order I want?

The code below works as expected:

-----
my @a;

for (reverse('a' .. 'z'), 'a' .. 'z') {
push(@a, [$_, int(rand(10))]);
}

sub pa
{
print($$_[0], "\t", $$_[1], "\n") for @_;
}

my @b = sort { $$a[0] cmp $$b[0] || $$a[1] <=> $$b[1] } @a;

pa(@b);

Henry Law

unread,
Nov 29, 2021, 5:50:02 PM11/29/21
to
On Mon, 29 Nov 2021 17:52:56 +0000, Rainer Weikusat wrote:

> The code below works as expected:

Indeed it does; and so does mine now that I've found the unrelated
mistake.

And I understand the error in my original reasoning. For anyone other
than me dim enough to need an explanation:

> my @b = sort { $$a[0] cmp $$b[0] || $$a[1] <=> $$b[1] } @a;

"||" operator returns the last value evaluated (see perlop); so if the
first comparison comes up zero and the second as, say, -1, the "||"
operation becomes 0 || -1; the result is -1 and the sort is satisfied.

I thought it would be restricted to 0 or 1, as in C; perlop clearly draws
attention to this distinction.

Andrzej Adam Filip

unread,
Nov 29, 2021, 7:15:31 PM11/29/21
to
Henry Law <ne...@lawshouse.org> wrote:
> I have an array of arrayrefs with this structure:
>
> @array = ( [ '20100403', 'text', 'account code' ],
> [ '19990503', 'text', 'other account code' ],
> ... etc )
>
> I want to sort by account code and then by date. I read in various
> places that the following will do the trick:
>
> @array = sort { $a->[2] cmp $b->[2] || $a->[0] <=> $b->[0] } @array;
>
> But I'm not getting the sort order I want. I know that the "sort"
> function requires a three-valued return from the block, -1, 0 or +1; but
> the "||" operator won't do that; it returns TRUE or FALSE.
>
> Is that understanding correct? Is that the reason that I'm not getting
> the order I want?

man perlop
The "||", "//" and "&&" operators return the last value evaluated
(unlike C's "||" and "&&", which return 0 or 1).

One-liner below produces "-1-1" :
perl -e 'print( -1 || 0, 0||-1)'

--
[Andrew] Andrzej A. Filip
Reply all
Reply to author
Forward
0 new messages