So I’m back again for the Perl Weekly Challenge 009

First square number that has at least 5 distinct digits

Challenge #1 demonstrates again the power of lazy lists. I think generating results from lazy lists is pretty much becoming my favourite feature of Perl 6 by default – I find a use for it so frequently.

In this case we lazily generate a list from 0 to infinity – (^∞) is basically a term, right? – map it to a list of square numbers with .map(* ** 2), and then filter to numbers that have 5 unique digits.

To do this I used Bags (Sets would have worked too):

.grep(*.comb.Bag.elems >= $digits)

Where $digits is in this case 5 (the default for a value entered on the command line in my solution). This will find all the Square numbers with at least 5 digits, but it will literally take forever, so best to cut it short at the first one with a [0].

As usual, it’s on github if you care.

Varying styles of rank

Insert standard rant about vague specifications here. In the end, for test data I got 20 copypasta from a random name generator and Z=>pped them up to dice rolls out of 10.

All the same, I went relatively all out on this one. Not because the challenge was hard, but because it’s a nice opportunity to (completely superfluously) play with multi dispatch.

RankMode is an enum of the ranking modes, <rank-standard rank-modified rank-dense>. Again, this is entirely unnecessary (actually I added it on at the end).

Now I can stick it in a multi dispatch prototype though (more stuff I added later):

my proto rank($, RankMode, &?) { * }

$ basically indicates the first argument is some kind of container (thanks @raiph for the correction), RankMode is as above, and &? indicates the third argument is a routine and is optional.

Following this (in source order - chronologically I did them first), I have three multi declarations that look like:

my multi rank(@scores where { $_».?value.all ~~ Int },
              rank-standard,
              &ranking = {$^b.key <=> $^a.key}) { ... }

With rank-modified and rank-dense substituted for rank-standard in the second and third subs’ signatures. This allows us to make apparently the same function call but a different implementation is used depending on what RankMode is given.

You might have noticed the where constraint on the @scores argument. This is to ensure that a list of Pairs has been passed (or at least of somethings that implement .value) with all the values being integers. This is actually a little contrary to the flexibility provided by having a &ranking call back, but provides an early indication of something that would prevent the internals of these functions working – and if we wanted to rank something with a different structure later we could add another version of rank with another constraint and both would still work.

The contents of these subroutines is very similar, where:

  • First, it inverts the list of scores added to it (this is why we compare the key in the default &ranking) and create a Hash from this, meaning that keys of the @scores argument get grouped together according to their score.
  • Then each group of Pairs is mapped to its ranking and spat out into an output array, with the Pair inverted back to its original order.
  • Somewhere in this rank of the group or the one following it is determined in the way appropriate for each implementation.

I’m actually a little frustrated that when I tried to do this with a mapping function, my $n ranking variable would always be evaluated before anything was mapped to the output, which made the rank-standard routine behave the same as the rank-modified one; if anyone has any ideas on how to avoid this, I’d love to hear them. The implementation was:

Hash.new.append(@scores.invert).pairs.sort(&ranking).map: {
  my $e = .invert.map: {$n => $_};
  $n += .value.elems;
  |$e;
}

Anyway I also have a fourth multi-sub:

my multi rank(%scores, RankMode $mode, &ranking = {$^b.key <=> $^a.key}) {
  rank(%scores.pairs, $mode, &ranking);
}

Which allows me to call rank with a Hash, which it will turn into a list of pairs, and then pass it on to the appropriate subroutine according to the given $mode. Which is just kind of cool.

There’s not a single if statement in my code. You can check this on github

Optional challenge

Again I’m opting out of this one, for the same reasons as last week, and also because I started this challenge late owed to being at said $day-job all week (which I wasn’t before).

If I was to have a go at this one though, I’d probably use Cro::Http::Client though, because it will even marshal objects to JSON for me.