To work on Perl Weekly Challenge 327 I must be MAD, you say? MAD? MAD?!?!
Task 1: Missing Integers
You are given an array of n
integers.
Write a script to find all the missing integers in the range 1..n
in the given array.
Example 1
Input: @ints = (1, 2, 1, 3, 2, 5)
Output: (4, 6)
The given array has 6 elements.
So we are looking for integers in the range 1..6 in the given array.
The missing integers: (4, 6)
Example 2
Input: @ints = (1, 1, 1)
Output: (2, 3)
Example 3
Input: @ints = (2, 2, 1)
Output: (3)
Approach
This is a set based exercise. We don’t care about how many times any of the integers appear in the input array of n
integers, we only care whether or not any of the integers in the range 1..n
don’t exist in it. We make a set of the integers, then loop from 1..n
to check if any are missing.
Raku
In Raku, there’s a Set class that will do that work for us. And because I love using Raku’s fancy unicode aliasing, I’m using the ∉ operator as well.
sub missingInts(@ints) {
my $set = @ints.Set;
my @missing;
for 1 .. @ints.elems -> $i {
@missing.push($i) if $i ∉ $set;
}
return @missing;
}
View the entire Raku script for this task on GitHub.
$ raku/ch-1.raku
Example 1:
Input: @ints = (1, 2, 1, 3, 2, 5)
Output: (4, 6)
Example 2:
Input: @ints = (1, 1, 1)
Output: (2, 3)
Example 3:
Input: @ints = (2, 2, 1)
Output: (3)
Perl
Perl doesn’t have sets, but it’s easy do do the same thing with a hash:
sub missingInts(@ints) {
my %set = map { $_ => 1} @ints;
my @missing;
foreach my $i ( 1 .. scalar(@ints) ) {
push @missing, $i unless exists $set{$i};
}
return @missing;
}
View the entire Perl script for this task on GitHub.
Python
But Python does have sets.
def missingInts(ints):
intSet = set(ints)
missing = []
for i in range(1, len(ints)+1):
if i not in intSet:
missing.append(i)
return missing
View the entire Python script for this task on GitHub.
Elixir
And so does Elixir.
defp map_reduce_func(i, {set, missing}) do
{i,
if not MapSet.member?(set, i) do
{set, missing ++ [i]}
else
{set, missing}
end
}
end
def missingInts(ints) do
{_, {_, missing}} =
1..length(ints)
|> Enum.map_reduce(
{ MapSet.new(ints), [] },
&map_reduce_func/2
)
missing
end
View the entire Elixir script for this task on GitHub.
Task 2: MAD
You are given an array of distinct integers.
Write a script to find all pairs of elements with minimum absolute difference (MAD) of any two elements.
Example 1
Input: @ints = (4, 1, 2, 3)
Output: [1,2], [2,3], [3,4]
The minimum absolute difference is 1.
Pairs with MAD: [1,2], [2,3], [3,4]
Example 2
Input: @ints = (1, 3, 7, 11, 15)
Output: [1,3]
Example 3
Input: @ints = (1, 5, 3, 8)
Output: [1,3], [3,5]
Approach
Ok, calculating the absolute difference between two integers x
and y
is easy, it’s ⎮x - y⎮
. So what we want to do is get all the possible pairs (I see a nested loop), compute the absolute difference for each pair in order, and compare it with whatever the minimum absolute difference was up until that point. If it’s the same as the previous minimum, we add the pair to a list. If it’s smaller than the previous minimum, we clear the list, make this the new minimum and add the pair to the new list.
Raku
At first, I thought about looping over the array with indexes, but then I realized I was going to have to get the values from the indexes, and I could just as easily accomplish what I wanted by shifting off an element in the outer loop and then just looping over the remaining elements in the inner loop. When I shifted the last element off the list in the final pass through the outer loop, there would be nothing left for the inner loop and that would just be a no-op.
sub MAD(@ints) {
my @mad;
# let's make the biggest int our starting minimum
my $min = max @ints;
while (my $x = @ints.shift) {
for @ints -> $y {
my $diff = abs($x - $y);
next if $diff > $min; # too big!
if ($diff < $min) { # we found a new min!
$min = $diff; # set the new minimum
@mad = (); # clear the old list
}
# make sure we put the numbers on the list
# with the smaller integer first
@mad.push([sort $x, $y]);
}
}
# and return the list with the pairs sorted as well
return @mad.sort({ $^a[0] cmp $^b[0] });
}
View the entire Raku script for this task on GitHub.
$ raku/ch-2.raku
Example 1:
Input: @ints = (4, 1, 2, 3)
Output: [1,2], [2,3], [3,4]
Example 2:
Input: @ints = (1, 3, 7, 11, 15)
Output: [1,3]
Example 3:
Input: @ints = (1, 5, 3, 8)
Output: [1,3], [3,5]
Perl
Again, there isn’t much different about the Perl implementation. We just need to import a max
function so I can set my initial minimum.
use List::AllUtils qw( max );
sub MAD(@ints) {
my @mad;
# let's make the biggest int our starting minimum
my $min = max @ints;
while (my $x = shift @ints) {
foreach my $y ( @ints ) {
my $diff = abs($x - $y);
next if $diff > $min; # too big!
if ($diff < $min) { # we found a new min!
$min = $diff; # set the new minimum
@mad = (); # clear the old list
}
# make sure we put the numbers on the list
# with the smaller integer first
push @mad, [sort $x, $y];
}
}
# and return the list with the pairs sorted as well
return sort { $a->[0] <=> $b->[0] } @mad;
}
View the entire Perl script for this task on GitHub.
Python
What bit me with the Python solution initially is that I didn’t indent lines 15-17 enough, so they weren’t inside the if
block on line 11. One might say that’s why I ought to be using 4-space indentation like Guido intended. Personally, I prefer having block delimiters that aren’t whitespace dependent. If I wanted so much significant whitespace, I’d use SNOBOL.
def MAD(ints):
mad = []
# let's make the biggest int our starting minimum
minDiff = max(ints)
x = ints.pop(0) # get the first value for x
while ints:
for y in ints:
diff = abs(x - y)
if diff <= minDiff: # not too big!
if diff < minDiff: # we found a new min!
minDiff = diff # set the new minimum
mad = [] # clear the old list
# make sure we put the numbers on the list
# with the smaller integer first
mad.append(sorted([x, y]))
x = ints.pop(0) # get the next value for x
# and return the list with the pairs sorted as well
return sorted(mad, key=lambda x: x[0])
View the entire Python script for this task on GitHub.
Elixir
In Elixir, I’m using a mix of two ways to iterate a list: for the outer loop, I’m using a function where I pass a list as a parameter and use Elixir’s [head | tail]
syntax to grab the first element off the list and leave the remaining elements in their own list (line 22). Then, for the inner loop, I’m using Enum.map_reduce/3
to loop over the remaining elements without modifying them and using a private function to modify minDiff
and madList
as needed. Then I’m making a recursive call on line 26 which will pull off the next value for x
and repeat the whole process.
defp map_reduce_func(y, {x, minDiff, madList}) do
diff = abs(x - y)
{minDiff, madList} = cond do
diff > minDiff ->
{minDiff, madList} # no change
diff == minDiff ->
# add [x,y] to the current list
{minDiff, madList ++ [ Enum.sort([x, y]) ]}
diff < minDiff ->
# we found a new min!
# make [x,y] the start of new list
{diff, [ Enum.sort([x, y]) ]}
end
{y, {x, minDiff, madList}}
end
def mad([], _, madList), do: madList
def mad([x | ints], minDiff, madList) do
{_, {_, minDiff, madList}} = Enum.map_reduce(
ints, {x, minDiff, madList}, &map_reduce_func/2
)
mad(ints, minDiff, madList)
end
def mad(ints) do
# return the list with the pairs sorted as well
mad(ints, Enum.max(ints), [])
|> Enum.sort(&(List.first(&1) <= List.first(&2)))
end
View the entire Elixir script for this task on GitHub.
Here’s all my solutions in GItHub: https://github.com/packy/perlweeklychallenge-club/tree/master/challenge-327/packy-anderson
Pingback: Perl Weekly Challenge: It MUST be nice! | Packy’s Place