Mark Lyons
Mark Lyons

Reputation: 1422

Sorting a two-dimensional array in Perl based on value of one item, with exceptions

Currently, I am working with a two dimensional array with information on soccer goals scored. Each item in the first array is an array with different information regarding the goal. The second item in this array is the minute scored (1-90) because I want them in sequential order so I can determine what they made the scoreline. I'm using that 'minutes' value to sort with this:

@allinfogoals = sort { $a->[1] <=> $b->[1] } @allinfogoals;

This works great, until I run into goals that were scored in extra time. The minutes for these are displayed like "90+2" or "45+3". Now, I can just add them together, but that could make the order incorrect. In this case, a goal scored right before half time could be stored as having been scored AFTER a goal that was scored shortly after the beginning of the second half.

So, I'm finding the minutes that have this 90+x format and splitting them at the '+'. I'm storing the first value where I regularly store minutes in the array, but I added another at the end of the array (12th item) and I'm putting that second part (mins into extra time) there. That is 0 when it is a regular goal.

How can I modify the sorting above to compensate this and have it maintain the proper order?

Upvotes: 4

Views: 10580

Answers (3)

TLP
TLP

Reputation: 67890

Here is a "brute force" solution, in that there's not much finesse in it, but does the job. It does not work with a two-dimensional data structure unless adapted, but on the other hand, I don't know what your data structure looks like.

use strict;
use warnings;
use feature 'say';

my @data = qw(22 45+3 45 46 90 90+3);

my @sorted = map $_->[2],               # turn back to org string
        sort {
            $a->[0] <=> $b->[0] ||      # default sort by period number
            $a->[1] <=> $b->[1]         # or by minute
        } map mysort($_), @data;        # map all minutes to 3-element array

say for @sorted;

sub mysort {
    my $time = shift;
    if ($time =~ /45\+(\d+)/) {
        return [1, 45+$1, $time];
    } elsif ($time =~ /90\+(\d+)/) {
        return [2, 90+$1, $time];
    } else {
        my $period = ($time <= 45 ? 1 : 2);
        return [$period, $time, $time]
    }
}

This uses a Schwartzian transform to turn each minute entry into a three element array, consisting of period number, minute within that period and the original string. The output of this script is:

22
45
45+3
46
90
90+3

Upvotes: 2

asjo
asjo

Reputation: 3194

It sounds like you want to sort on one key first, and if that key is the same, then you want to sort on a second key.

E.g. you want 45+2 to be sorted between 45 and 46.

You can do this by simply using:

@ls = sort { $a->[0] <=> $b->[0] || $a->[1] <=> $b->[1] } @ls

Only if the first key is the same, the second key is consulted.

Here is a complete example:

my @allinfogoals=(
                  [ 46, 0 ],
                  [ 45, 2 ],
                  [ 45, 0 ],
                  [ 33, 0 ],
                  [ 91, 0 ],
                  [ 90, 2 ],
                 );

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

use Data::Dump; dd \@allinfogoals;

And the output is:

[[33, 0], [45, 0], [45, 2], [46, 0], [90, 2], [91, 0]]

Upvotes: 5

asjo
asjo

Reputation: 3194

In this particular case, as you have described it, you could also (as someone answered but deleted again, it seems) convert your overtime minutes to tenths and order numerically:

my @allinfogoals=qw(46 45+2 45 33 91 90+2);

@allinfogoals=map { s/[.]/+/; $_ } sort { $a <=> $b } map { s/[+]/./; $_ } @allinfogoals;

use Data::Dump; dd \@allinfogoals;

... and then convert back. Output:

[33, 45, "45+2", 46, "90+2", 91]

Upvotes: 0

Related Questions