mortimerfreeze
mortimerfreeze

Reputation: 95

How to sort array values nested inside a hash with different keys?

I have an html table showing outage start and end times with different types of outages. Currently, I am sorting the outages in order of outage type, but I would like to be able to sort them by earliest to latest start times. The times in each start and end will already be in order, but I am trying to get them in order, regardless of type. I know for sorting by value, you usually use some sort of value compare like this "sort { $h{$a} <=> $h{$b} } keys(%h);"

Currently they sort like:
1 | phone | 00:00:00 | 04:08:03
2 | phone | 14:26:03 | 18:00:00
3 | television | 12:34:19 | 12:34:25

But it should be like:
1 | phone | 00:00:00 | 04:08:03
2 | television | 12:34:19 | 12:34:25
3 | phone | 14:26:03 | 18:00:00

This is my code.

my %outages;

my @outage_times = qw(start end);

my %outage_reasons = (
  'tv' => 'television',
  'p' => 'phone'
);

foreach my $outage_reason (values %outage_reasons) {
  foreach my $outage (@outage_times) {
    $outages{$outage_reason}{$outage} = [];
  }
}

$outages{television}{start} = ['00:00:00', '14:26:03'];
$outages{television}{end} = ['04:08:03', '18:00:00'];
$outages{phone}{start} = ['12:32:02'];
$outages{phone}{end} = ['12:38:09'];

my $outage_number = 1;

foreach my $outage (sort keys %outages){
  for my $i (0 .. scalar (@{$outages{$outage}{start}})-1) {
    my $outage_start_time = $outages{$outage}{start}[$i];
    my $outage_end_time = $outages{$outage}{end}[$i];
    my $row_html = "<tr><td>$outage_number</td><td>$outage</td>";
        $row_html .= "<td>$outage_start_time</td>";
        $row_html .= "<td>$outage_end_time</td></tr>";
    $outage_number += 1;
  }
}

Upvotes: 1

Views: 111

Answers (2)

Dave Cross
Dave Cross

Reputation: 69244

I think this is a situation where you are making life difficult for yourself because your data structure is unnecessarily complicated. I don't know where your data is coming from, but it would be far easier if you could get an array of hashes like this:

my @outages = ({
  type  => 'phone',
  start => '00:00:00',
  end   => '04:04:03',
}, {
  type  => 'phone',
  start => '14:26:03',
  end   => '18:00;00',
}, {
  type  => 'television',
  start => '12:34:19',
  end   => '12:34:25',
});

The code to sort and print these then becomes almost trivial.

my $number = 1;
for (sort { $a->{start} cmp $b->{start} } @outages) {
  my $row_html = '<tr>'
               . "<td>$number</td>"
               . "<td>$_->{type}</td>"
               . "<td>$_->{start}</td>"
               . "<td>$_->{end}</td>"
               . "</tr>\n";
  $number++;
  print $row_html;
}

It's worth noting that this only works because your timestamps can be treated as strings which are easily sorted. If the timestamps were more complicated and included dates then you're probably going to want to convert them to sortable data using something like Time::Piece or DateTime.

I'd also mention that one day you'll discover that including raw HTML tags in your Perl code is a recipe for disaster. Far better to use a templating system like the Template Toolkit.

Upvotes: 3

Stefan Becker
Stefan Becker

Reputation: 5962

Don't store the time stamp as strings but as seconds-since-epoch. Then you can use normal numeric compare

foreach my $outage (sort { $a->{start} <=> $b->{start} values %outages) {

EDIT: SOP for time stamp processing in any language/program, unless you have some really out-of-this-world requirements:

  1. parse the input format to convert time stamps to "X since epoch"
    • always convert to UTC, ie. determine time zone if it is not given
    • determine resolution (seconds, milliseconds, microseconds) provided by input
    • Date::Manip can be your friend here
  2. process time stamps in your algorithm as numerical values
    • compare: a < b -> a happens before b
    • differences: a - b at your given resolution
  3. convert timestamps to desired output format
    • if you have control of the output format, always opt for a precise format, e.g. use the UTC timestamp directly or ISO-8601 format
    • again Date::Manip::Date printf() method can be your friend here

Upvotes: 1

Related Questions