Perl Weekly Challenge: Sort Languages to the Largest of Three

Yeah, yeah, it feels like a bit of a stretch this week, but when I saw the tasks, my brain read off “Largest of Three” to the tune of “Power of Two” by The Indigo Girls. No more of a stretch than Three of a Reverse Sum Pair was, I guess.

Task 1: Sort Language

You are given two array of languages and its popularity.

Write a script to sort the language based on popularity.

Example 1

Input: @lang = ('perl', 'c', 'python')
       @popularity = (2, 1, 3)
Output: ('c', 'perl', 'python')

Example 2

Input: @lang = ('c++', 'haskell', 'java')
       @popularity = (1, 3, 2)
Output: ('c++', 'java', 'haskell')

Approach

This could be done with a single loop, using the second array to assign values from the first array to particular indices in the output array:

for (i = 0; i < length(lang); i++) {
  output[ popularity[i]-1 ] = lang[i];
}

But this task is phrased as a sort, so let’s code it that way: the second array has the values we’ll use to compare the first array elements with in a custom sort.

Raku

sub sortLanguage(@lang, @popularity) {
  # build a hash associating @popularity with @lang
  my %lang_pop = map {
    @lang[$_] => @popularity[$_]
  }, @lang.keys;
  my @sorted = @lang.sort({
    # sort by %lang_pop, not @lang
    %lang_pop{$^a} <=> %lang_pop{$^b}
  });
  return @sorted;
}

I’m remembering my discovery last week that @lang.keys would give me the sequence 0, 1, 2.

View the entire Raku script for this task on GitHub.

Perl

Again, the changes from Raku to Perl aren’t Earth-shattering:

sub sortLanguage{
  my ($lang, $popularity) = @_;
  # build a hash associating @popularity with @lang
  my %lang_pop = map {
    $lang->[$_] => $popularity->[$_]
  } 0 .. $#{$lang};
  my @sorted = sort {
    # sort by %lang_pop, not @$lang
    $lang_pop{$a} <=> $lang_pop{$b}
  } @$lang;
  return @sorted;
}

View the entire Perl script for this task on GitHub.

Python

Python’s nifty sorted built-in makes this pretty easy.

def sortLanguage(lang, popularity):
    # build a dict associating popularity with lang
    lang_pop = {
        v: popularity[i] for i,v in enumerate(lang)
    }
    sorted_list = sorted(lang,
                         # sort by lang_pop, not lang
                         key=lambda x: (lang_pop[x]))
    return sorted_list

View the entire Python script for this task on GitHub.


Task 2: Largest of Three

You are given an array of integers >= 0.

Write a script to return the largest number formed by concatenating some of the given integers in any order which is also multiple of 3. Return -1 if none found.

Example 1

Input: @ints = (8, 1, 9)
Output: 981

981 % 3 == 0

Example 2

Input: @ints = (8, 6, 7, 1, 0)
Output: 8760

Example 3

Input: @ints = (1)
Output: -1

Approach

Ok, it’s pretty obvious that the largest combination will have the digits sorted in descending order, so I’m guessing I want to sort the digits first, and then start making combinations until I either a) find a combination that’s a multiple of 3, or b) exhaust my combinations.

Raku

sub largestOfThree(@ints) {
  my $max = -1; # initialize our failure case
  for @ints.combinations -> @combo {
    next unless @combo.elems > 0; # not empty set
    # sort the digits in descending order,
    # join them, then convert to an Int
    my $num = @combo.sort.reverse.join('').Int;
    next unless $num > $max;   # not bigger than current max
    next unless $num % 3 == 0; # not divisible by 3
    $max = $num;
  }
  return $max;
}

View the entire Raku script for this task on GitHub.

Perl

Again,  Algorithm::Combinatorics’ combinations function comes to the rescue.

use Algorithm::Combinatorics qw( combinations );

sub largestOfThree {
  my @ints = @_;
  my $max = -1; # initialize our failure case
  my @combos = map {
    combinations(\@ints, $_)
  } 1 .. scalar(@ints);
  foreach my $combo ( @combos ) {
    # sort the digits in descending order,
    # join them, then convert to an Int
    my $num = join('', reverse sort @$combo) + 0;
    next unless $num > $max;   # not bigger than current max
    next unless $num % 3 == 0; # not divisible by 3
    $max = $num;
  }
  return $max;
}

View the entire Perl script for this task on GitHub.

Python

from itertools import combinations

def largestOfThree(ints):
    # generate a list of combinations
    combos = [
        c for i in range(1, len(ints)+1)
          for c in combinations(ints, i)
    ]
    maxval = -1 # initialize our failure case
    for combo in combos:
        combo_list = list(combo)
        combo_list.sort(reverse=True)
        num = int(''.join(map(str, combo_list)))
        if num <= maxval: # not bigger than current max
            continue
        if num % 3 != 0: # not divisible by 3
            continue
        maxval = num
    return maxval

At least this week I made the nested for loops to generate the combinations prettier.

View the entire Python script for this task on GitHub.


Here’s all my solutions in GItHub: https://github.com/packy/perlweeklychallenge-club/tree/master/challenge-245/packy-anderson

Perl Weekly Challenge: Bonus Script

Recently, the Perl Weekly Challenge has been linking to a EZPWC script that tries to automate a bunch of the interaction that’s necessary to participate in the challenge. But it does a bunch of stuff I don’t need, and it feels a bit like overkill. So I decided to whip up my own pwc script that I can run each week to take care of the repetitive stuff I do each week when I work on my challenge solutions.

#!bash - for syntax highlighting

function pwc_skeleton () {
  SKELETON=$1
  FILE=$2
  if [[ ! -f $FILE ]]; then
    cp $SKELETON $FILE
  fi
  chmod +x $FILE
}

function pwc () {
  cd $HOME/git/perlweeklychallenge-club/

  # update the repository to the latest week
  if ! git remote | grep upstream >/dev/null; then
    git remote add upstream \
      git@github.com:manwar/perlweeklychallenge-club.git
  fi
  git fetch upstream 
  git switch master
  git merge upstream/master
  git push

  # find the latest challenge directory
  CHALLENGE_DIR=$(ls -d challenge-* | tail -1)
  cd $CHALLENGE_DIR/packy-anderson

  # set up the skeleton files
  mkdir raku
  pwc_skeleton $CFGDIR/pwc/skeleton.raku raku/ch-1.raku
  pwc_skeleton $CFGDIR/pwc/skeleton.raku raku/ch-2.raku

  mkdir perl
  pwc_skeleton $CFGDIR/pwc/skeleton.pl perl/ch-1.pl
  pwc_skeleton $CFGDIR/pwc/skeleton.pl perl/ch-2.pl

  mkdir python
  pwc_skeleton $CFGDIR/pwc/skeleton.py python/ch-1.py
  pwc_skeleton $CFGDIR/pwc/skeleton.py python/ch-2.py

  touch blog.txt
  git add .
  code .
}

And yes, it’s in Bash and not Perl or Raku. Because sometimes Bash is just the right tool for the job. This file gets sourced from my .bashrc file, so the functions are defined and when I type pwc it’s executed in my current shell.

This and my skeleton files are under source control at https://github.com/packy/maccfg/tree/master/pwc.

Perl Weekly Challenge: Count… just a little bit smaller…

Ok, I don’t get to choose what music my brain pushes at me when I look at these challenges. Because my wife is performing in a production of Beehive: The 60’s Musical, one of the songs she gets to do is Try by Janis Joplin.

My wife does Janis proud.

But on to this week’s Challenge!

Image of Kay Koch as Janis Joplin

Task 1: Count Smaller

You are given an array of integers.

Write a script to calculate the number of integers smaller than the integer at each index.

Example 1

Input: @int = (8, 1, 2, 2, 3)
Output: (4, 0, 1, 1, 3)

For index = 0, count of elements less 8 is 4.
For index = 1, count of elements less 1 is 0.
For index = 2, count of elements less 2 is 1.
For index = 3, count of elements less 2 is 1.
For index = 4, count of elements less 3 is 3.

Example 2

Input: @int = (6, 5, 4, 8)
Output: (2, 1, 0, 3)

Example 3

Input: @int = (2, 2, 2)
Output: (0, 0, 0)

Approach

This is another double-loop over a single array, like last week. The outer loop (let’s call it the i loop) iterates over each of the elements in the array to produce the count for that index. The inner (j) loop iterates over each of the elements again and compares them to the i element. Easy-peasy.

Raku

sub countSmaller(@int) {
  my @counts;
  for 0 .. @int.elems - 1 -> $i {\
    for 0 .. @int.elems - 1 -> $j {
      @counts[$i]++ if @int[$j] < @int[$i];
    }
  }
  return @counts;
}

But when I ran this, I got

$ raku/ch-1.raku
Example 1:
Input: @int = (8, 1, 2, 2, 3)
Use of uninitialized value @output of type Any in string context.
Methods .^name, .raku, .gist, or .say can be used to stringify it to something meaningful.
  in sub solution at raku/ch-1.raku line 17
Output: (4, , 1, 1, 3)

Example 2:
Input: @int = (6, 5, 4, 8)
Use of uninitialized value @output of type Any in string context.
Methods .^name, .raku, .gist, or .say can be used to stringify it to something meaningful.
  in sub solution at raku/ch-1.raku line 17
Output: (2, 1, , 3)

Example 3:
Input: @int = (2, 2, 2)
Output: ()

What was going on here? Time to add some debugging:

sub countSmaller(@int) {
  my @counts;
  for 0 .. @int.elems - 1 -> $i {
    for 0 .. @int.elems - 1 -> $j {
      @counts[$i]++ if @int[$j] < @int[$i];
    }
  }
  say @counts.raku;
  return @counts;
}
$ raku/ch-1.raku
Example 1:
Input: @int = (8, 1, 2, 2, 3)
[4, Any, 1, 1, 3]
Use of uninitialized value @output of type Any in string context.
Methods .^name, .raku, .gist, or .say can be used to stringify it to something meaningful.
  in sub solution at raku/ch-1.raku line 18
Output: (4, , 1, 1, 3)

Example 2:
Input: @int = (6, 5, 4, 8)
[2, 1, Any, 3]
Use of uninitialized value @output of type Any in string context.
Methods .^name, .raku, .gist, or .say can be used to stringify it to something meaningful.
  in sub solution at raku/ch-1.raku line 18
Output: (2, 1, , 3)

Example 3:
Input: @int = (2, 2, 2)
[]
Output: ()

Ahhh! I see what’s happening: because I’m only incrementing the @counts[$i] value if @counts[$j] is smaller, then if none of the values are smaller, I never autovivified the value for that element. In Perl, the value would be undef, but in Raku, it’s Any. There’s an easy way to fix this: just initialize @counts[$i] to 0 before the $j loop:

sub countSmaller(@int) {
  my @counts;
  for 0 .. @int.elems - 1 -> $i {
    @counts[$i] = 0;
    for 0 .. @int.elems - 1 -> $j {
      @counts[$i]++ if @int[$j] < @int[$i];
    }
  }
  return @counts;
}

But something was bothering me. Coming from Perl, I have to say I like $#int better than @int.elems - 1. There should be a Raku-ish way to get the index of the last element in a list. I seem to recall seeing it once. And, after a bit of searching, I found it again: .end.

sub countSmaller(@int) {
  my @counts;
  for 0 .. @int.end -> $i {
    @counts[$i] = 0;
    for 0 .. @int.end -> $j {
      @counts[$i]++ if @int[$j] < @int[$i];
    }
  }
  return @counts;
}

Then I saw there’s something even BETTER: .keys! I’d never thought to get the keys of a list, only of a hash. But of course this should work in Raku!

sub countSmaller(@int) {
  my @counts;
  for @int.keys -> $i {
    @counts[$i] = 0;
    for @int.keys -> $j {
      @counts[$i]++ if @int[$j] < @int[$i];
    }
  }
  return @counts;
}

View the entire Raku script for this task on GitHub.

Perl

sub countSmaller {
  my @int = @_;
  my @counts;
  foreach my $i ( 0 .. $#int ) {
    $counts[$i] = 0;
    for my $j ( 0 .. $#int ) {
      $counts[$i]++ if $int[$j] < $int[$i];
    }
  }
  return @counts;
}

View the entire Perl script for this task on GitHub.

Python

Ooh. I just ran across a nifty Python built-in, enumerate:

def countSmaller(arr):
    counts = []
    for i, i_val in enumerate(arr):
        counts[i] = 0
        for j, j_val in enumerate(arr):
            if j_val < i_val:
                counts[i] += 1
    return counts
$ python/ch-1.py
Example 1:
Input: @int = (8, 1, 2, 2, 3)
Traceback (most recent call last):
  File "/Users/packy/git/perlweeklychallenge-club/challenge-244/packy-anderson/python/ch-1.py", line 22, in <module>
    solution([8, 1, 2, 2, 3])
  File "/Users/packy/git/perlweeklychallenge-club/challenge-244/packy-anderson/python/ch-1.py", line 18, in solution
    output = countSmaller(arr)
  File "/Users/packy/git/perlweeklychallenge-club/challenge-244/packy-anderson/python/ch-1.py", line 7, in countSmaller
    counts[i] = 0
IndexError: list assignment index out of range

Oh, right. You can’t just add elements to a Python array by assigning to its index. You need to .append() to the array:

def countSmaller(arr):
    counts = []
    for i, i_val in enumerate(arr):
        counts.append(0)
        for j, j_val in enumerate(arr):
            if j_val < i_val:
                counts[i] += 1
    return counts

View the entire Python script for this task on GitHub.


Task 2: Group Hero

You are given an array of integers representing the strength.

Write a script to return the sum of the powers of all possible combinations; power is defined as the square of the largest number in a sequence, multiplied by the smallest.

Example 1

Input: @nums = (2, 1, 4)
Output: 141

Group 1: (2) => square(max(2)) * min(2) => 4 * 2 => 8
Group 2: (1) => square(max(1)) * min(1) => 1 * 1 => 1
Group 3: (4) => square(max(4)) * min(4) => 16 * 4 => 64
Group 4: (2,1) => square(max(2,1)) * min(2,1) => 4 * 1 => 4
Group 5: (2,4) => square(max(2,4)) * min(2,4) => 16 * 2 => 32
Group 6: (1,4) => square(max(1,4)) * min(1,4) => 16 * 1 => 16
Group 7: (2,1,4) => square(max(2,1,4)) * min(2,1,4) => 16 * 1 => 16

Sum: 8 + 1 + 64 + 4 + 32 + 16 + 16 => 141

Approach

Ok, I feel like there are a bunch of pieces here, and the clearest way to tackle the problem is to attack each of the pieces individually:

First, we need a function that, given a list, calculates the power for that list. The meat of that abstracts out to square(max(list)) * min(list). Then we need to generate lists of all the combinations of our list of numbers, push each of those through our power function, and then sum those results.

Raku

Fortunately, in Raku, getting the max and min values of a list are easy:

sub power(@nums) {
  return( (@nums.max ** 2) * @nums.min );
}

And getting all the possible combinations for a list is easy, too: .combinations.

sub groupHero(@nums) {
  my $sum = 0;
  for @nums.combinations: 1 .. @nums.elems -> @list {
    $sum += power(@list);
  }
  return $sum;
}

But wait! I’m just adding things up? That sounds like… Raku’s Reduction Metaoperator[ ]! All I have to do is put what I’m summing in a list…

sub groupHero(@nums) {
  return [+] (
    power($_) for @nums.combinations: 1 .. @nums.elems
  );
}

View the entire Raku script for this task on GitHub.

Perl

In Perl, not everything is built in, but that’s where the power of CPAN comes in: List::Util and its min, max, and sum functions, and Algorithm::Combinatorics’ combinations function.

use Algorithm::Combinatorics qw( combinations );
use List::Util qw( min max sum );

sub power {
  my $list = shift;
  return( (max(@$list) ** 2) * min(@$list) );
}

sub groupHero(@nums) {
  return sum(
    # generate the list of powers for each combination
    map { power($_) }
    # generate the list of combinations
    map { combinations(\@nums, $_) } 1 .. scalar(@nums)
  );
}

View the entire Perl script for this task on GitHub.

Python

from itertools import combinations

def power(arr):
    return( (max(arr) ** 2) * min(arr) )

def groupHero(nums):
    # generate a list of combinations
    comb = []
    for i in range(1, len(nums)+1):
        for c in combinations(nums, i):
            comb.append(c)
    return sum(
      # generate the list of powers for each combination
      [ power(x) for x in comb ] 
    )

I tried to not build the list of combinations with two loops and an intermediate array object, but I kept getting a list of iterables passed to power, so I got tired…

View the entire Python script for this task on GitHub.


Here’s all my solutions in GItHub: https://github.com/packy/perlweeklychallenge-club/tree/master/challenge-244/packy-anderson

Perl Weekly Challenge: Three of a Reverse Sum Pair

King Crimson - Three of a Perfect Pair

Finally, for Perl Weekly Challenge 243 I get my musical association mojo back, and the first challenge immediately made me think of King Crimson’s Three of a Perfect Pair.

I’m listening to the album while I write these solutions.

Task 1: Reverse Pairs

You are given an array of integers.

Write a script to return the number of reverse pairs in the given array.

A reverse pair is a pair (i, j) where: a) 0 <= i < j < nums.length and b) nums[i] > 2 * nums[j].

Example 1

Input: @nums = (1, 3, 2, 3, 1)
Output: 2

(1, 4) => nums[1] = 3, nums[4] = 1, 3 > 2 * 1
(3, 4) => nums[3] = 3, nums[4] = 1, 3 > 2 * 1

Example 2

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

(1, 4) => nums[1] = 4, nums[4] = 1, 4 > 2 * 1
(2, 4) => nums[2] = 3, nums[4] = 1, 3 > 2 * 1
(3, 4) => nums[3] = 5, nums[4] = 1, 5 > 2 * 1

Approach

This is a pretty straightforward nested loop over an array: one loop for i from 0 to array length – 1, and an inner loop for j from i to array length. Just to make things easier to read, I’m going to make the test a function, isReversePair(), so the code is more expressive.

Raku

sub isReversePair(@arr, $i, $j) {
  return @arr[$i] > 2 * @arr[$j];
}

sub findReversePairs(@arr) {
  my @pairs;
  for 0 .. @arr.elems - 2 -> $i {
    for $i+1 .. @arr.elems - 1 -> $j {
      @pairs.push([$i, $j]) if isReversePair(@arr, $i, $j);
    }
  }
  return @pairs;
}

Now, I don’t have to test condition a) of the definition of a reverse pair, because the way I’m looping, 0 <= i < j < nums.length will always be true.

But you know what? That’s boring. I feel like, in Raku, at least, I should be able to make isReversePair() a method call on the array… and in Raku, I can. It took a little bit of searching for examples of how to extend the Array class, but I found it in the Raku Advent Calendar for Dec 8, 2013: Array-based Objects. In retrospect, it seems obvious that all I would need to do in a method to access the array elements is self[].

class ReversePairArray is Array {
  method isReversePair($i, $j) {
    return self[$i] > 2 * self[$j];
  }
}

sub findReversePairs(@arr) {
  my @pairs;
  my @rpArray := ReversePairArray.new(@arr);
  for 0 .. @rpArray.elems - 2 -> $i {
    for $i+1 .. @rpArray.elems - 1 -> $j {
      @pairs.push([$i, $j]) if @rpArray.isReversePair($i, $j);
    }
  }
  return @pairs;
}

The one thing that I’m glad the advent calendar entry addressed was the need for := instead of = if I wanted to use the sigil @ on my variable. Without the colon, a positional container (a variable with the sigil @) will be created as an empty Array whose contained values are then set to the list after the =. I could have used a $ for a variable that would hold any type and used a =, but I wanted to make this feel as array-like as possible.

View the entire Raku script for this task on GitHub.

Perl

sub isReversePair {
  my ($arr, $i, $j) = @_;
  return $arr->[$i] > 2 * $arr->[$j];
}

sub findReversePairs {
  my @arr = @_;
  my @pairs;
  foreach my $i ( 0 .. $#arr - 1) {
    foreach my $j ( $i+1 .. $#arr) {
      push @pairs, [$i, $j] if isReversePair(\@arr, $i, $j);
    }
  }
  return @pairs;
}

For Perl, however, I arrays aren’t built-in classes that I can easily override, so I’m just going with the boring function-based approach where I’m passing in a reference to the array and the two indices I’m checking.

If I was concerned with performance over expressiveness, I could just inline the condition and forgo the function isReversePair():

sub findReversePairs {
  my @arr = @_;
  my @pairs;
  foreach my $i ( 0 .. $#arr - 1) {
    foreach my $j ( $i+1 .. $#arr) {
      push @pairs, [$i, $j] if $arr[$i] > 2 * $arr[$j];
    }
  }
  return @pairs;
}

View the entire Perl script for this task on GitHub.

Python

from collections import UserList

class ReversePairArray(UserList):
    def isReversePair(self, i, j):
        return self.data[i] > 2 * self.data[j]

def findReversePairs(nums):
    pairs = []
    rpArray = ReversePairArray(nums)
    for i in range(0, len(nums) - 1):
        for j in range(i+1, len(nums)):
            if rpArray.isReversePair(i, j):
                pairs.append([i, j])
    return pairs

I had to do a bunch of Googling to figure out the best way to extend arrays, and it seems that collections.UserList was the best candidate:

This class acts as a wrapper around list objects. It is a useful base class for your own list-like classes which can inherit from them and override existing methods or add new ones. In this way, one can add new behaviors to lists.

View the entire Python script for this task on GitHub.


Task 2: Floor Sum

You are given an array of positive integers (>=1).

Write a script to return the sum of floor(nums[i] / nums[j]) where 0 <= i,j < nums.length. The floor() function returns the integer part of the division.

Example 1

Input: @nums = (2, 5, 9)
Output: 10

floor(2 / 5) = 0
floor(2 / 9) = 0
floor(5 / 9) = 0
floor(2 / 2) = 1
floor(5 / 5) = 1
floor(9 / 9) = 1
floor(5 / 2) = 2
floor(9 / 2) = 4
floor(9 / 5) = 1

Example 2

Input: @nums = (7, 7, 7, 7, 7, 7, 7)
Output: 49

Approach

Another nested loop over an array. This time we’re just summing the results of performing integer division on each of the elements against each other. I don’t quite grok the arrangement of the explanatory text in Example 1; I feel like it should be sorted like this:

floor(2 / 2) = 1
floor(2 / 5) = 0
floor(2 / 9) = 0
floor(5 / 2) = 2
floor(5 / 5) = 1
floor(5 / 9) = 0
floor(9 / 2) = 4
floor(9 / 5) = 1
floor(9 / 9) = 1

For the second example, it’s just going to be dividing 7 by itself each time, yielding 1. Since there’s 7 elements in the input list, we’re doing this division 49 (72) times.

Raku

sub floorSum(@arr) {
  my $sum = 0;
  for 0 .. @arr.elems - 1 -> $i {
    for 0 .. @arr.elems - 1 -> $j {
      $sum += (@arr[$i] / @arr[$j]).truncate;
    }
  }
  return $sum;
}

View the entire Raku script for this task on GitHub.

Perl

sub floorSum {
  my @arr = @_;
  my $sum = 0;
  foreach my $i (0 .. $#arr) {
    foreach my $j (0 .. $#arr) {
      $sum += int($arr[$i] / $arr[$j]);
    }
  }
  return $sum;
}

View the entire Perl script for this task on GitHub.

Python

from math import trunc

def floorSum(nums):
    sum = 0;
    for i in range(0, len(nums)):
        for j in range(0, len(nums)):
            sum += trunc(nums[i] / nums[j])
    return sum

View the entire Python script for this task on GitHub.


Here’s all my solutions in GItHub: https://github.com/packy/perlweeklychallenge-club/tree/master/challenge-243/packy-anderson

Perl Weekly Challenge: Flip the Missing Matrix Members

Is it bad that I’m getting frustrated that I’m not coming up with a musical inspiration for these two challenges? 😤

Task 1: Missing Members

You are given two arrays of integers.

Write a script to find out the missing members in each other arrays.

Example 1

Input: @arr1 = (1, 2, 3)
       @arr2 = (2, 4, 6)
Output: ([1, 3], [4, 6])

(1, 2, 3) has 2 members (1, 3) missing in the array (2, 4, 6).
(2, 4, 6) has 2 members (4, 6) missing in the array (1, 2, 3).

Example 2

Input: @arr1 = (1, 2, 3, 3)
       @arr2 = (1, 1, 2, 2)
Output: ([3])

(1, 2, 3, 3) has 2 members (3, 3) missing in the array (1, 1, 2, 2). Since they are same, keep just one.
(1, 1, 2, 2) has 0 member missing in the array (1, 2, 3, 3).

Approach

Well, whenever we need to look for whether an element exists in a set, I think hashes. If we take the target array, hashify it, and then ask whether each element in the source array exists in that hash, we can easily find elements in the source missing from the target.

Raku

In Raku, that would look like this:

sub findMissing(@source, @target) {
  # convert the target into a hash with each element as keys
  my %targetHash = @target.map: * => 1;

  # see which elements in the source are not in the target
  my @missing;
  for @source -> $elem {
    if (%targetHash{$elem}:!exists) {
      @missing.push($elem);
    }
  }

  return @missing;
}

Remembering from Challenge 334 that

  • Testing for the existence of an element is the Subscript Adverb :exists.
  • If you try to use the construction ! $hash{$key}:exists, you get the error Precedence issue with ! and :exists, perhaps you meant :!exists? (I didn’t get an error with unless ($hash{$key}:exists), but I also wound up not getting the results I wanted.

But we need to call this twice to find the missing elements, and formatting the explanation will also be repetitive, so let’s put that in the subroutine, too:

use Lingua::Conjunction;

sub findMissing(@source, @target, @output, $explanation is rw) {
  # convert the target into a hash with each element as keys
  my %targetHash = @target.map: * => 1;

  # see which elements in the source are not in the target
  my @missing;
  for @source -> $elem {
    if (%targetHash{$elem}:!exists) {
      @missing.push($elem);
    }
  }

  # format output explaining what we found
  $explanation ~= "\n(" ~ @source.join(', ') ~ ") has ";
  $explanation ~= @missing.elems;
  $explanation ~= conjunction(@missing, :str(' member[|s] '));
  if (@missing.elems > 0) {
    $explanation ~= '(' ~ @missing.join(', ') ~ ') ';
    @output.push(@missing.unique);
  }
  $explanation ~= 'missing from the array ';
  $explanation ~= '(' ~ @target.join(', ') ~ ')';
}

sub findSolution(@arr1, @arr2, @output, $explanation is rw) {
  findMissing(@arr1, @arr2, @output, $explanation);
  findMissing(@arr2, @arr1, @output, $explanation);
}

Note I’m making the string parameter $explanation a read-write parameter by specifying is rw. I don’t have to do that for @output because arrays are passed by reference by default.

View the entire Raku script for this task on GitHub.

Perl

The Perl solution is basically the same, except we pass around references instead of read/write parameters.

use List::Util qw( uniq );

sub findMissing {
  my ($source, $target, $output, $explanation) = @_;

  # convert the target into a hash with each element as keys
  my %targetHash = map { $_ => 1 } @$target;

  # see which elements in the source are not in the target
  my @missing;
  foreach my $elem ( @$source ) {
    if (! exists $targetHash{$elem}) {
      push @missing, $elem;
    }
  }

  # format output explaining what we found
  $$explanation .= "\n(" . join(', ', @$source) . ") has ";
  $$explanation .= scalar(@missing);
  $$explanation .= @missing == 1 ? ' member ' : ' members ';
  if (scalar(@missing) > 0) {
    $$explanation .= '(' . join(', ', @missing) . ') ';
    push @$output, [ uniq @missing ];
  }
  $$explanation .= 'missing from the array ';
  $$explanation .= '(' . join(', ', @$target) . ')';
}

sub findSolution {
  my($arr1, $arr2, $output, $explanation) = @_;
  findMissing($arr1, $arr2, $output, $explanation);
  findMissing($arr2, $arr1, $output, $explanation);
}

View the entire Perl script for this task on GitHub.

Python

In Python, all passing is by value, which isn’t a problem for an object like an array, since the value that’s passed in is the reference to the object. But when a string is passed, the value of the string is passed, so any changes to the string are local to the function… unless we return the string as a return value. 😉

def comma_join(arr):
    return ', '.join(map(lambda i: str(i), arr))

def findMissing(source, target, output, explanation):
    # convert the target into a map with each element as keys
    targetMap = { x: 1 for x in target }

    # see which elements in the source are not in the target
    missing = []
    for elem in source:
        if not elem in targetMap:
            missing.append(elem)

    # format output explaining what we found
    explanation += "\n(" + comma_join(source) + ") has "
    explanation += str(len(missing))
    explanation += ' member ' if len(missing) == 1 \
                              else ' members '
    if (len(missing) > 0):
        explanation += '(' + comma_join(missing) + ') '
        output.append(set(missing))
    explanation += 'missing from the array '
    explanation += '(' + comma_join(target) + ')'
    return explanation


def findSolution(arr1, arr2, output):
    explanation = ''
    explanation = findMissing(arr1, arr2, output, explanation)
    explanation = findMissing(arr2, arr1, output, explanation)
    return explanation

View the entire Python script for this task on GitHub.


Task 2: Flip Matrix

You are given n x n binary matrix.

Write a script to flip the given matrix as below.

1 1 0
0 1 1
0 0 1

a) Reverse each row

0 1 1
1 1 0
1 0 0

b) Invert each member

1 0 0
0 0 1
0 1 1

Example 1

Input: @matrix = ([1, 1, 0], [1, 0, 1], [0, 0, 0])
Output: ([1, 0, 0], [0, 1, 0], [1, 1, 1])

Example 2

Input: @matrix = ([1, 1, 0, 0], [1, 0, 0, 1], [0, 1, 1, 1], [1, 0, 1, 0])
Output: ([1, 1, 0, 0], [0, 1, 1, 0], [0, 0, 0, 1], [1, 0, 1, 0])

Approach

There isn’t much to this: reverse each row, then flip the bits.

Raku

sub flipMatrix(@matrix) {
  for @matrix -> @subarray {
    @subarray = @subarray.reverse.map: (* - 1).abs;
  }
  return @matrix;
}

I’m taking advantage of the digits only being 1 and 0 by subtracting 1 from the digit and then taking the absolute value to flip them: 1 becomes 1 - 1 then 0, and 0 becomes 0 - 1 then -1 then 1. The most challenging thing was getting the slurpy parameters correct on my solution function.

View the entire Raku script for this task on GitHub.

Perl

If anything, Perl was easier because there wasn’t any danger of automatically flattening the arrays:

sub flipMatrix {
  my(@matrix) = @_;
  foreach my $subarray ( @matrix ) {
    $subarray = [ map { abs($_ - 1) } reverse @$subarray ];
  }
  return @matrix;
}

View the entire Perl script for this task on GitHub.

Python

Python was slightly trickier because I wanted to modify the matrix while I was looping over it, and the way to do that is to access the array via indices:

def flipMatrix(matrix):
    for index in range(0, len(matrix)):
        matrix[index] = map(
            lambda i: abs(i - 1), reversed(matrix[index])
        )
    return matrix

View the entire Python script for this task on GitHub.


Here’s all my solutions in GItHub: https://github.com/packy/perlweeklychallenge-club/tree/master/challenge-242/packy-anderson