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

Perl Weekly Challenge: Building Acronym Arrays

This Perl Weekly Challenge didn’t lend itself to a song. Drat.

My learning Elixir is going to have to wait a few weeks; I’ve added some theatrical tech work to my evenings, so until I’m done with that, I’m just writing these solutions in the languages I know well.

Task 1: Acronym

You are given an array of strings and a check string.

Write a script to find out if the check string is the acronym of the words in the given array.

Example 1

Input: @str = ("Perl", "Python", "Pascal")
       $chk = "ppp"
Output: true

Example 2

Input: @str = ("Perl", "Raku")
       $chk = "rp"
Output: false

Example 3

Input: @str = ("Oracle", "Awk", "C")
       $chk = "oac"
Output: true

Approach

Pretty straightforward: go through each of the items in @str, grab the first character, append it to a string, then lowercase the result and compare it to the value of $chk. Easy-peasy.

Raku

Once again, I wanted to use Raku’s Reduction Metaoperator[ ], because it’s just so neat… but at first I couldn’t think of a way to get it to append the first character from each element without it just becoming the first character of the first and last elements. So I did this:

sub makeAcronym(@str) {
  my $acronym;
  for @str -> $s {
    $acronym ~= substr($s, 0, 1);
  }
  return $acronym.lc;
}

But then I was reading more that the reduction metaoperator gives the same result as the reduce routine, so I went and read up on that. And I saw the following:

When the list contains no elements, an exception is thrown, unless &with is an operator with a known identity value (e.g., the identity value of infix:<+> is 0). For this reason, you’re advised to prefix the input list with an initial value (or explicit identity value):

my \strings = "One good string!", "And one another good string!";
say reduce { $^a ~ $^b }, '', |strings;               # like strings.join 
 
my \numbers = 1, 2, 3, 4, 5;
say reduce { $^a > $^b ?? $^a !! $^b }, 0, |numbers; # like numbers.max 
 
sub count-and-sum-evens( (Int \count, Int \sum), Int \x ) {
    x %% 2 ?? (count+1, sum+x) !! (count, sum)
}
 
say reduce &count-and-sum-evens, (0, 0), |numbers;    # OUTPUT: «(2 6)␤»

Well, that made me think: if I reduced { $^a ~ substr($^b, 0, 1) } and made an empty string the first element of the list I was reducing, then the first time through the reduction my $^a string would be an empty string, and each subsequent time it would be the entirety of the acronym I was building. This got me to a new version:

sub makeAcronym(@str) {
  my $acronym = reduce { $^a ~ substr($^b, 0, 1) },
      ('', |@str);
  return $acronym.lc;
}

The |@str part took me a little while to grok: because ('', @str) winds up being represented internally as ['', [ 'one', 'two', 'three' ] ], I needed some way to flatten @str when it was being added to the list. I thought ('', @str.flat) would do what I wanted, but it didn’t. It turns out that if I was going to use .flat I needed to use ('', @str).flat. But then I realized it was staring me in the face in the documentation: |. This is a quick way to do a Slip:

Sometimes you want to insert the elements of a list into another list. This can be done with a special type of list called a Slip.

Another way to make a Slip is with the | prefix operator. Note that this has a tighter precedence than the comma, so it only affects a single value, but unlike the above options, it will break Scalars.

say (1, |(2, 3), 4) eqv (1, 2, 3, 4);        # OUTPUT: «True␤» 
say (1, |$(2, 3), 4) eqv (1, 2, 3, 4);       # OUTPUT: «True␤» 
say (1, slip($(2, 3)), 4) eqv (1, 2, 3, 4);  # OUTPUT: «True␤»

I was being thrown by the sample code in the reduce routine documentation because it didn’t use a @ sigil for its arrays.

Since I’d gotten it to work with reduce, could I get it to work with a reduction metaoperator? Sure. But because it wasn’t a single operator, I would need to wrap a call to a function instead:

sub firstOfSecond { $^a ~ substr($^b, 0, 1) };

sub makeAcronym(@str) {
  my $acronym = [[&firstOfSecond]] ('', |@str);
  return $acronym.lc;
}

I like that solution a lot. View the entire Raku script for this task on GitHub.

Perl

It turns out my Perl-fu is still greater than my Raku-fu, because turning the Raku code into Perl code just fell from my fingers:

use List::Util qw( reduce );

sub makeAcronym {
  my $str = shift;
  my $acronym = reduce { $a . substr($b, 0, 1) } '', @$str;
  return lc($acronym);
}

Mostly, it’s Perl’s proclivity to generate lists from scalars or arrays separated by commas. I didn’t have to do anything fancy to make sure that '', @$str was flattened to a list of scalars.
View the entire Perl script on GitHub.

Python

Because last week I was using functools.reduce, it was fresh on my mind.

from functools import reduce

def makeAcronym(str_list):
    # add empty string to beginning of list
    str_list = [''] + str_list
    acronym = reduce(lambda a, b: a + b[0], str_list)
    return acronym.lower()

View the entire Python script for this task on GitHub.


Task 2: Build Array

You are given an array of integers.

Write a script to create an array such that new[i] = old[old[i]] where 0 <= i < new.length.

Example 1

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

Example 2

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

Approach

This one feels weird. I think its the old[old[i]] construction that makes it a little confusing. I’m hoping that given the array old I’ll just be able to use old[old[i]] as I loop through the indices of the array and it will just produce the desired output.

I also don’t know why it specifies new.length instead of old.length, because if we’re building the array new, we don’t really know the length of the array before we start, and if we tried to have i >= old.length we’d get an out-of-bounds error (whether it was caught or not) when we tried to access old[i]. Oh, well.

Raku

sub buildArray(@old) {
  my @new;
  for 0 .. @old.elems - 1 -> $i {
    @new.push(@old[@old[$i]]);
  }
  return @new;
}

And it does produce the desired output. View the entire Raku script for this task on GitHub.

Perl

sub buildArray(@old) {
  my @new;
  foreach my $i (0 .. $#old) {
    push @new, $old[$old[$i]];
  }
  return @new;
}

One thing I like about Perl over Raku is being able to say $#old to get the index of the last element of an array instead of @old.elems - 1. View the entire Perl script on GitHub.

Python

def buildArray(old):
    new = []
    for i in range(0, len(old)):
        new.append(old[old[i]])
    return new

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-240/packy-anderson

Perl Weekly Challenge: Now it’s the same old string, but with consistency since you’ve been gone…

This week’s Perl Weekly Challenge tasks reminded me of It’s the Same Old Song.

I want to add Elixir to the guest languages I’m implementing these in, but it’s a busy week, due to the new job and theatrical tech evenings. Maybe next week.

Task 1: Same String

You are given two arrays of strings.

Write a script to find out if the word created by concatenating the array elements is the same.

Example 1

Input: @arr1 = ("ab", "c")
       @arr2 = ("a", "bc")
Output: true

Using @arr1, word1 => "ab" . "c" => "abc"
Using @arr2, word2 => "a" . "bc" => "abc"

Example 2

Input: @arr1 = ("ab", "c")
       @arr2 = ("ac", "b")
Output: false

Using @arr1, word1 => "ab" . "c" => "abc"
Using @arr2, word2 => "ac" . "b" => "acb"

Example 3

Input: @arr1 = ("ab", "cd", "e")
       @arr2 = ("abcde")
Output: true

Using @arr1, word1 => "ab" . "cd" . "e" => "abcde"
Using @arr2, word2 => "abcde"

Approach

This is a pretty straightforward task: accept two arrays of strings, concatenate each of the arrays until they produce a single string, and then compare whether the strings are the same. In fact, this reminds me of Raku’s Reduction Metaoperator[ ], so I think I’ll do that code first:

Raku

sub sameString(@arr1, @arr2) {
  my $words = "";
  my $word1 = [~] @arr1;
  $words ~= "\n" ~ 'Using @arr1, word1 => "'
         ~ @arr1.join('" . "')
         ~ '" => "' ~ $word1 ~ '"';
  my $word2 = [~] @arr2;
  $words ~= "\n" ~ 'Using @arr2, word2 => "'
         ~ @arr2.join("' . '")
         ~  '" => "' ~ $word2 ~ '"';
  my $same = $word1 eq $word2;
  return($same, $words);
}

Ugh. That’s too much repeated code.

sub concatString($num, @arr) {
  my $word = [~] @arr;
  my $words = "\nUsing \@arr$num, word$num => \""
            ~ @arr.join('" . "')
            ~ '" => "' ~ $word ~ '"';
  return ($word, $words);
}

sub sameString(@arr1, @arr2) {
  my ($word1, $words1) = concatString(1, @arr1);
  my ($word2, $words2) = concatString(2, @arr2);
  return($word1 eq $word2, $words1 ~ $words2);
}

There. Much better. View the entire Raku script for this task on GitHub.

Perl

Really, the only changes I need to make to back-port the Raku to Perl is

  • Use List::Util::reduce to replace Raku’s reduction metaoperator
  • Use array references instead of array objects
  • Swap Raku’s ~ string concatenation operator for Perl’s . string concatenation operator
use List::Util qw( reduce );

sub concatString {
  my($num, $arr) = @_;
  my $word = reduce { $a . $b } @$arr;
  my $words = "\nUsing \@arr$num, word$num => \""
            . join('" . "', @$arr)
            . '" => "' . $word . '"';
  return ($word, $words);
}

sub sameString {
  my ($arr1, $arr2) = @_;
  my ($word1, $words1) = concatString(1, $arr1);
  my ($word2, $words2) = concatString(2, $arr2);
  return($word1 eq $word2, $words1 . $words2);
}

And that’s it. As you can see on on GitHub, the rest of the script is identical.

Python

Similarly, Python has a functools.reduce function:

from functools import reduce

def concatString(num, arr):
    word = reduce(lambda a, b: a + b, arr)
    words = (
      f'\nUsing @arr{num}, word{num} => "' +
      "' . '".join(arr) +
      f'" => "{word}"'
    )
    return word, words

def sameString(arr1, arr2):
    word1, words1 = concatString(1, arr1)
    word2, words2 = concatString(2, arr2)
    return(word1 == word2, words1 + words2)

View the entire Python script for this task on GitHub.


Task 2: Consistent Strings

You are given an array of strings and allowed string having distinct characters.

A string is consistent if all characters in the string appear in the string allowed.

Write a script to return the number of consistent strings in the given array.

Example 1

Input: @str = ("ad", "bd", "aaab", "baa", "badab")
       $allowed = "ab"
Output: 2

Strings "aaab" and "baa" are consistent since they only contain characters 'a' and 'b'.

Example 2

Input: @str = ("a", "b", "c", "ab", "ac", "bc", "abc")
       $allowed = "abc"
Output: 7

Example 3

Input: @str = ("cc", "acd", "b", "ba", "bac", "bad", "ac", "d")
       $allowed = "cad"
Output: 4

Strings "cc", "acd", "ac", and "d" are consistent.

Approach

I’m not reproducing the explanatory text this time. So, we’re given a list of characters that are allowed, and we need to count the strings in an array that only use those characters. This feels to me a lot like regular expression character classes! The “allowed” parameter defines the characters in the class, and we count the strings in @str that consist entirely of those characters.

Raku

sub consistentCount($allowed, @str) {
  my $regex = '^ <[' ~ $allowed ~ ']>+ $';
  my $count = 0;
  for @str -> $s {
    $count++ if $s.match: / <$regex> /;
  }
  return $count;
}

View the entire Raku script for this task on GitHub.

Perl

Again, the only changes I need to make to back-port the Raku to Perl is

  • Switch to Perl’s regular expression format and matching operators
  • Use array references instead of array objects
  • Swap Raku’s ~ string concatenation operator for Perl’s . string concatenation operator
sub consistentCount {
  my($allowed, $str) = @_;
  my $regex = '^[' . $allowed . ']+$';
  my $count = 0;
  foreach my $s ( @$str ) {
    $count++ if $s =~ /$regex/;
  }
  return $count;
}

Again, that’s it. As you can see on on GitHub, the rest of the script is identical.

Python

For Python, we need to import the regular expression library, but the syntax of the regular expression itself is the same as Perl.

import re

def consistentCount(allowed, str):
    regex = re.compile('^[' + allowed + ']+$')
    count = 0
    for s in str:
        if regex.match(s):
            count += 1
    return count

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-239/packy-anderson

Perl Weekly Challenge: Be Runnin’ Up That Sum, Be Persisten’ Up That Sort

Yes, yes, after last week, my brain wants to hear music based on the Perl Weekly Challenge. Because I’m starting a new job this week (yay!), I’m going to hold off on the Java solution and blog about it later. I want to keep up with the process of learning Java by adding it to the “guest languages” I do PWC solutions in, but I don’t want to delay getting my primary languages (Perl, Raku, & Python) submitted because I’ve got new-job tasks to accomplish.

Though my new job does have code in a language that’s new to me—Elixir. Since Elixir’s a functional language like Lisp, I want to learn it so I can better understand how my Elixir-coding coworkers think, so that’s going to be the next guest language I add to the PWC.

Task 1: Running Sum

You are given an array of integers.

Write a script to return the running sum of the given array. The running sum can be calculated as sum[i] = num[0] + num[1] + …. + num[i].

Example 1

Input: @int = (1, 2, 3, 4, 5)
Output: (1, 3, 6, 10, 15)

Example 2

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

Example 3

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

Once again, there’s an easier way to calculate the values being asked for than the way problem is defined. See, the way the problem is defined, it would appear that the way to generate the output array would be like this:

for (my $i = 0; $i <= $#int; $i++) {
  # sum the 0th through $i-th numbers in @int
  my $sum = $int[0];
  for (my $j = 1; $j <= $i; $j++) {
    $sum += $int[$j];
  }
  $sums[$i] = $sum;
}

Or, if we were savvy about modules, we would use sum out of List::Util and an array slice to avoid the inner loop:

use List::Util qw(sum);
for (my $i = 0; $i <= $#int; $i++) {
  # sum the 0th through $i-th numbers in @int
  $sums[$i] = sum @int[0 .. $i];
}

But… note that each new number in the output array is obtained by adding the next number in the input array to the last number in the output array. So we don’t need more than one loop through the input array:

sub runningSum {
  my @int = @_;
  my @sums;
  my $running_sum = 0;
  foreach my $num ( @int ) {
    # add the next number to the sum of numbers before it
    $running_sum += $num;
    # add the current running sum to the output array
    push @sums, $running_sum;
  }
  return @sums;
}

View the entire Perl script for this task on GitHub.


On to the Raku solution, which really shows how closely related Perl and Raku are. The only changes I made were accepting the @int array as a defined parameter, the syntax of the for loop, and using the .push() method on @sums instead of push @sums, $running_sum;.

sub runningSum(*@int) {
  my @sums;
  my $running_sum = 0;
  for @int -> $num {
    # add the next number to the sum of numbers before it
    $running_sum += $num;
    # add the current running sum to the output array
    @sums.push( $running_sum );
  }
  return @sums;
}

View the entire Raku script for this task on GitHub.


In much the same way, Python didn’t require much changes from the Raku version besides its silly syntax differences:

def runningSum(int):
    sums = []
    running_sum = 0
    for num in int:
        # add the next number to the sum of numbers before it
        running_sum += num
        # add the current running sum to the output array
        sums.append( running_sum )
    return sums

Really, I chalk this up to Python (even though it’s based off ABC) liberally borrowing syntax ideas from C-family programming languages. Which might be why I picked up Python so quickly: it feels really familiar.

View the entire Python script for this task on GitHub.


Task 2: Persistence Sort

You are given an array of positive integers.

Write a script to sort the given array in increasing order with respect to the count of steps required to obtain a single-digit number by multiplying its digits recursively for each array element. If any two numbers have the same count of steps, then print the smaller number first.

Example 1

Input: @int = (15, 99, 1, 34)
Output: (1, 15, 34, 99)

15 => 1 x 5 => 5 (1 step)
99 => 9 x 9 => 81 => 8 x 1 => 8 (2 steps)
1  => 0 step
34 => 3 x 4 => 12 => 1 x 2 => 2 (2 steps)

Example 2

Input: @int = (50, 25, 33, 22)
Output: (22, 33, 50, 25)

50 => 5 x 0 => 0 (1 step)
25 => 2 x 5 => 10 => 1 x 0 => 0 (2 steps)
33 => 3 x 3 => 9 (1 step)
22 => 2 x 2 => 4 (1 step)

This time, we want to be able to multiply all the digits in a number, and product in List::Util will be very useful!

my $step_count = 0;
while ( length($num) > 1 ) {
  # split $num into its individual digits
  my @digits = split //, $num;
  # generate a new number by multiplying all the digits
  $num = product @digits;
  # add to our count of steps
  $step_count++;
}

But since we need to do this for each number in our input array and then later sort based on the results, let’s make the step count a hash indexed on the number. Note that because $num is an alias to the actual value in the @int array, we should make a copy of $num so we’re not modifying the values in @int.

my %step_count;
foreach my $num ( @int ) {
  $step_count{$num} = 0;
  my $num_copy = $num; # copy the num so we can modify it
  while ( length($num_copy) > 1 ) {
    # split $num_copy into its individual digits
    my @digits = split //, $num_copy;

    # generate a new number by multiplying all the digits
    $num_copy = product @digits;

    # add to our count of steps
    $step_count{$num}++;
  }
}

Once we have a %step_count hash containing how many steps were needed for each number, sorting the input array as requested becomes trivial:

my @sorted = sort {
  # sort by step count
  $step_count{$a} <=> $step_count{$b}
  ||
  # then sort numerically
  $a <=> $b
} @int;

Once I added code to generate the verbose step explanations seen in the examples, we have the following :

use List::Util qw( product );

sub persistenceSort {
  my @int = @_;
  my %step_count;
  my $steps;
  # first, calculates the steps for each number
  foreach my $num ( @int ) {
    $step_count{$num} = 0;

    $steps .= "\n$num"; # our starting number

    my $num_copy = $num; # copy the num so we can modify it

    while ( length($num_copy) > 1 ) {
      # split $num_copy into its individual digits
      my @digits = split //, $num_copy;

      # generate a new number by multiplying all the digits
      $num_copy = product @digits;

      # show the multiplication in the steps for this num
      $steps .= ' => ' . join(' x ', @digits);
      $steps .= " => $num_copy";

      # add to our count of steps
      $step_count{$num}++;
    }

    # put the step count in the steps for this num
    $steps .=
      sprintf " (%d step%s)", $step_count{$num},
              $step_count{$num} == 1 ? '' : 's';
  }

  # now, sort by steps/numeric value
  my @sorted = sort {
    # sort by step count
    $step_count{$a} <=> $step_count{$b}
    ||
    # then sort numerically
    $a <=> $b
  } @int;

  return \@sorted, $steps;
}

View the entire Perl script for this task on GitHub.


With Raku, there are a few differences:

  • I didn’t need to pull in a module to get the product of the digits, because that’s something built into the language with Raku’s Reduction Metaoperator[ ]. So, what in Perl was $num_copy = List::Util::product @digits; became $num_copy = [*] @digits; in Raku.
  • When counting the number of digits in $num_copy, we need to pass it through the Int class’ .Str method to get a string representation of the number.
  • Perl’s length() function becomes Raku’s Str class’ .chars routine.
  • As I noted back in PWC 334, when splitting a string into its component characters, make sure you add the :skip-empty parameter, otherwise you’ll get leading and trailing empty character entries.
  • And one of the tricky things I’ve noticed is if you’re returning a List from a subroutine, if you try to capture it in a variable with the @ sigil, the list is assigned to the first element of the variable.
sub persistenceSort(*@int) {
  my %step_count;
  my $steps;
  # first, calculates the steps for each number
  for @int -> $num {
    %step_count{$num} = 0;

    $steps ~= "\n$num"; # our starting number

    my $num_copy = $num; # copy the num so we can modify it

    while ( $num_copy.Str.chars > 1 ) {
      # split $num_copy into its individual digits
      my @digits = $num_copy.split('', :skip-empty);

      # generate a new number by multiplying all the digits
      $num_copy = [*] @digits;

      # show the multiplication in the steps for this num
      $steps ~= ' => ' ~ @digits.join(' x ');
      $steps ~= " => $num_copy";

      # add to our count of steps
      %step_count{$num}++;
    }

    # put the step count in the steps for this num
    $steps ~=
      sprintf " (%d step%s)", %step_count{$num},
              %step_count{$num} == 1 ?? '' !! 's';
  }

  # now, sort by steps/numeric value
  my @sorted = @int.sort({
    # sort by step count
    %step_count{$^a} <=> %step_count{$^b}
    ||
    # then sort numerically
    $^a <=> $^b
  });

  return @sorted, $steps;
}

sub solution {
  my @int = @_;
  say 'Input: @int = (' ~ @int.join(', ') ~ ')';
  # if we attempt to capture the returned array in
  # @sorted, the array becomes the first ELEMENT in
  # @sorted (and the $steps Str becomes the second
  # element) so we capture it in $sorted
  my ($sorted, $steps) = persistenceSort(@int);
  say 'Output: (' ~ $sorted.join(', ') ~ ')';
  say $steps;
}

View the entire Raku script for this task on GitHub.


With Python, I initially encountered a problem because I had defined the input parameter to persistenceSort to be the variable int… but that shadowed the Python function int() that I needed to convert individual characters back to integers and caused TypeError: 'list' object is not callable when I tried to execute lambda a, b: int(a) * int(b). Quickly renaming the variable to int_list solved the problem.

Edit: Initially, I had a custom sort function to handle the multi-factor sorting, but then I was reading some more and I discovered that if the key function returned a tuple, Python would do what we want: sort by the first element of the tuple, but if the first elements are equal, sort by the second element of the tuple.

from functools import cmp_to_key, reduce

def persistenceSort(int_list):
    step_count = {}
    steps = ""

    # first, calculates the steps for each number
    for num in int_list:
        step_count[num] = 0

        steps += f"\n{num}" # our starting number

        num_copy = str(num) # copy the num so we can modify it

        while len(num_copy) > 1:
            # split num_copy into its individual digits
            digits = list(num_copy)

            # generate a new number by multiplying
            # all the digits
            num_copy = str(
                reduce(
                    lambda a, b: int(a) * int(b),
                    digits
                )
            )

            # show the multiplication in the steps for this num
            steps += ' => ' + ' x '.join(digits)
            steps += ' => ' + num_copy

            # add to our count of steps
            step_count[num] += 1

        # put the step count in the steps for this num
        step_word = 'step' if step_count[num] == 1 else 'steps'
        steps += f" ({step_count[num]} {step_word})"

    # now, sort by steps/numeric value
    sorted_list = sorted(int_list,
                         key=lambda x: (step_count[x], x))

    return sorted_list, steps

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-238/packy-anderson

Perl Weekly Challenge: Minute by minute, that’s how you win it!

Ok, it’s time for another Perl Weekly Challenge, but first a note about the blog title: when I first saw the name of the first task, my brain started singing the big number from Disney’s Newsies: The MusicalSeize The Day. Yes, I’m a Perl geek, but I’m also a theater geek.

So, onward with Perl Weekly Challenge 237!

Task 1: Seize The Day

Given a year, a month, a weekday of month, and a day of week (1 (Mon) .. 7 (Sun)), print the day.

Example 1

Input: Year = 2024, Month = 4, Weekday of month = 3, day of week = 2
Output: 16

The 3rd Tue of Apr 2024 is the 16th

Example 2

Input: Year = 2025, Month = 10, Weekday of month = 2, day of week = 4
Output: 9

The 2nd Thu of Oct 2025 is the 9th

Example 3

Input: Year = 2026, Month = 8, Weekday of month = 5, day of week = 3
Output: 0

There isn't a 5th Wed in Aug 2026

This reminds me a lot of challenge 227, which also involved manipulating dates: in that case, counting up the number of Friday the 13ths in a given year. For that challenge, I started at the first of the year, then found the first Friday, then rolled forward by a week and counted the number of times the day of the month was the 13th. This time, I’m starting at the first of the month that’s specified, finding the first occurrence of the specified day of the week, and then rolling forward by a week until I reach the desired weekday of the month or I roll off the end of the month.

Caveats:

  • The format of the examples is using a 0-Sunday-indexed day of the week (0 = Sun, 2 = Tue, 3 = Wed, 4 = Thu), but Time::Piece‘s wday method is 1-Sunday-indexed (1 = Sun, 3 = Tue, 4 = Wed, 5 = Thu)
  • If I want to print out the descriptive text, I probably want to use the ordinal method in Lingua::EN::Inflexion::Noun (I used the Lingua::EN::Inflexion module back in challenge 230).
# let's use the core modules for date manipulation
use Time::Piece;
use Time::Seconds qw( ONE_DAY );
use Lingua::EN::Inflexion qw( noun );

sub seizeTheDay {
  my %params = @_;
  # build the first day of the specified month
  my $start = $params{year} . '-' . $params{month} . '-01';
  # create an object for Jan 01 of the given year
  my $t = Time::Piece->strptime($start, "%Y-%m-%d")
                     ->truncate(to => 'day');
  # in Time::Piece->wday, 1 = Sun, 2 = Mon, 3 = Tue, but our
  # input is 0 = Sun, 1 = Mon, 2 = Tue, so adjust our input
  $params{day_of_week}++;

  # find the FIRST instance of the desired day of the week
  while ( $t->wday != $params{day_of_week} ) {
    $t += ONE_DAY; # add 1 day
  }

  # take note of some values that won't change
  # for our description
  my $year  = $t->year;
  my $month = $t->month;
  my $dow   = $t->wdayname;
  my $count = 1;

  my $ord_weekday_of_month = 
    noun($params{weekday_of_month})->ordinal(0);

  # now roll forward through the month until the desired
  # weekday of the month
  while (
    # we're still in the desired month
    $t->mon == $params{month}
    &&
    # we haven't reached the desired weekday of the month
    $count != $params{weekday_of_month}
  ) {
    # add a week to the date
    $t += ONE_DAY * 7;
    # add to the weekday of the month count
    $count++;
  }

  # if we rolled out of the month, return an error condition
  if ($t->mon != $params{month}) {
    return 0, "There isn't a $ord_weekday_of_month $dow "
            . "in $month $year";
  }
  else {
    # take note of what the day of the month is
    my $day = $t->day_of_month;
    my $ord_day_of_month = noun($day)->ordinal(0);
    return $day, "The $ord_weekday_of_month $dow "
               . "of $month $year is the $ord_day_of_month";
  }
}

View the entire Perl script for this task on GitHub.


In Raku, the built-in Date class is easier to deal with, and the output of the .day-of-week method matches the input we’re getting for the day of week, but…

  • There’s no built-in facility in Date to produce the English names for months and weekdays, so I loaded Tom Browder’s Date::Names module.
  • I needed a way to get ordinals from numbers, so I loaded Steve Schulze’s Lingua::EN::Numbers.
use Date::Names;
use Lingua::EN::Numbers; # for ordinal-digit()

sub seizeTheDay(
  Int :$year,
  Int :$month,
  Int :$weekday_of_month,
  Int :$day_of_week
) {
  # object for the first day of the specified month
  my $t = Date.new($year, $month, 1);

  # in Date.day-of-week, 0 = Sun, 1 = Mon, 2 = Tue,
  # which matches our input, so no adjustment is needed

  # find the FIRST instance of the desired day of the week
  while ( $t.day-of-week != $day_of_week ) {
    $t++; # add 1 day
  }

  # instantiate a Date::Names object
  my $dn = Date::Names.new;

  # take note of some values that won't change
  # for our description
  my $month_name = $dn.mon($t.month);
  my $dow        = $dn.dow($t.day-of-week);
  my $count      = 1;

  my $ord_weekday_of_month = ordinal-digit($weekday_of_month);

  # now roll forward through the month until the desired
  # weekday of the month
  while (
    # we're still in the desired month
    $t.month == $month
    &&
    # we haven't reached the desired weekday of the month
    $count < $weekday_of_month
  ) {
    # add a week to the date
    $t += 7;
    # add to the weekday of the month count
    $count++;
  }

  # if we rolled out of the month, return an error condition
  if ($t.month != $month) {
    return 0, "There isn't a $ord_weekday_of_month $dow "
            ~ "in $month $year";
  }
  else {
    # take note of what the day of the month is
    my $day = $t.day;
    my $ord_day_of_month = ordinal-digit($day);
    return $day, "The $ord_weekday_of_month $dow "
               ~ "of $month $year is the $ord_day_of_month";
  }
}

View the entire Raku script for this task in GitHub.


Python has a really robust datetime module, so of course I was going to use that, but I didn’t find anything in the standard library that generated ordinal numbers, so I went ahead and installed Savoir-faire Linux’s num2words module.

from datetime  import date, timedelta
from num2words import num2words

def seizeTheDay(year, month, weekday_of_month, day_of_week):
    """
    Function to determine, given a year, month, weekday of
    month and day of week, whether such a date exists and,
    if so, what day of the month it is.
    """
    # object for the first day of the specified month
    t = date(year, month, 1)

    # datetime.date.isoweekday returns 1 = Mon, 2 = Tue, etc.,
    # which matches our input, so no adjustment is needed

    # find the FIRST instance of the desired day of the week
    while ( t.isoweekday() != day_of_week ):
        t += timedelta(days = 1) # add 1 day

    # take note of some values that won't change
    # for our description
    month_name = t.strftime('%b')
    dow        = t.strftime('%a')
    count      = 1
  
    ord_weekday_of_month = num2words(
        weekday_of_month, to="ordinal_num"
    )

    # now roll forward through the month until the desired
    # weekday of the month
    while (
      # we're still in the desired month
      t.month == month
      and
      # we haven't reached the desired weekday of the month
      count < weekday_of_month
    ):
        # add a week to the date
        t += timedelta(days = 7)
        # add to the weekday of the month count
        count += 1

    # if we rolled out of the month, return an error condition
    if (t.month != month):
        return(
          0,
          f"There isn't a {ord_weekday_of_month} {dow} " +
          f"in {month_name} {year}"
        )
    else:
        # take note of what the day of the month is
        day = t.day
        ord_day_of_month = num2words(day, to="ordinal_num")
        return(
            day,
            f"The {ord_weekday_of_month} {dow} " +
            f"of {month_name} {year} is the {ord_day_of_month}"
        )

View the entire Python script in GitHub.


For the Java implementation, I decided to make seizeTheDay its own class, because I could then return an object that had attributes with the day and description in it. I had to search to learn how to do date manipulation, but once I found examples, it was pretty easy to grok. I couldn’t find a standard library to turn my numbers into ordinals, so I rolled my own.

I also learned that String.format() can accept C printf()-style format strings, not the weird positional ones I’d been using. That was a welcome discovery,

import java.util.Calendar;  
import java.text.SimpleDateFormat;

class seizeTheDay {
  public int    day;
  public String description;

  public seizeTheDay(
    int year,
    int month,
    int weekday_of_month,
    int day_of_week
  ) {
    // object for the first day of the specified month
    Calendar t = Calendar.getInstance();
    t.set(year, month - 1, 1);

    // find the FIRST instance of the desired day of the week
    while (t.get(Calendar.DAY_OF_WEEK) != day_of_week+1) {
      t.add(Calendar.DATE, 1);
    }

    String month_str =
      new SimpleDateFormat("MMM").format(t.getTime());
    String dow_str =
      new SimpleDateFormat("EEE").format(t.getTime());
    int count = 1;

    // now roll forward through the month until the desired
    // weekday of the month
    while (
      // we're still in the desired month
      t.get(Calendar.MONTH) == month - 1
      && 
      // we haven't reached the desired weekday of the month
      count < weekday_of_month
    ) {
      t.add(Calendar.DATE, 7);
      count++;
    }
    if (t.get(Calendar.MONTH) != month - 1) {
      this.day = 0;
      this.description = String.format(
        "There isn't a %s %s in %s %d",
        this.ord_suffix(weekday_of_month),
        dow_str,
        month_str,
        year
      );
    }
    else {
      this.day = t.get(Calendar.DATE);
      this.description = String.format(
        "The %s %s of %s %d is the %s",
        this.ord_suffix(weekday_of_month),
        dow_str,
        month_str,
        year,
        this.ord_suffix(this.day)
      );
    }
  }

  private String ord_suffix(int num) {
    // quick function to add an ordinal suffix
    // to a number
    if (num == 11 || num == 12 | num == 13) {
      return num + "th";
    }
    else {
      switch (num % 10) {
        case 1:  return num + "st";
        case 2:  return num + "nd";
        case 3:  return num + "rd";
        default: return num + "th";
      }
    }
  }
}

View the entire Java file in GitHub.


Task 2: Maximise Greatness

You are given an array of integers.

Write a script to permute the given array such that you get the maximum possible greatness.

To determine greatness, nums[i] < perm[i] where 0 <= i < nums.length

Example 1

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

One possible permutation: (2, 5, 1, 3, 3, 1, 1) which returns 4 greatness as below:
nums[0] < perm[0]
nums[1] < perm[1]
nums[3] < perm[3]
nums[4] < perm[4]

Example 2

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

One possible permutation: (2, 3, 4, 1) which returns 3 greatness as below:
nums[0] < perm[0]
nums[1] < perm[1]
nums[2] < perm[2]

This problem looks like there are multiple ways to solve it. The simplest way would be to just make all the possible permutations of the number array and count up the “greatness” of each. But for the first example with an array of 7 items, that means generating 5040 (7!) permutations and testing each of them. There has to be a better way.

I think there is: count up how many of each number we have, and then build a permutation item by item where either the item is the smallest remaining number larger than the current item, or the smallest remaining number. In fact, when we look at the example permutations, that’s exactly how they’re structured…

In example 1, we have the list 1, 3, 5, 2, 1, 3, 1, which has three 1s, one 2, two 3s, and one 5. So when building the permutation, we pair up one of the 2s with the first 1, the 5 with the first 3, then because there’s nothing greater than 5, we use one of the 1s, then one of the 3s with the 2, the remaining 3 with the next 1, and then we have to use the remaining 1s for the rest of the list.

In example 2, we have the list 1, 2, 3, 4, which has one each of 1, 2, 3, 4, and as we build the greatest permutation, each time we pair up the smallest possible number that’s greater than the current item: 2, 3, 4, and then finally the 1, because nothing in the list was greater than 4.

So, now to implement this in Perl. My first crack looked like this:

sub greatness {
  # determine the "greatness" of a permutation
  # relative to the original array; accepts two
  # array references to do the comparison
  my ($nums, $perm) = @_;
  my $greatness = 0;
  foreach my $i ( 0 .. $#$nums ) {
    $greatness++ if $nums->[$i] < $perm->[$i];
  }
  return $greatness;
}

sub removeNumFromCount {
  # since we do this in two locations below,
  # make this a subroutine we can call;
  # accept a reference to the count hash and
  # the number being removed
  my ($num_count, $num) = @_;

  # decrement the count of that number available
  $num_count->{$num}--;

  # if there are no more of that number, remove it
  # from the %num_count hash
  delete $num_count->{$num}
    if $num_count->{$num} == 0;
}

sub greatestPermutation {
  my @nums = @_;
  # first, count up how many of each num we have
  my %num_count;
  foreach my $num ( @nums ) {
    $num_count{$num}++;
  }

  # now, build a permutation that maximizes "greatness"
  my @perm;
  NUM: foreach my $num ( @nums ) {
    my @available = sort keys %num_count;
    my $smallest_available = $available[0];
    foreach my $avail ( @available ) {
      if ( $avail > $num ) {
        # push the available number onto the permutation
        push @perm, $avail;

        removeNumFromCount(\%num_count, $avail);

        # go to the next input number
        next NUM;
      }
    }
    # we didn't find an available number larger than $num,
    # so let's put the smallest available number on @perm
    push @perm, $smallest_available;

    removeNumFromCount(\%num_count, $smallest_available);
  }

  return @perm;
}

But I was getting bothered by repeating sort keys %num_count the once per item in the input array. Sorting is a somewhat expensive operation, and really, if we’ve sorted the list of available numbers once, we don’t need to sort it again—we just need to remove numbers that aren’t available anymore, and that can be accomplished with a simple scan of the array. So I modified removeNumFromCount to accept a second reference, this time to the array I’m storing the result of sort keys %num_count in:

sub removeNumFromCount {
  # since we do this in two locations below,
  # make this a subroutine we can call;
  # accept references to the count hash and
  # the list of available numbers, and the
  # number being removed
  my ($num_count, $available, $num) = @_;

  # decrement the count of that number available
  $num_count->{$num}--;

  # if there are no more of that number, remove it
  # from the %num_count hash and the @available array
  if ( $num_count->{$num} == 0 ) {
    # remove key from the hash
    delete $num_count->{$num};
    # filter array to not include $num
    @$available = grep { $_ != $num } @$available;
  }
}

sub greatestPermutation {
  my @nums = @_;
  # first, count up how many of each num we have
  my %num_count;
  foreach my $num ( @nums ) {
    $num_count{$num}++;
  }

  # now, build a permutation that maximizes "greatness"
  my @perm;
  my @available = sort keys %num_count; # do the sort once
  NUM: foreach my $num ( @nums ) {
    my $smallest_available = $available[0];
    foreach my $avail ( @available ) {
      if ( $avail > $num ) {
        # push the available number onto the permutation
        push @perm, $avail;

        removeNumFromCount(
          \%num_count, \@available, $avail
        );

        # go to the next input number
        next NUM;
      }
    }
    # we didn't find an available number larger than $num,
    # so let's put the smallest available number on @perm
    push @perm, $smallest_available;

    removeNumFromCount(
      \%num_count, \@available, $smallest_available
    );
  }

  return @perm;
}

View the entire Perl script for this task on GitHub.


The Raku version is pretty much exactly the same, except for the syntactical differences between Perl and Raku:

sub greatness(@nums, @perm) {
  # determine the "greatness" of a permutation
  # relative to the original array; accepts two
  # arrays to do the comparison
  my $greatness = 0;
  for 0 .. @nums.elems - 1 -> $i {
    $greatness++ if @nums[$i] < @perm[$i];
  }
  return $greatness;
}

sub removeNumFromCount(%num_count, @available, $num) {
  # since we do this in two locations below,
  # make this a subroutine we can call; accept
  # the count hash and the list of available
  # numbers, and the number being removed

  # decrement the count of that number available
  %num_count{$num}--;

  # if there are no more of that number, remove it
  # from the %num_count hash and the @available array
  if ( %num_count{$num} == 0 ) {
    # remove key from the hash
    %num_count{$num}:delete;
    # filter array to not include $num
    @available = @available.grep( { $_ != $num } );
  }
}

sub greatestPermutation(@nums) {
  # first, count up how many of each num we have
  my %num_count;
  for @nums -> $num {
    %num_count{$num}++;
  }

  # now, build a permutation that maximizes "greatness"
  my @perm;
  my @available = %num_count.keys().sort(); # do the sort once
  NUM: for @nums -> $num {
    my $smallest_available = @available[0];
    for @available -> $avail {
      if ( $avail > $num ) {
        # push the available number onto the permutation
        @perm.push($avail);

        removeNumFromCount(
          %num_count, @available, $avail
        );

        # go to the next input number
        next NUM;
      }
    }
    # we didn't find an available number larger than $num,
    # so let's put the smallest available number on @perm
    push @perm, $smallest_available;

    removeNumFromCount(
      %num_count, @available, $smallest_available
    );
  }

  return @perm;
}

View the entire Raku script for this task in GitHub.


When writing the Python version for this, it occurred to me that I don’t need to remove values from num_count when their count becomes 0 anymore: because I’m just removing values from available when their count drops to 0 instead of re-populating available by sorting the keys of num_count, I don’t care if there are keys in num_count with a 0 count anymore. I’m getting my next possible values for the permutation from available. I’ve gone back and made this change to the Perl and Raku versions I checked in, but I’m keeping how I originally wrote about them above.

def greatness(nums, perm):
    """
    Function to enumerate the greatness of
    the list perm relative to the list nums
    """
    greatness_num = 0
    for i in range(0, len(nums) - 1):
        if nums[i] < perm[i]:
            greatness_num += 1
    return greatness_num

def greatestPermutation(nums):
    """
    Function to generate a permutation of the list nums
    which has the largest relative "greatness" to nums
    """

    # first, count up how many of each num we have
    num_count = {}
    for num in nums:
        num_count[num] = num_count.get(num, 0) + 1

    # now, build a permutation that maximizes "greatness"
    perm = []
    available = sorted(num_count.keys()) # only sort once
    for num in nums:
        # default to the smallest available number
        num_to_add = available[0]

        # but now look for the smallest available number
        # that's GREATER than the current number
        for avail in available:
            if avail > num:
                num_to_add = avail
                break

        # add num_to_add to the permutation
        perm.append(num_to_add)

        # decrement its count in num_count
        num_count[num_to_add] -= 1

        # if there are no more, remove it from available
        if num_count[num_to_add] == 0:
            available = [
                x for x in available if x != num_to_add
            ]

    return perm

View the entire Python script in GitHub.


This Java solution was trickier because I wound up using a lot of three-term loops which I didn’t always get right.

  public static int greatness(int[] nums, int[] perm) {
    // determine the "greatness" of a permutation
    // relative to the original array; accepts two
    // arrays to do the comparison
    int greatness_num = 0;
    for (int i = 0; i < nums.length; i++) {
      if (nums[i] < perm[i]) {
        greatness_num++;
      }
    }
    return greatness_num;
  }

  public static int[] greatestPermutation(int[] nums) {
    // first, count up how many of each num we have
    HashMap<Integer, Integer> num_count =
      new HashMap<Integer, Integer>();
    for (int i = 0; i < nums.length; i++) {
      num_count.put(
        nums[i],
        num_count.getOrDefault(nums[i], 0) + 1
      );
    }

    // make a list of the available numbers
    // to put in a permutation
    List<Integer> available =
      new ArrayList<>(num_count.keySet());
    Collections.sort(available);

    // now, build a permutation that maximizes "greatness"
    List<Integer> perm = new ArrayList<>();
    for (Integer num : nums) {
      // default to the smallest available number
      int num_to_add = available.get(0);
      for (int i = 0; i < available.size(); i++) {
        int this_num = available.get(i);
        if (num < this_num) {
          num_to_add = this_num;
          break;
        }
      }
      perm.add(num_to_add);

      // decrement the count of that number available
      num_count.put(
        num_to_add,
        num_count.get(num_to_add) - 1
      );
  
      // if there are no more of that number, remove it
      // from available list
      if ( num_count.get(num_to_add) == 0 ) {
        // filter array to not include $num
        int size = available.size();
        for (int i = 1; i < size; i++) {
          int this_num = available.get(i);
          if (num_to_add == this_num) {
            available.remove(i);
            break;
          }
        }
      }
    }

    // because we built the permutations in a List,
    // convert the list to an int array for return
    int[] perm_return = new int[perm.size()];
    for (int i = 0; i < perm.size(); i++) {
      perm_return[i] = perm.get(i);
    }
    return perm_return;
  }

View the entire Java file in GitHub.


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

Perl Weekly Challenge: Exact Change and Array Loops

It’s time for the Perl Weekly Challenge 236!


Task 1: Exact Change

You are asked to sell juice each costs $5. You are given an array of bills. You can only sell ONE juice to each customer but make sure you return exact change back. You only have $5, $10 and $20 notes. You do not have any change in hand at first.

Write a script to find out if it is possible to sell to each customers with correct change.

Example 1

Input: @bills = (5, 5, 5, 10, 20)
Output: true

From the first 3 customers, we collect three $5 bills in order.
From the fourth customer, we collect a $10 bill and give back a $5.
From the fifth customer, we give a $10 bill and a $5 bill.
Since all customers got correct change, we output true.

Example 2

Input: @bills = (5, 5, 10, 10, 20)
Output: false

From the first two customers in order, we collect two $5 bills.
For the next two customers in order, we collect a $10 bill and give back a $5 bill.
For the last customer, we can not give the change of $15 back because we only have two $10 bills.
Since not every customer received the correct change, the answer is false.

Example 3

Input: @bills = (5, 5, 5, 20)
Output: true

Ok, how to attack this problem. I’m sure there’s a clever way to figure out whether or not the given assortment of bills can produce exact change, but as always, I’m going for straightforward and easy to understand over clever. A clever solution would be worth it if we needed performance.

So, the straightforward way is to keep a count of bills we take in and then return whether we can produce exact change. If at any point we don’t have the exact change on hand for a transaction, we can bail out of the function early and return false. If we get through all the transactions and are able to produce exact change for all of them, we return true. I’m going to use a hash because we’re tracking three bill amounts separated by a bit of space, and keeping those in a numerically indexed array would yield a bunch of empty elements I’d need to deal with.

sub isExactChangePossible {
  my @bills = @_;
  my %till; # we keep the bills in a "till"
  BILLS: foreach my $collected ( @bills ) {
    # put the bill we collected in the "till"
    $till{$collected}++;

    # calculate the required change
    my $change_required = $collected - 5;

    # if we don't need to make change,
    # skip to the next bill collected!
    next BILLS unless $change_required;

    # loop through the bills we have on hand
    # in descending size (try to make change
    # with the largest bills possible)
    foreach my $bill ( reverse sort { $a <=> $b } keys %till ) {

      # as long as we have more of this bill and
      # using it would not yield TOO MUCH change
      while ($till{$bill} > 0 && $change_required - $bill >= 0) {
        # deduct the amount from the required change
        $change_required -= $bill;

        # remove the bill from the till
        $till{$bill}--;
      }

      # move on if we managed to make exact change!
      next BILLS unless $change_required;
    }

    # if we weren't able to make change, fail
    return 0 if $change_required;
  }

  # we successfully made change for all transactions!
  return 1;
}

I’m just going to link to the full Perl script in GitHub.


The Raku version is almost identical:

sub isExactChangePossible(*@bills where ($_.all ~~ Int)) {
  my %till; # we keep the bills in a "till"
  BILLS: for @bills -> $collected {
    # put the bill we collected in the "till"
    %till{$collected}++;

    # calculate the required change
    my $change_required = $collected - 5;

    # if we don't need to make change,
    # skip to the next bill collected!
    next BILLS unless $change_required;

    # loop through the bills we have on hand
    for %till.keys().sort({ .Int }).reverse() -> $bill {
      # as long as we have more of this bill and
      # using it would not yield TOO MUCH change
      while (%till{$bill} > 0 && $change_required - $bill >= 0) {
        # deduct the amount from the required change
        $change_required -= $bill;

        # remove the bill from the till
        %till{$bill}--;
      }

      # move on if we managed to make exact change!
      next BILLS unless $change_required;
    }

    # if we weren't able to make change, fail
    return 0 if $change_required;
  }
  
  # we successfully made change for all transactions!
  return 1;
}

The one thing to note is that we can just say that the items being sorted are .Int and Raku will handle the comparison. Here’s the full Raku script in GitHub.


For Python, I had to tweak my logic a little to get around not being able to continue to the next iteration of the outer for bills loop from within the inner for till loop.

def isExactChangePossible(bills):
    till = {}; # we keep the bills in a "till"
    for collected in bills:
        # put the bill we collected in the "till"
        #
        # using .get(collected, 0) yields the value in the
        # dict for the key 'collected' if it exists, or the
        # specified default (in this case, 0) if it doesn't
        till[collected] = till.get(collected, 0) + 1

        # calculate the required change
        change_required = collected - 5

        # loop through the bills we have on hand
        for bill in sorted(till, reverse=True):
            # as long as we have more of this bill and
            # using it would not yield TOO MUCH change
            while till[bill] > 0 and change_required - bill >= 0:
                # deduct the amount from the required change
                change_required -= bill

                # remove the bill from the till
                till[bill] -= 1

        # if we weren't able to make change, fail
        if change_required:
            return 0
  
    # we successfully made change for all transactions!
    return 1

Here’s the full Python script in GitHub.


And now to the Java version. It’s slightly more annoying because Java Maps aren’t native to the language, but the approach works well:

  public static boolean isExactChangePossible(int[] bills) {
    // we keep the bills in a "till"
    HashMap<Integer, Integer> till =
      new HashMap<Integer, Integer>();

    for (int collected : bills) {
      // put the bill we collected in the "till"
      //
      // using .getOrDefault(collected, 0) yields the value
      // in the map for the key 'collected' if it exists, or
      // the specified default (in this case, 0) if it doesn't
      till.put(
        collected,
        till.getOrDefault(collected, 0) + 1
      );

      // calculate the required change
      int change_required = collected - 5;

      // loop through the bills we have on hand, making sure
      // we go from largest to smallest bill
      List<Integer> keys = new ArrayList<>(till.keySet());
      Collections.sort(keys, Collections.reverseOrder());
      for (Integer bill : keys) {
        // as long as we have more of this bill and
        // using it would not yield TOO MUCH change
        while (till.get(bill) > 0 &&
               change_required - bill >= 0) {
          // deduct the amount from the required change
          change_required -= bill;

          // remove the bill from the till
          till.put(bill, till.get(bill) - 1);
        }
      }
      // if we weren't able to make change, fail
      if (change_required > 0) {
          return false;
      }
    }
    return true;
  }

Here’s the full Java script in GitHub.


Task 2: Array Loops

You are given an array of unique integers.

Write a script to determine how many loops are in the given array.

To determine a loop: Start at an index and take the number at array[index] and then proceed to that index and continue this until you end up at the starting index.

Example 1

Input: @ints = (4,6,3,8,15,0,13,18,7,16,14,19,17,5,11,1,12,2,9,10)
Output: 3

To determine the 1st loop, start at index 0, the number at that index is 4, proceed to index 4, the number at that index is 15, proceed to index 15 and so on until you're back at index 0.

Loops are as below:
[4 15 1 6 13 5 0]
[3 8 7 18 9 16 12 17 2]
[14 11 19 10]

Example 2

Input: @ints = (0,1,13,7,6,8,10,11,2,14,16,4,12,9,17,5,3,18,15,19)
Output: 6

Loops are as below:
[0]
[1]
[13 9 14 17 18 15 5 8 2]
[7 11 4 6 10 16 3]
[12]
[19]

Example 3

Input: @ints = (9,8,3,11,5,7,13,19,12,4,14,10,18,2,16,1,0,15,6,17)
Output: 1

Loop is as below:
[9 4 5 7 19 17 15 1 8 12 18 6 13 2 3 11 10 14 16 0]

So to attack this we want to loop over each item in @ints and see if a loop starts at that element. Things I thought about:

  • I was worried about loops that don’t go back to the start, but one of the assumptions is that the list is unique integers, so we’re never going to have to worry about that.
  • Double counting loops: if we start at an element that’s in a loop we’ve already counted, we shouldn’t count it again.
  • When I wanted to pass multiple types of arguments to loopExistsAt(), I decided it would be a good opportunity to use the idea of having named parameters for a Per sub by passing it a hash.
sub loopExistsAt {
  my %params = @_;
  my $ints  = $params{ints};
  my $start = $params{start};
  my $seen  = $params{seen};

  # bail early if we're in a loop we've seen before
  return if exists $seen->{$start};

  my @loop;
  my $i = $start;
  for (;;) {
    # keep track of the values in the order we visit them
    push @loop, $ints->[$i];

    # track where we've already been
    # to avoid double-counting loops
    $seen->{$i} = 1;

    # get the next index
    $i = $ints->[$i];

    # make sure the index is in bounds
    last unless $i >= 0 && $i <= $#{$ints};

    # make sure we haven't seen the index before
    last if exists $seen->{$i};
  }

  # if the last element points back to
  # the start, it's a loop!
  if ($loop[-1] == $start) {
    return @loop;
  }
  # otherwise, return an empty list
  return;
}

sub identifyLoops {
  my @ints = @_;
  my @loops;
  my %seen; # keep track of indices we've seen
            # to avoid duplicating loops
  foreach my $start ( 0 .. $#ints ) {
    my @loop = loopExistsAt(
      start => $start,
      ints  => \@ints,
      seen  => \%seen
    );
    if (@loop) {
      push @loops, \@loop;
    }
  }
  return @loops;
}

Here’s the full Perl script in GitHub.


The Raku version wound up catching on bits of my Raku-newbie knowledge:

  • When I attempted to return nothing with just return;, what I wound up returning was an Any object. If I want to return an empty list, I need to return [];
  • I had to look up how to do named parameters in Raku.
sub loopExistsAt(:@ints, :$start, :%seen) {
  # bail early if we're in a loop we've seen before
  return [] if %seen{$start}:exists;

  my @loop;
  my $i = $start;
  loop (;;) {
    # keep track of the values in the order we visit them
    push @loop, @ints[$i];

    # track where we've already been
    # to avoid double-counting loops
    %seen{$i} = 1;

    # get the next index
    $i = @ints[$i];

    # make sure the index is in bounds
    last unless $i >= 0 && $i < @ints.elems;

    # make sure we haven't seen the index before
    last if %seen{$i}:exists;
  }

  # if the last element points back to
  # the start, it's a loop!
  if (@loop[*-1] == $start) {
    return @loop;
  }
  # otherwise, return an empty list
  return [];
}

sub identifyLoops {
  my @ints = @_;
  my @loops;
  my %seen; # keep track of indices we've seen
            # to avoid duplicating loops
  for 0 .. $@ints.elems - 1 -> $start {
    my @loop = loopExistsAt(
      start => $start,
      ints  => @ints,
      seen  => %seen
    );
    if (@loop) { 
      push @loops, @loop;
    }
  }
  return @loops;
}

Here’s the full Raku script in GitHub.


Python:

def loopExistsAt(ints=[], seen={}, start=0):
    # bail early if we're in a loop we've seen before
    if start in seen:
        return []

    loop = [] # initialize an empty list to start
    i = start # initialize i to starting point
    while True:
        # keep track of the values in the order we visit them
        loop.append(ints[i])

        # track where we've already been
        # to avoid double-counting loops
        seen[i] = 1

        # get the next index
        i = ints[i]

        # make sure the index is in bounds
        if i < 0 or i >= len(ints):
            break

        # make sure we haven't seen the index before
        if i in seen:
            break

    # if the last element points back to
    # the start, it's a loop!
    if loop[-1] == start:
        return loop

    # otherwise, return an empty list
    return []

def identifyLoops(ints):
    loops = []
    seen = {}; # keep track of indices we've seen
               # to avoid duplicating loops
    for start in range(0, len(ints)):
        loop = loopExistsAt(
          start = start,
          ints  = ints,
          seen  = seen
        )
        if loop:
            loops.append(loop)
    return loops

Here’s the full Python script in GitHub.


Java:

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.stream.Collectors;

public class Ch2 {
  public static ArrayList<Integer> loopExistsAt(
    int start, int[] ints, HashMap<Integer, Integer> seen
  ) {
    // bail early if we're in a loop we've seen before
    if (seen.get(start) != null) {
      // return an empty ArrayList
      return new ArrayList<Integer>();
    }

    // initialize an empty list to start
    ArrayList<Integer> loop = new ArrayList<Integer>();
    // initialize i to starting point
    int i = start;
    while (true) {
      // keep track of the values in the order we visit them
      loop.add(ints[i]);

      // track where we've already been
      // to avoid double-counting loops
      seen.put(i, 1);

      // get the next index
      i = ints[i];

      // make sure the index is in bounds
      if (i < 0 || i >= ints.length) {
        break;
      }

      // make sure we haven't seen the index before
      if (seen.get(i) != null) {
        break;
      }
    }

    // if the last element points back to
    // the start, it's a loop!
    if (loop.get(loop.size() - 1) == start) {
        return loop;
    }

    // otherwise, return an empty ArrayList
    return new ArrayList<Integer>();
  }

  public static ArrayList<ArrayList<Integer>> identifyLoops(int[] ints) {
    ArrayList<ArrayList<Integer>> loops =
      new ArrayList<ArrayList<Integer>>();
    HashMap<Integer, Integer> seen = 
      new HashMap<Integer, Integer>();

    for (int i = 0; i < ints.length; i++) {
      ArrayList<Integer> loop = loopExistsAt(i, ints, seen);
      if (loop.size() > 0) {
        loops.add(loop);
      }
    }
    return loops;
  }

  public static String comma_joined(int[] ints) {
    // we're using it more than once, make it a method
    return Arrays.stream(ints)
                 .mapToObj(String::valueOf)
                 .collect(Collectors.joining(","));
  }

  public static void solution(int[] ints) {
    System.out.println("Input: @ints = (" + comma_joined(ints) +
                       ")");
    ArrayList<ArrayList<Integer>> loops = identifyLoops(ints);
    int count = loops.size();
    System.out.println(String.format("Output: %1$d", count));
    if (count > 0) {
      String loop_noun = (count == 1) ? "Loop" : "Loops";
      String are_verb  = (count == 1) ? "is"   : "are";
      System.out.println("\n" + loop_noun + " " + are_verb +
                         " as below:");

      for (ArrayList<Integer> loop : loops) {
        String as_list = loop.stream()
                             .map(String::valueOf)
                             .collect(Collectors.joining(" "));
        System.out.println("[" + as_list + "]");
      }
    }
  }

  public static void main(String[] args) {
    System.out.println("Example 1:");
    solution(new int[] {4,6,3,8,15,0,13,18,7,16,14,
                        19,17,5,11,1,12,2,9,10});

    System.out.println("\nExample 2:");
    solution(new int[] {0,1,13,7,6,8,10,11,2,14,16,
                        4,12,9,17,5,3,18,15,19});

    System.out.println("\nExample 3:");
    solution(new int[] {9,8,3,11,5,7,13,19,12,4,14,
                        10,18,2,16,1,0,15,6,17});
  }
}

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