PWC181 - Hot Day

TL;DR

On with TASK #2 from The Weekly Challenge #181. Enjoy!

The challenge

You are given file with daily temperature record in random order.

Write a script to find out days hotter than previous day.

Example

Input File: (temperature.txt)

2022-08-01, 20
2022-08-09, 10
2022-08-03, 19
2022-08-06, 24
2022-08-05, 22
2022-08-10, 28
2022-08-07, 20
2022-08-04, 18
2022-08-08, 21
2022-08-02, 25

Output:
2022-08-02
2022-08-05
2022-08-06
2022-08-08
2022-08-10

The questions

Can I be lazy? I don’t want to read the file!

There’s a little ambiguity in the challenge, in that we don’t know what to do with the first day. Should we consider it hotter than the previous one? Colder? Same? Decide based on the average of the few days ahead? There are endless possibilities.

The solution

This is one of those challenges that make it easy to highlight some of the worst critiques to Perl. That is: it’s very easy to produce write-only code (that is, code that is easy two write, but then hard to read).

Here’s my solution in Perl:

sub hot_day ($input) {
   my $p; # previous value
   map { $_->[0] }
      grep { ((my $test, $p) = (($p // $_->[1]) < $_->[1], $_->[1]))[0] }
      sort { $a->[0] cmp $b->[0] }
      map { [ split m{,\s*}mxs ] }
      split m{\n}mxs, $input;
}

We have to start from… the end:

  • split on lines
  • split each line into date and temperature, as an array ref
  • sort by date (first element in the array ref)
  • apply the required filtering, with the help of an external “memory” variable $p which is usually frowned upon because grep is supposed to (generally) be pure
  • keep only the first item in the array ref (i.e. the date).

I feel ashamed, so the solution proposal will have comments:

#!/usr/bin/env perl
use v5.24;
use warnings;
use experimental 'signatures';
no warnings 'experimental::signatures';

say for hot_day(temperature_txt());

sub hot_day ($input) {
   my $p; # previous value
   map { $_->[0] } # keep the date only

      # filter according to the input requirement
      grep { ((my $test, $p) = (($p // $_->[1]) < $_->[1], $_->[1]))[0] }
      sort { $a->[0] cmp $b->[0] } # sort by date
      map { [ split m{,\s*}mxs ] } # split on date, temperature
      split m{\n}mxs, $input;      # split into lines
}

sub temperature_txt {
   return <<'END' =~ s{^\s+}{}rgmxs;
      2022-08-01, 20
      2022-08-09, 10
      2022-08-03, 19
      2022-08-06, 24
      2022-08-05, 22
      2022-08-10, 28
      2022-08-07, 20
      2022-08-04, 18
      2022-08-08, 21
      2022-08-02, 25
END
}

The Raku counterpart is pretty much a straight translation, with a few differences:

  • it seems that we have to get rid of empty lines explicitly, or we will get a few warnings;
  • the sequence of operations as written also follow the way data flows and do not go “backwards” as in the Perl case. This helps readability, although I think that comments are still needed anyway.
#!/usr/bin/env raku
use v6;
sub MAIN { .put for hot-day(temperature-txt()) }

sub hot-day ($input) {
   my $p;  # previous value
   $input.split(/\n/)  # split into lines
      .grep(/\,/)      # keep valid lines only
      .map({ [ .split(/\,\s*/) ] }) # split on [date, temperature]
      .sort({ $^a[0] cmp $^b[0] })  # sort by date

      # filter according to the input requirement
      .grep({ ((my $t, $p) = (($p // $_[1]) < $_[1], $_[1]))[0] })

      .map({ $_[0] } ); # keep the date only
}

sub temperature-txt {
   return q:to/END/;
      2022-08-01, 20
      2022-08-09, 10
      2022-08-03, 19
      2022-08-06, 24
      2022-08-05, 22
      2022-08-10, 28
      2022-08-07, 20
      2022-08-04, 18
      2022-08-08, 21
      2022-08-02, 25
      END
}

So here we are, we have the hot days!

Stay safe and fresh!


Comments? Octodon, , GitHub, Reddit, or drop me a line!