Perl Weekly Challenge 268: Magic Numbers

These are some answers to the Week 268, Task 1, of the Perl Weekly Challenge organized by Mohammad S. Anwar.

Spoiler Alert: This weekly challenge deadline is due in a few days from now (on May 12, 2024 at 23:59). This blog post provides some solutions to this challenge. Please don’t read on if you intend to complete the challenge on your own.

Task 1: Magic Number

You are given two arrays of integers of same size, @x and @y.

Write a script to find the magic number that when added to each elements of one the array gives the second array. Elements order is not important.

Example 1

Input: @x = (3, 7, 5)``
       @y = (9, 5, 7)
Output: 2

The magic number is 2.
@x = (3, 7, 5)
   +  2  2  2
@y = (5, 9, 7)

Example 2

Input: @x = (1, 2, 1)
       @y = (5, 4, 4)
Output: 3

The magic number is 3.
@x = (1, 2, 1)
   +  3  3  3
@y = (5, 4, 4)

Example 3

Input: @x = (2)
       @y = (5)
Output: 3

We should first notice that it is possible that there is no solution. In fact, with random input, the probability is high that there will be no solution, and I believe we should consider this case. In both my implementations (Raku and Perl) below, I have added one such case to the examples provided with the task.

Magic Number in Raku

The task specifies that elements order is not important. But any solution will rely on the order being the same, either ascending or descending (or some other custom order). So we will sort the input arrays and compare the items pairwise.

In the following Raku implementation, we sort the items and build an array (@gaps) of the pairwise differences between the sorted items. We have a solution if all the pairwise differences are equal, which we find out using the [==] reduction meta-operator.

sub magic-nr (@x, @y) {
    my @in1 = @x.sort;                                                                                 `
    my @in2 = @y.sort;                                                ]                                              `
    my @gaps = map {@in1[$_] - @in2[$_]}, 0..@x.end;
    return Nil unless [==] @gaps;
    return @gaps[0].abs;
}

my @tests = (<3 7 5>, <9 5 7>), (<1 2 1>, <5 4 4>), 
            ((2,), (5,)), (<3 7 5>, <6 5 7>);
for @tests -> @test {
    printf "%-6s - %-6s => ", "@test[0]", "@test[1]";
    say magic-nr @test[0], @test[1];
}

This program displays the following output:

$ raku ./magic-numbers.raku
3 7 5  - 9 5 7  => 2
1 2 1  - 5 4 4  => 3
2      - 5      => 3
3 7 5  - 6 5 7  => Nil

Magic Number in Perl

Our Perl implementation will be slightly different and, we hope, slightly more efficient in some failure cases: we don't need to compute and compare all items, we can stop the iteration as soon as we find a mismatch.

use strict;
use warnings;
use feature 'say';

sub magic_nr  {
    my @in1 = sort {$a<=>$b} @{$_[0]};
    my @in2 = sort {$a<=>$b} @{$_[1]};
    my $gap = $in1[0] - $in2[0];
    for my $i (1..$#in1) {
        return "undef" if $in1[$i] - $in2[$i] != $gap;
    }
    return abs $gap;
}

my @tests = ([[<3 7 5>], [<9 5 7>]], [[<1 2 1>], [<5 4 4>]],
             [[2,], [5]], [[<3 7 5>], [<6 5 7>]] );

for my $test (@tests) {
    printf "%-6s - %-6s => ", "@{$test->[0]}", "@{$test->[1]}";
    say magic_nr $test->[0], $test->[1];
}

This program displays the following output:

$ perl ./magic-numbers.pl
3 7 5  - 9 5 7  => 2
1 2 1  - 5 4 4  => 3
2      - 5      => 3
3 7 5  - 6 5 7  => undef

Wrapping up

The next week Perl Weekly Challenge will start soon. If you want to participate in this challenge, please check https://perlweeklychallenge.org/ and make sure you answer the challenge before 23:59 BST (British summer time) on May 19, 2024. And, please, also spread the word about the Perl Weekly Challenge if you can.

Leave a comment

About laurent_r

user-pic I am the author of the "Think Perl 6" book (O'Reilly, 2017) and I blog about the Perl 5 and Raku programming languages.