Perl Weekly Challenge: Oh, yes! I’m the great row column!

Perl Weekly Challenge 381‘s tasks are “Same Row Column” and “Smaller Greater Element”.

This week I honed in on “great” for the music choice, and Freddie Mercury’s version of The Great Pretender immediately jumped to mind.

Task 1: Same Row Column

You are given a n x n matrix containing integers from 1 to n.

Write a script to find if every row and every column contains all the integers from 1 to n.

Example 1

Input: @matrix = ([1, 2, 3, 4],
                  [2, 3, 4, 1],
                  [3, 4, 1, 2],
                  [4, 1, 2, 3],)
Output: true

Example 2

Input: @matrix = ([1])
Output: true

Example 3

Input: @matrix = ([1, 2, 5],
                  [5, 1, 2],
                  [2, 5, 1],)
Output: false

Elements are out of range 1..3.

Example 4

Input: @matrix = ([1, 2, 3],
                  [1, 2, 3],
                  [1, 2, 3],)
Output: false

Example 5

Input: @matrix = ([1, 2, 3],
                  [3, 1, 2],
                  [3, 2, 1],)
Output: false

Approach

Well, we’re looking at whether or not a given set of n integers has all the integers from 1..n. So we put the integers into a set-like structure (a hash or dict), and then test to see if the numbers from 1..n exist in the set.

Raku

This is the first solution I whipped up in Raku. First? Yeah, wait a moment—we’ll get back to that. Because I’m doing the check to see if the if the numbers from 1..n exist in the set in multiple places, I spun it out into its own function so I didn’t have to repeat the code. In the first loop, I check the columns to see if they have all the numbers, and I use an inner loop to build column arrays, so I can later, in a second loop, check those columns to see if they  have all the numbers.

sub hasAllNum($n, @elems) {
  my $set = set @elems;
  return False unless $set.elems == $n;
  for 1 .. $n -> $i {
    return False unless $i$set;
  }
  return True;
}

sub sameRowColumn(@matrix) {
  my $n = @matrix.elems;
  my @col;
  for 0 .. $n-1 -> $r {
    # check the row
    return False unless hasAllNum($n, @matrix[$r]);
    # build columns
    for 0 .. $n-1 -> $c {
      @col[$c].push(@matrix[$r][$c]);
    }
  }
  # check columns
  for 0 .. $n-1 -> $c {
    return False unless hasAllNum($n, @col[$c]);
  }
  return True;
}
$ raku/ch-1.raku
Example 1:
Input: @matrix = [
                   [1, 2, 3, 4],
                   [2, 3, 4, 1],
                   [3, 4, 1, 2],
                   [4, 1, 2, 3]
                 ]
Output: True

Example 2:
Input: @matrix = [
                   [1]
                 ]
Output: True

Example 3:
Input: @matrix = [
                   [1, 2, 5],
                   [5, 1, 2],
                   [2, 5, 1]
                 ]
Output: False

Example 4:
Input: @matrix = [
                   [1, 2, 3],
                   [1, 2, 3],
                   [1, 2, 3]
                 ]
Output: False

Example 5:
Input: @matrix = [
                   [1, 2, 3],
                   [3, 1, 2],
                   [3, 2, 1]
                 ]
Output: False

Python

There is a trick in Python, however, for getting the columns from a 2D matrix: use the zip() function. The zip function is supposed to be used to zip together multiple lists into tuples with an item from each list, but if you feed it an n x n matrix, you wind up with n tuples of n items each… effectively the columns of the matrix!

def has_all_num(n, elems):
  if len(elems) != n: return False # check num of elems
  for i in range(1, n+1):
    if i not in elems: return False
  return True

def same_row_column(matrix):
  n = len(matrix)
  for row in matrix: # check the row
    if not has_all_num(n, row): return False
  for col in zip(*matrix): # check columns
    if not has_all_num(n, col): return False
  return True

View the entire Python script for this task on GitHub.

Perl

And knowing that trick, I did the same thing in Perl, using List::AllUtils’ zip_by.

use List::AllUtils qw( zip_by );

sub hasAllNum($n, @elems) {
  my %set = map { $_ => 1 } @elems;
  return false unless (keys %set == $n); # check num of elems
  for my $i ( 1 .. $n ) {
    return false unless exists $set{$i};
  }
  return true;
}

sub sameRowColumn(@matrix) {
  my $n = @matrix;
  for my $r (0 .. $n-1) {
    # check the row
    return 'False' unless hasAllNum($n, @{$matrix[$r]});
  }
  my @col = zip_by { [ @_ ] } @matrix; # flip rows and col
  # check columns
  for my $c (0 .. $n-1) {
    return 'False' unless hasAllNum($n, @{$col[$c]});
  }
  return 'True';
}

View the entire Perl script for this task on GitHub.

Raku, take 2!

I kept coming back to the Raku solution, thinking there must be some way to transpose the matrix with zip and get the columns. The zip function didn’t work…

$ raku
Welcome to Rakudo™ v2026.01.
Implementing the Raku® Programming Language v6.d.
Built on MoarVM version 2026.01.

To exit type 'exit' or '^D'
[0] > my @matrix = [
  [1, 2, 3],
  [3, 1, 2],
  [3, 2, 1]
];
[[1 2 3] [3 1 2] [3 2 1]]
[1] > zip @matrix;
(([1 2 3] [3 1 2] [3 2 1]))

But using the Zip metaoperator as a reduction metaoperator like [Z] does work!

[2] > [Z] @matrix;
((1 3 3) (2 1 2) (3 2 1))

Hence, the improved Raku solution:

sub hasAllNum($n, @elems) {
  my $set = set @elems;
  return False unless $set.elems == $n; # check num of elems
  for 1 .. $n -> $i {
    return False unless $i$set;
  }
  return True;
}

sub sameRowColumn(@matrix) {
  my $n = @matrix.elems;
  for @matrix -> @row {
    # check the row
    return False unless hasAllNum($n, @row);
  }
  # check columns
  for [Z] @matrix -> @col {
    return False unless hasAllNum($n, @col);
  }
  return True;
}


View the entire Raku script for this task on GitHub.

Elixir

And we can do the same thing in Elixir, except that Elixir tuples have to be explicitly converted to lists (Tuple.to_list/1 on line 20) before we can pass it to has_all_num/2, which is expecting a list as its second parameter. I had been assigning the result of the Enum.reduce/3 on lines 14-16 to a variable result, and then passing that as the accumulator to the next Enum.reduce/3, but then I remembered Kernel.then/2 and decided I’m much rather pipe the result into the next reduce.

def has_all_num(n, elems) when length(elems) != n, do: false

def has_all_num(n, elems), do:
  Enum.reduce(1..n, true, fn i,result ->
    result and Enum.any?(elems, &(&1 == i))
  end)

def same_row_column(matrix) do
  n = length(matrix)
  # check rows
  Enum.reduce(matrix, true, fn row, result ->
    result and has_all_num(n, row)
  end)
  |> then(&(
    # check columns
    Enum.reduce(Enum.zip(matrix), &1, fn col, result ->
      result and has_all_num(n, Tuple.to_list(col))
    end)
  ))
end

View the entire Elixir script for this task on GitHub.


Task 2: Smaller Greater Element

You are given an array of integers.

Write a script to find the number of elements that have both a strictly smaller and greater element in the given array.

Example 1

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

Not enough elements in the array.

Example 2

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

Example 3

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

The elements are 4 and 8.

Example 4

Input: @int = (3, 6, 6, 9)
Output: 2

Both instances of 6.

Example 5

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

The elements are 0, -2, and 4.

Approach

I looked at this, and immediately thought of my favorite data structure, the Bag.

Put the integers into a bag. If there are fewer than three keys, we don’t have enough unique elements to meet the condition. Then… ooh, it just occurred to me that we’re only asked to determine the number of elements with both a strictly smaller and greater element. If we needed to determine what those elements were, we could discard the lowest and highest from the bag’s keys, and the remaining keys would be the elements that have both a strictly smaller and greater element. But since we only need the number, we just take the count of keys, subtract 2, and return that value.

And I did that, and got the wrong answer for example 4, because we need to count each of the 6s. So instead of taking the count of the keys, we discard the lowest and highest from the bag’s keys, and then take a sum of the values (the count of times those keys appear in the array).

Raku

Because I want the Bag to be mutable so I can drop the lowest and highest keys, I need to use a BagHash.

And one more thing I discovered: when I asked for the maximum value from the list of keys, I didn’t get the result I expected, because the keys were converted to Strings.

$ raku
Welcome to Rakudo™ v2026.01.
Implementing the Raku® Programming Language v6.d.
Built on MoarVM version 2026.01.

To exit type 'exit' or '^D'
[0] > ("8", "1", "4", "12").Seq.max
8
[1] > ("12", "1", "8", "4").Seq.max(:by({ $_.Int }))
12


So I had to figure out how to pass a function to the min and max functions. It turns out, I needed to pass a function using :by({ }) and I could use $_ in that function to cast the keys as Ints.

sub smallerGreater(@int) {
  my %bag = @int.BagHash;
  return 0 if %bag.elems < 3; # not enough unique elements
  %bag{ %bag.keys.min(:by({ $_.Int })) }:delete; # the smallest
  %bag{ %bag.keys.max(:by({ $_.Int })) }:delete; # the greatest
  %bag.values.sum;
}

View the entire Raku script for this task on GitHub.

$ raku/ch-2.raku
Example 1:
Input: @int = (2, 4)
Output: 0

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

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

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

Example 5:
Input: @int = (0, -5, 10, -2, 4)
Output: 3

Perl

Fortunately, having worked all the wrinkles in my approach out in Raku, the Perl implementation went smoothly. Once again, I’m using List::MoreUtil’s frequency to make the bag (hash), and List::AllUtils’ minmax_by to get the minimum and maximum values from the keys, passing them through the Perl idiom of adding 0 to a value to cast it as a numeric. Then I sum those values and return that.

use List::AllUtils qw( minmax_by sum );
use List::MoreUtils qw( frequency );

sub smallerGreater(@int) {
  my %bag = frequency @int;
  return 0 if keys %bag < 3; # not enough unique elements
  my ($min, $max) = minmax_by { $_ + 0 } keys %bag;
  delete %bag{ $min }; # the smallest
  delete %bag{ $max }; # the greatest
  sum values %bag;
}

View the entire Perl script for this task on GitHub.

Python

Python made this easy because it never re-cast the integers as strings, so I didn’t need to do anything special for the min/max functions, and the collections module’s Counter type has a .total() that returns the sum of all the values.

from collections import Counter

def smaller_greater(ints):
  bag = Counter(ints)
  keys = bag.keys()
  if len(keys) < 3: return 0 # not enough unique elements
  (min_k, max_k) = (min(keys), max(keys))
  del bag[ min_k ] # the smallest
  del bag[ max_k ] # the greatest
  return bag.total()

View the entire Python script for this task on GitHub.

Elixir

The fun think in the Elixir solution is that I got to use Map.drop/2 to drop the minimum and maximum keys from the bag map in a single statement. I broke out the logic into three functions smaller_greater, one of arity 1 to be called with just the list of integers, and two private functions of arity 2 to be called with the bag and the list of keys. Why pass the list of keys when I can just call Map.keys/1 to get the keys? Because passing the keys as a list allows me to use Kernel.length/1 as a guard to handle the case where we don’t have enough unique elements to have any elements matching the criteria.

# not enough unique elements
defp smaller_greater(_, keys) when length(keys) < 3, do: 0

defp smaller_greater(bag, keys) do
  {{min, max} = {Enum.min(keys), Enum.max(keys)}}
  # drop the smallest and the greatest
  Map.drop(bag, [min, max])
  |> Map.values |> Enum.sum
end

def smaller_greater(int) do
  bag = Enum.frequencies(int)
  smaller_greater(bag, Map.keys(bag))
end

View the entire Elixir script for this task on GitHub.


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

Leave a Reply