You've already got good answers, but I'm taking the opportunity to expose you to some other standard Raku tools and idioms that seemed natural to me for your problem.
For both my solutions:
My equivalents of your %totals variable store keys in a structured data form rather than just as string keys. The supposed rationale is to simplify the sort and presentation. (But really it's to show you another way. It would of course be trivial to ensure the month and day numbers are concatenated as two two digit numbers to ensure correct sorting.) I use two different key types to show variations on this theme.
I deal with conversion to/from month names by constructing hashes mapping names to numbers. I declare one with the .pairs or .antipairs method, and then apply the reverse to convert in the other direction. I do this one way in the first solution and the other in the second. And I set the number for jan to 0 in one solution and 1 in the other.
Short and sweet, lean on Pairs
When declaring a %foo variable, if you don't specify its key type, it defaults to Str. But in this code, the key of each Pair in %totals is itself a Pair:
my %totals{Pair}; # Keys of the `Pair`s in `%totals` are themselves `Pair`s
my %months = <jan feb mar apr may jun jul aug sep oct nov dec> .pairs; # 0 => jan
for lines.grep(/redis/ & /Partial/)».words {
++%totals{ %months.antipairs.hash{ lc .[0] }.Int => .[1].Int }
}
for %totals .sort {
printf "%3s %2d : %-d\n", %months{.key.key}, .key.value, .value
}
If no sort closure(s) are specified, Raku's sort routine, when applied to a hash, sorts its entries by comparing their keys using cmp. Furthermore, for an ordinary hash, comparing two keys means comparing two strings.
That would work fine for your situation if these strings were each date's month and day formatted as two digits each and then concatenated. Alternatively, splitting and schwartzian works fine too. Raku's really good at that stuff but I preferred to go a different way with this answer, so that the default sort did the right thing.
For this first solution, I picked Pairs as the key type. When cmp compares Pairs, it sorts first by key and then by value within that. Both key and value were coerced to Ints, thus the above code correctly sorts by month, then days within that.
More structure, use Dates
This version adds structure and more fancy typing. It wraps the equivalent of the %totals hash (renamed %.data) into an outer object containing some utility routines, and makes the inner key object be a Date instead of a Pair:
role Totals {
my %months = <jan feb mar apr may jun jul aug sep oct nov dec> .antipairs «+» 1; # jan => 1
method month-name (Int $num --> Str) { %months.antipairs.hash{$num} }
method month-num (Str $name --> Int) { %months{lc $name} }
has %.data{Date} handles <sort>;
}
my $totals = Totals.new;
for lines.grep(/redis/ & /Partial/)».words {
++$totals.data{ Date.new: :year(2000), :month(Totals.month-num: .[0]), :day(.[1]) }
}
for $totals .sort {
printf "%3s %2d : %-d\n", Totals.month-name(.key.month), .key.day, .value
}
In the first solution, sort did the right thing because it was comparing Pairs, and cmp in turn did the right thing given how I'd set the pairs up.
In this solution sort/cmp do the right thing without needing to coerce string values to Ints, because the totals entries are Dates and they compare according to ordinary date comparison rules.