This post is part of a series on Mohammad Anwar’s excellent Weekly Challenge, where hackers submit solutions in Perl, Raku, or any other language, to two different challenges every week. (It’s a lot of fun, if you’re into that sort of thing.)
(Yes, the title is a reference to the 2006 Zach Snyder film.)
I’d say I can hardly believe we’ve hit the 300th week in a row of the Weekly Challenge. But, then again, knowing a little bit about the persistence and work ethic of Mohammad Anwar, it’s really no surprise. Let’s keep this going for a good while longer!
Task 1: Beautiful Arrangements
The first task this week has us take a positive $integer and look at every permutation @P of the numbers 1..$int. We then return the count of permutations where every element in @P is either divisible by its 1-based index, or the index is divisible by the element.
For example, beautiful(3) has six possible permutations. Note a|b means “a divides b”:
| [1,2,3] | 1|1, 2|2, 3|3 | Valid (trivial case) |
| [1,3,2] | Second case fails, since 2|3 and 3|2 are both false. Third case fails for the same reason. | Invalid |
| [2,1,3] | 1|2, 1|2, 3|3 | Valid |
| [2,3,1] | Second case fails, since 2|3 and 3|2 are both false. | Invalid |
| [3,1,2] | Third case fails, since 2|3 and 3|2 are both false. | Invalid |
| [3,2,1] | 1|3, 2|2, 1|3 | Valid |
beautiful(3), and whether it would contribute to the result or not.Therefore beautiful(3) = 3. Some other results:
beautiful(1) = 1
beautiful(2) = 2
beautiful(3) = 3
beautiful(4) = 8
beautiful(5) = 10
beautiful(6) = 36
beautiful(7) = 41
beautiful(8) = 132
beautiful(9) = 250
beautiful(10) = 700
beautiful(11) = 750
beautiful(12) = 4,010
The code ends up being much more concise than my explanation:
use Algorithm::Permute;
sub beautiful($int) {
my $perms = Algorithm::Permute->new([1..$int]);
my $count = 0;
P: while (my @P = $perms->next) {
$P[$_-1] % $_ and $_ % $P[$_-1] and next P for 1..$int;
$count++;
}
$count;
}
I’ve written code to generate permutations in previous tasks, and permutations are more of a side quest in this particular task. So, I chose to simply use the very popular Algorithm::Permute CPAN module instead.
Since we are checking every possible permutation of n = $int elements, the time complexity of this algorithm is an eye-wateringly bad O(n!), which is why I opted to stop after beautiful(12) looked at 12! = 479,001,600 permutations.
You’ll note the use of next P, which effectively short-circuits the inner loop as soon as any of the conditions aren’t met. This speeds things up considerably. Not enough to move the needle away from factorial big-O complexity, but in practical terms, beautiful(10) returned the correct answer in about a third of the time, compared to a non-short-circuiting version.
Line 8 (highlighted) uses the common technique with modulo arithmetic to handle the “divides” checks, since $b % $a will be a true value if and only if $a does not divide $b, since there would be a remainder.
Change history
2024-12-19: Initial post.
2024-12-19: Corrected a logical error in my description of $b % $a and how that relates to a|b.
Task 2: Nested Array
The second task this week has us take an arbitrary permutation, P, of n integers between (0..n-1) and return the length of the longest sequence generated by starting at some index i and chaining as follows, until we reach a cycle:
i → P[i] → P[ P[i] ] → ⋯ → P[P[ … P[i] … ]]
That sequence is easy to generate iteratively:
use List::Util qw< max >;
sub longest_set_len(@ints) {
max map {
my %seen;
$_ = $ints[$_] while not $seen{$_}++;
scalar keys %seen;
} 0..$#ints;
}
We detect a cycle by tracking %seen values in a hash. An array would work just as well, but it would not be significantly faster, and this way we don’t have to track some separate counter; we can just return the number of keys as our result for each starting point.
The use of (core module) List::Util here gives us some more expressive syntax with the max function. Each loop through (the body of map { ... }) represents one starting index into the sequence.