Perl Weekly Challenge 116
Part 1 was probably the hardest this week, but I ended up being able to adapt a solution from an existing Python library.
Task 1: Number Sequence
You are given a number $N >= 10
.
Write a script to split the given number such that the difference between two consecutive numbers is always 1 and it shouldn’t have leading 0.
Print the given number if it’s impossible to split the number.
Examples
Input: $N = 1234
Output: 1,2,3,4
Input: $N = 91011
Output: 9,10,11
Input: $N = 10203
Output: 10203 as it is impossible to split satisfying the conditions.
Solution
See below for explanation and any implementation-specific comments.
# Raku adaptation of Python's more-itertools.partitions: https://git.io/JZL8Q
sub partitions(Str $S) {
my @sequence = $S.comb;
my $n = @sequence.elems;
my @partitions = gather for (1..^$n).combinations -> @combination {
my @partition = gather for (0, |@combination) Z (|@combination, $n) -> ($i, $j) {
take [@sequence[$i..^$j]];
}
take @partition;
}
gather for @partitions -> @partition {
# Filter out elements with leading zeros
my @invalid = @partition.grep(*.head eq '0');
if @invalid.elems == 0 {
take @partition.map(*.join.Int);
}
}
}
sub challenge(Int $N where $N >= 10) returns Str {
my $S = $N.Str;
my $solution = partitions($S).first: -> @partition { # [1]
my @zipped = @partition[0..*-1] Z @partition[1..*]; # [2]
my @filtered = @zipped.grep(-> ($a, $b) { $b - $a == 1 }); # [3]
@zipped.elems > 0 && @zipped.elems == @filtered.elems; # [4]
}
with $solution { # [5]
$solution.join(',');
} else {
$S
}
}
sub MAIN(Int $N) {
say challenge($N);
}
This program runs as such:
$ raku ch-1.raku 1234
1,2,3,4
Explanation
I won’t focus on the partitions
function too much, as it is just a means to and end. At a high level, it takes a string (let’s say 1234
) and returns a list that looks like this: ((1234) (1 234) (12 34) (123 4) (1 2 34) (1 23 4) (12 3 4) (1 2 3 4))
; basically every permutation of every size split possible (with order maintained). Once we have that list of partitions, we want to find the first one that satisfies our condition – each element is 1 less than the next element. If we found one, return it as a comma-separated string. Otherwise, return the input.
Specific comments
first
is kind of likecollectFirst
in Scala; it will return either the element that matches the condition orNil
if nothing matches.- To compare our elements pairwise, we need to zip them up like so. In Scala this could be
partition.init.zip(partition.tail)
orpartition.sliding(2)
, but again, Raku doesn’t have those cool functions. - Now that we have the zipped up, we filter it to down to elements that match our condition.
- Finally, if
@filtered
didn’t remove anything, then this is a winner! We also need to check if@zipped
is non-empty, because a@partition
of size 1 will cause that. with
is the same as sayingif $solution.defined
, since$solution
can beNil
.
Task 2: Sum of Squares
You are given a number $N >= 10
.
Write a script to find out if the given number $N
is such that sum of squares of all digits is a perfect square. Print 1 if it is otherwise 0.
Examples
Input: $N = 34
Ouput: 1 as 3^2 + 4^2 => 9 + 16 => 25 => 5^2
Input: $N = 50
Output: 1 as 5^2 + 0^2 => 25 + 0 => 25 => 5^2
Input: $N = 52
Output: 0 as 5^2 + 2^2 => 25 + 4 => 29
Solution
See below for explanation and any implementation-specific comments.
sub challenge(Int $N where $N >= 10) returns Int {
my $square-sum = $N.comb.map(*²).sum; # [1][2]
$square-sum.sqrt.narrow ~~ Int ?? 1 !! 0; # [3]
}
sub MAIN(Int $N) {
say challenge($N);
}
This program runs as such:
$ raku ch-2.raku 34
1
Explanation
We convert the input number to a list of digits, then square each digit, then sum all the squares. We then check if the sum of squares is an integer (i.e., a perfect square). If so, we return 1
, else 0
.
Specific Comments
- I like the first part support of Unicode in Raku. It makes it very clear we are squaring each element of a list.
- I don’t like this – this takes 2 passes (one for squaring and one for summing). In Scala, I would write
list.foldLeft(0)((runningSum, elem) => runningSum + math.pow(elem, 2))
, which only takes one pass. However, themath.pow
is uglier than it is in Raku, so you win some you lose some. narrow
returns the most granular numerical type that this number matches (sqrt
returns aNumeric
). If it is anInt
, this is a perfect square.
Final Thoughts
Two fun challenges this week! Glad I am ahead of the curve and won’t be behind due to a busy weekend this week.