Perl Weekly Challenge: Small, but Oddly Current

With Perl Weekly Challenge 337 being Smaller Than Current and an Odd Matrix, I figured I’d dig into my collection and pull out a track called Odd Ones on the Kanno Yōko (菅野 よう子) Cowboy Bebop soundtrack mini-album, Vitaminless.

So with the music out of the way, let’s do some odd programming and knock off PWC337.

Task 1: Smaller Than Current

You are given an array of numbers, @num1.

Write a script to return an array, @num2, where $num2[i] is the count of all numbers less than or equal to $num1[i].

Example 1

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

index 0: numbers <= 6 are 5, 4    => 2
index 1: numbers <= 5 are 4       => 1
index 2: numbers <= 4, none       => 0
index 3: numbers <= 8 are 6, 5, 4 => 3

Example 2

Input: @num1 = (7, 7, 7, 7)
Output: (3, 3, 3, 3)

Example 3

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

Example 4

Input: @num1 = (-1, 0, 3, -2, 1)
Output: (1, 2, 4, 0, 3)

Example 5

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

Approach

There are multiple ways we could do this. Back in college many moons ago, I’d loop over the list, and inside the loop I would loop over the list again, skipping over the current number, and compare it to the current number, counting the ones that are less than or equal, like this:

count = array[4] of integer;
for i := 0 to 4 do
begin
  count[i] := 0;
  for j := 0 to 4 do
  begin
    if j != i and num[j] <= num[i] then
      count[i] := count[i] + 1;
  end;
end;

But lately I’ve been really enamored of Multisets. We can count how many instances of any particular number using a multiset, and then sum the values for keys less than or equal to the current value, and subtract 1 because there will always be at least 1 instance of the current key.

Raku

Part of me hoped that I could get this down to a one-liner, but I couldn’t make it happen. But I was able to get the meat of the work to happen on line 8. $bag.keys.grep({ $_ <= @num1[$i] }) produces a list of keys where the key is less than or equal to $i, and we then pass that back into $bag{ } to get a slice of the values of the map for those keys. Summing the values of these keys will include the current value, so we subtract 1 from the total to get the final count.

sub smallerThan(@num1) {
  my $bag = @num1.Bag;
  my @out;
  for 0..@num1.end -> $i {
    @out[$i] = $bag{ $bag.keys.grep({ $_ <= @num1[$i] }) }.sum - 1;
  }
  @out
}

View the entire Raku script for this task on GitHub.

$ raku/ch-1.raku
Example 1:
Input: @arr = (6, 5, 4, 8)
[2, 1, 0, 3]
Output: (2, 1, 0, 3)

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

Example 3:
Input: @arr = (5, 4, 3, 2, 1)
[4, 3, 2, 1, 0]
Output: (4, 3, 2, 1, 0)

Example 4:
Input: @arr = (-1, 0, 3, -2, 1)
[1, 2, 4, 0, 3]
Output: (1, 2, 4, 0, 3)

Example 5:
Input: @arr = (0, 1, 1, 2, 0)
[1, 3, 3, 4, 1]
Output: (1, 3, 3, 4, 1)

Perl

I like reading other people’s solutions to the challenge, and Matthias Muth has been doing wonderful solutions using List::MoreUtils’ frequency to essentially create bags/multisets.

I tried doing sum(values %bag{grep { $_ <= $num1[$i] } keys %bag }) - 1 like I had in line 8 of the Raku solution, but I found myself getting the error Type of arg 1 to values must be hash or array (not key/value hash slice), so I had to assign the hash slice to a temporary variable, %bag2.

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

sub smallerThan(@num1) {
  my %bag = frequency @num1;
  my @out;
  foreach my $i ( 0 .. $#num1 ) {
    my %bag2 = %bag{grep { $_ <= $num1[$i] } keys %bag };
    $out[$i] = sum(values %bag2) - 1;
  }
  @out
}

View the entire Perl script for this task on GitHub.

Python

I realized when I wrote the Python solution that I could have been using push in the Raku/Perl solutions to add the values to the output array.

def smaller_than(num1):
  bag = Counter(num1)
  out = []
  for i in range(len(num1)):
    out.append(
      sum(bag[key] for key in bag.keys() if key <= num1[i]) - 1
    )
  return out

View the entire Python script for this task on GitHub.

Elixir

With Elixir, we’re writing a recursive function smaller_than/3 to process each of the items in num1. We probably could have done it with nested Enum.map_reduce/3 statements, but I think this looks cleaner.

def smaller_than([], _, out), do: out

def smaller_than([i | rest], bag, out) do
  {_, count} = Enum.map_reduce(Map.keys(bag), 0,
    fn k, count ->
      cond do
        k <= i ->
          {k, count + Map.get(bag, k)}
        true ->
          {k, count}
      end
    end
  )
  smaller_than(rest, bag, out ++ [count - 1])
end

def smaller_than(num1) do
  bag = Enum.frequencies(num1)
  smaller_than(num1, bag, [])
end

View the entire Elixir script for this task on GitHub.


Task 2: Odd Matrix

You are given row and col, also a list of positions in the matrix.

Write a script to perform action on each location (0-indexed) as provided in the list and find out the total odd valued cells.

For each location (r, c), do both of the following:

a) Increment by 1 all the cells on row r.
b) Increment by 1 all the cells on column c.

Example 1

Input: $row = 2, $col = 3, @locations = ([0,1],[1,1])
Output: 6

Initial:
[ 0 0 0 ]
[ 0 0 0 ]

Apply [0,1]:
Increment row 0:
Before     After
[ 0 0 0 ]  [ 1 1 1 ]
[ 0 0 0 ]  [ 0 0 0 ]
Increment col 1:
Before     After
[ 1 1 1 ]  [ 1 2 1 ]
[ 0 0 0 ]  [ 0 1 0 ]

Apply [1,1]:
Increment row 1:
Before     After
[ 1 2 1 ]  [ 1 2 1 ]
[ 0 1 0 ]  [ 1 2 1 ]
Increment col 1:
Before     After
[ 1 2 1 ]  [ 1 3 1 ]
[ 1 2 1 ]  [ 1 3 1 ]

Final:
[ 1 3 1 ]
[ 1 3 1 ]

Example 2

Input: $row = 2, $col = 2, @locations = ([1,1],[0,0])
Output: 0

Initial:
[ 0 0 ]
[ 0 0 ]

Apply [1,1]:
Increment row 1:
Before    After
[ 0 0 ]   [ 0 0 ]
[ 0 0 ]   [ 1 1 ]
Increment col 1:
Before    After
[ 0 0 ]   [ 0 1 ]
[ 1 1 ]   [ 1 2 ]

Apply [0,0]:
Increment row 0:
Before    After
[ 0 1 ]   [ 1 2 ]
[ 1 2 ]   [ 1 2 ]
Increment col 0:
Before    After
[ 1 2 ]   [ 2 2 ]
[ 1 2 ]   [ 2 2 ]

Final:
[ 2 2 ]
[ 2 2 ]

Example 3

Input: $row = 3, $col = 3, @locations = ([0,0],[1,2],[2,1])
Output: 0

Initial:
[ 0 0 0 ]
[ 0 0 0 ]
[ 0 0 0 ]

Apply [0,0]:
Increment row 0:
Before     After
[ 0 0 0 ]  [ 1 1 1 ]
[ 0 0 0 ]  [ 0 0 0 ]
[ 0 0 0 ]  [ 0 0 0 ]
Increment col 0:
Before     After
[ 1 1 1 ]  [ 2 1 1 ]
[ 0 0 0 ]  [ 1 0 0 ]
[ 0 0 0 ]  [ 1 0 0 ]

Apply [1,2]:
Increment row 1:
Before     After
[ 2 1 1 ]  [ 2 1 1 ]
[ 1 0 0 ]  [ 2 1 1 ]
[ 1 0 0 ]  [ 1 0 0 ]
Increment col 2:
Before     After
[ 2 1 1 ]  [ 2 1 2 ]
[ 2 1 1 ]  [ 2 1 2 ]
[ 1 0 0 ]  [ 1 0 1 ]

Apply [2,1]:
Increment row 2:
Before     After
[ 2 1 2 ]  [ 2 1 2 ]
[ 2 1 2 ]  [ 2 1 2 ]
[ 1 0 1 ]  [ 2 1 2 ]
Increment col 1:
Before     After
[ 2 1 2 ]  [ 2 2 2 ]
[ 2 1 2 ]  [ 2 2 2 ]
[ 2 1 2 ]  [ 2 2 2 ]

Final:
[ 2 2 2 ]
[ 2 2 2 ]
[ 2 2 2 ]

Example 4

Input: $row = 1, $col = 5, @locations = ([0,2],[0,4])
Output: 2

Initial:
[ 0 0 0 0 0 ]

Apply [0,2]:
Increment row 0:
Before         After
[ 0 0 0 0 0 ]  [ 1 1 1 1 1 ]
Increment col 2:
Before         After
[ 1 1 1 1 1 ]  [ 1 1 2 1 1 ]

Apply [0,4]:
Increment row 0:
Before         After
[ 1 1 2 1 1 ]  [ 2 2 3 2 2 ]
Increment col 4:
Before         After
[ 2 2 3 2 2 ]  [ 2 2 3 2 3 ]

Final:
[ 2 2 3 2 3 ]

Example 5

Input: $row = 4, $col = 2, @locations = ([1,0],[3,1],[2,0],[0,1])
Output: 8

Initial:
[ 0 0 ]
[ 0 0 ]
[ 0 0 ]
[ 0 0 ]

Apply [1,0]:
Increment row 1:
Before     After
[ 0 0 ]    [ 0 0 ]
[ 0 0 ]    [ 1 1 ]
[ 0 0 ]    [ 0 0 ]
[ 0 0 ]    [ 0 0 ]
Increment col 0:
Before     After
[ 0 0 ]    [ 1 0 ]
[ 1 1 ]    [ 2 1 ]
[ 0 0 ]    [ 1 0 ]
[ 0 0 ]    [ 1 0 ]

Apply [3,1]:
Increment row 3:
Before     After
[ 1 0 ]    [ 1 0 ]
[ 2 1 ]    [ 2 1 ]
[ 1 0 ]    [ 1 0 ]
[ 1 0 ]    [ 2 1 ]
Increment col 1:
Before     After
[ 1 0 ]    [ 1 1 ]
[ 2 1 ]    [ 2 2 ]
[ 1 0 ]    [ 1 1 ]
[ 2 1 ]    [ 2 2 ]

Apply [2,0]:
Increment row 2:
Before     After
[ 1 1 ]    [ 1 1 ]
[ 2 2 ]    [ 2 2 ]
[ 1 1 ]    [ 2 2 ]
[ 2 2 ]    [ 2 2 ]
Increment col 0:
Before     After
[ 1 1 ]    [ 2 1 ]
[ 2 2 ]    [ 3 2 ]
[ 2 2 ]    [ 3 2 ]
[ 2 2 ]    [ 3 2 ]

Apply [0,1]:
Increment row 0:
Before     After
[ 2 1 ]    [ 3 2 ]
[ 3 2 ]    [ 3 2 ]
[ 3 2 ]    [ 3 2 ]
[ 3 2 ]    [ 3 2 ]
Increment col 1:
Before     After
[ 3 2 ]    [ 3 3 ]
[ 3 2 ]    [ 3 3 ]
[ 3 2 ]    [ 3 3 ]
[ 3 2 ]    [ 3 3 ]

Final:
[ 3 3 ]
[ 3 3 ]
[ 3 3 ]
[ 3 3 ]

Approach

The approach for this is fairly straightforward: we generate a 2D-array/matrix of with the number of columns/rows specified, and then we increment the rows/columns as listed in the @locations array. Most of the work is going to be reproducing the progress display.

Raku

This gave me a chance to show off some neat Raku features:

  • Zip metaoperator – applies a given infix operator to pairs taken one left, one right, from its arguments. The example shows it being used with the infix ~ string concatenation operator, which is exactly what I needed to do on line 6.
  • List repetition operator (xx) – returns a Sequence of $a repeated and evaluated $b times ($b is coerced to Int). On line 22 I’m using it to make columns for the empty matrix.
  • On line 49 I was writing for @locations -> @location and then using @location[0] and @location[1] for the row and column, but then I realized that I could replace @location with the list ($row, $col), and I could get much more readable code.
# take two displayMatrix() strings and display them side-by-side
sub displayTwo($display1, $display2) {
  # split the two strings and join each row together
  my @display = $display1.split(/\n/) Z~ $display2.split(/\n/);
  @display.join("\n")
}

sub displayMatrix($label, @matrix) {
  my $width = (@matrix[0].elems + 2) * 2 + 1;
  my $display = sprintf "%-*s\n", $width, $label;
  for 0 .. @matrix.end -> $r {
    my @row = @matrix[$r];
    $display ~= "[ " ~ @row.join(" ") ~ " ]  \n";
  }
  $display
}

sub emptyMatrix($row, $col) {
  my @matrix;
  for 1..$row -> $r { @matrix.push([ 0 xx $col ]); }
  @matrix;
}

sub incrementRow($row, @matrix is copy) {
  for 0..@matrix[0].end -> $col { @matrix[$row][$col]++; }
  @matrix
}

sub incrementCol($col, @matrix is copy) {
  for 0..@matrix.end -> $row { @matrix[$row][$col]++; }
  @matrix
}

sub countOdd(@matrix) {
  my $count = 0;
  for 0..@matrix.end -> $row {
    for 0..@matrix[0].end -> $col {
      $count++ if @matrix[$row][$col] % 2;
    }
  }
  $count
}

sub oddMatrix($row, $col, @locations) {
  my @matrix = emptyMatrix($row, $col);
  my $display = displayMatrix("Initial:", @matrix);
  for @locations -> ($row, $col) {
    $display ~= "\nApply [$row,$col]:\n";

    $display ~= "Increment row $row:\n";
    my $before = displayMatrix("Before", @matrix);
    @matrix = incrementRow($row, @matrix);
    my $after = displayMatrix("After", @matrix);
    $display ~= displayTwo($before, $after);

    $display ~= "Increment col $col:\n";
    $before = displayMatrix("Before", @matrix);
    @matrix = incrementCol($col, @matrix);
    $after = displayMatrix("After", @matrix);
    $display ~= displayTwo($before, $after);
  }
  $display ~= "\n";
  return
    countOdd(@matrix),
    $display ~ displayMatrix('Final:', @matrix);
}

View the entire Raku script for this task on GitHub.

$ raku/ch-2.raku
Example 1:
Input: $row = 2, $col = 3, @locations = ([0,1],[1,1])
Output: 6

Initial:
[ 0 0 0 ]
[ 0 0 0 ]

Apply [0,1]:
Increment row 0:
Before     After
[ 0 0 0 ]  [ 1 1 1 ]
[ 0 0 0 ]  [ 0 0 0 ]
Increment col 1:
Before     After
[ 1 1 1 ]  [ 1 2 1 ]
[ 0 0 0 ]  [ 0 1 0 ]

Apply [1,1]:
Increment row 1:
Before     After
[ 1 2 1 ]  [ 1 2 1 ]
[ 0 1 0 ]  [ 1 2 1 ]
Increment col 1:
Before     After
[ 1 2 1 ]  [ 1 3 1 ]
[ 1 2 1 ]  [ 1 3 1 ]

Final:
[ 1 3 1 ]
[ 1 3 1 ]


Example 2:
Input: $row = 2, $col = 2, @locations = ([1,1],[0,0])
Output: 0

Initial:
[ 0 0 ]
[ 0 0 ]

Apply [1,1]:
Increment row 1:
Before   After
[ 0 0 ]  [ 0 0 ]
[ 0 0 ]  [ 1 1 ]
Increment col 1:
Before   After
[ 0 0 ]  [ 0 1 ]
[ 1 1 ]  [ 1 2 ]

Apply [0,0]:
Increment row 0:
Before   After
[ 0 1 ]  [ 1 2 ]
[ 1 2 ]  [ 1 2 ]
Increment col 0:
Before   After
[ 1 2 ]  [ 2 2 ]
[ 1 2 ]  [ 2 2 ]

Final:
[ 2 2 ]
[ 2 2 ]


Example 3:
Input: $row = 3, $col = 3, @locations = ([0,0],[1,2],[2,1])
Output: 0

Initial:
[ 0 0 0 ]
[ 0 0 0 ]
[ 0 0 0 ]

Apply [0,0]:
Increment row 0:
Before     After
[ 0 0 0 ]  [ 1 1 1 ]
[ 0 0 0 ]  [ 0 0 0 ]
[ 0 0 0 ]  [ 0 0 0 ]
Increment col 0:
Before     After
[ 1 1 1 ]  [ 2 1 1 ]
[ 0 0 0 ]  [ 1 0 0 ]
[ 0 0 0 ]  [ 1 0 0 ]

Apply [1,2]:
Increment row 1:
Before     After
[ 2 1 1 ]  [ 2 1 1 ]
[ 1 0 0 ]  [ 2 1 1 ]
[ 1 0 0 ]  [ 1 0 0 ]
Increment col 2:
Before     After
[ 2 1 1 ]  [ 2 1 2 ]
[ 2 1 1 ]  [ 2 1 2 ]
[ 1 0 0 ]  [ 1 0 1 ]

Apply [2,1]:
Increment row 2:
Before     After
[ 2 1 2 ]  [ 2 1 2 ]
[ 2 1 2 ]  [ 2 1 2 ]
[ 1 0 1 ]  [ 2 1 2 ]
Increment col 1:
Before     After
[ 2 1 2 ]  [ 2 2 2 ]
[ 2 1 2 ]  [ 2 2 2 ]
[ 2 1 2 ]  [ 2 2 2 ]

Final:
[ 2 2 2 ]
[ 2 2 2 ]
[ 2 2 2 ]


Example 4:
Input: $row = 1, $col = 5, @locations = ([0,2],[0,4])
Output: 2

Initial:
[ 0 0 0 0 0 ]

Apply [0,2]:
Increment row 0:
Before         After
[ 0 0 0 0 0 ]  [ 1 1 1 1 1 ]
Increment col 2:
Before         After
[ 1 1 1 1 1 ]  [ 1 1 2 1 1 ]

Apply [0,4]:
Increment row 0:
Before         After
[ 1 1 2 1 1 ]  [ 2 2 3 2 2 ]
Increment col 4:
Before         After
[ 2 2 3 2 2 ]  [ 2 2 3 2 3 ]

Final:
[ 2 2 3 2 3 ]


Example 5:
Input: $row = 4, $col = 2, @locations = ([1,0],[3,1],[2,0],[0,1])
Output: 8

Initial:
[ 0 0 ]
[ 0 0 ]
[ 0 0 ]
[ 0 0 ]

Apply [1,0]:
Increment row 1:
Before   After
[ 0 0 ]  [ 0 0 ]
[ 0 0 ]  [ 1 1 ]
[ 0 0 ]  [ 0 0 ]
[ 0 0 ]  [ 0 0 ]
Increment col 0:
Before   After
[ 0 0 ]  [ 1 0 ]
[ 1 1 ]  [ 2 1 ]
[ 0 0 ]  [ 1 0 ]
[ 0 0 ]  [ 1 0 ]

Apply [3,1]:
Increment row 3:
Before   After
[ 1 0 ]  [ 1 0 ]
[ 2 1 ]  [ 2 1 ]
[ 1 0 ]  [ 1 0 ]
[ 1 0 ]  [ 2 1 ]
Increment col 1:
Before   After
[ 1 0 ]  [ 1 1 ]
[ 2 1 ]  [ 2 2 ]
[ 1 0 ]  [ 1 1 ]
[ 2 1 ]  [ 2 2 ]

Apply [2,0]:
Increment row 2:
Before   After
[ 1 1 ]  [ 1 1 ]
[ 2 2 ]  [ 2 2 ]
[ 1 1 ]  [ 2 2 ]
[ 2 2 ]  [ 2 2 ]
Increment col 0:
Before   After
[ 1 1 ]  [ 2 1 ]
[ 2 2 ]  [ 3 2 ]
[ 2 2 ]  [ 3 2 ]
[ 2 2 ]  [ 3 2 ]

Apply [0,1]:
Increment row 0:
Before   After
[ 2 1 ]  [ 3 2 ]
[ 3 2 ]  [ 3 2 ]
[ 3 2 ]  [ 3 2 ]
[ 3 2 ]  [ 3 2 ]
Increment col 1:
Before   After
[ 3 2 ]  [ 3 3 ]
[ 3 2 ]  [ 3 3 ]
[ 3 2 ]  [ 3 3 ]
[ 3 2 ]  [ 3 3 ]

Final:
[ 3 3 ]
[ 3 3 ]
[ 3 3 ]
[ 3 3 ]

Perl

Really, the only interesting thing in the Perl implementation is my use of List::AllUtils’ pairwise to accomplish what I did with Raku’s Zip metaoperator. Everything else is stock Raku → Perl translation.

use List::AllUtils qw( pairwise );

# take two displayMatrix() strings and display them side-by-side
sub displayTwo($display1, $display2) {
  # split the two strings and join each row together
  my @d1 = split /\n/, $display1;
  my @d2 = split /\n/, $display2;
  join("\n", pairwise { $a . $b } @d1, @d2) . "\n";
}

sub displayMatrix($label, @matrix) {
  my $width = (scalar(@{$matrix[0]}) + 2) * 2 + 1;
  my $display = sprintf "%-*s\n", $width, $label;
  foreach my $r (0 .. $#matrix) {
    my $row = $matrix[$r];
    $display .= "[ " . join(" ", @$row) . " ]  \n";
  }
  $display
}

sub emptyMatrix($row, $col) {
  my @matrix;
  foreach my $r (1 .. $row) { push @matrix, [ (0) x $col ]; }
  @matrix;
}

sub incrementRow($row, @matrix) {
  foreach my $col ( 0 .. $#{$matrix[0]} ) {
    $matrix[$row][$col]++;
  }
  @matrix
}

sub incrementCol($col, @matrix) {
  foreach my $row ( 0 .. $#matrix ) { $matrix[$row][$col]++; }
  @matrix
}

sub countOdd(@matrix) {
  my $count = 0;
  foreach my $row ( 0 .. $#matrix ) {
    foreach my $col ( 0 .. $#{$matrix[0]} ) {
      $count++ if $matrix[$row][$col] % 2;
    }
  }
  $count
}

sub oddMatrix($row, $col, @locations) {
  my @matrix = emptyMatrix($row, $col);
  my $display = displayMatrix("Initial:", @matrix);
  foreach my $location ( @locations ) {
    my ($row, $col) = @$location;
    $display .= "\nApply [$row,$col]:\n";

    $display .= "Increment row $row:\n";
    my $before = displayMatrix("Before", @matrix);
    @matrix = incrementRow($row, @matrix);
    my $after = displayMatrix("After", @matrix);
    $display .= displayTwo($before, $after);

    $display .= "Increment col $col:\n";
    $before = displayMatrix("Before", @matrix);
    @matrix = incrementCol($col, @matrix);
    $after = displayMatrix("After", @matrix);
    $display .= displayTwo($before, $after);
  }
  $display .= "\n";
  return
    countOdd(@matrix),
    $display . displayMatrix('Final:', @matrix);
}

View the entire Perl script for this task on GitHub.

Python

Same deal with the Python version: it’s pretty much straight up translation of the Raku script.

  • The Python zip function returns an iterator of tuples, so on line 6 we stuff the two parts of the tuple into variables d1 and d2, concatenate them together, and then append them to the display list
def display_two(display1, display2):
  display = []
  # split the two strings and join each row together
  for d1,d2 in zip(display1.split("\n"), display2.split("\n")):
    display.append(d1 + d2)
  return "\n".join(display)

def display_matrix(label, matrix):
  width = (len(matrix[0]) + 2) * 2 + 1
  display = label.ljust(width) + "\n"
  for r in range(len(matrix)):
    display += "[ " + int_join(" ",matrix[r]) + " ]  \n"
  return display

def empty_matrix(row, col):
  matrix = []
  for r in range(row):
    matrix.append([])
    for c in range(col):
      matrix[r].append(0)
  return matrix

def increment_row(row, matrix):
  for col in range(len(matrix[0])):
    matrix[row][col] += 1
  return matrix

def increment_col(col, matrix):
  for row in range(len(matrix)):
    matrix[row][col] += 1
  return matrix

def count_odd(matrix):
  count = 0
  for row in range(len(matrix)):
    for col in range(len(matrix[0])):
      if matrix[row][col] % 2: count += 1
  return count

def odd_matrix(row, col, locations):
  matrix = empty_matrix(row, col)
  display = display_matrix("Initial:", matrix)
  for row,col in locations:
    display += f"\nApply [{row},{col}]:\n"

    display += f"Increment row {row}:\n"
    before = display_matrix("Before", matrix)
    matrix = increment_row(row, matrix)
    after = display_matrix("After", matrix)
    display += display_two(before, after)

    display += f"Increment col {col}:\n"
    before = display_matrix("Before", matrix)
    matrix = increment_col(col, matrix)
    after = display_matrix("After", matrix)
    display += display_two(before, after)
  display += "\n"
  return(
    count_odd(matrix),
    display + display_matrix('Final:', matrix)
  )

def int_join(joiner, arr):
  return joiner.join(map(lambda i: str(i), arr))

View the entire Python script for this task on GitHub.

Elixir

For the Elixir solution, I rediscovered how to use ELixir’s List Comprehensions to do a bunch of the looping over lists.

require Integer # because is_odd is a guard

def display_two(display1, display2) do
  Enum.zip_with(
    String.split(display1, "\n"),
    String.split(display2, "\n"),
    fn x, y -> x <> y end
  )
  |> Enum.join("\n")
end

def display_matrix(label, matrix) do
  rows = for row <- matrix, do:
    "[ " <> Enum.join(row, " ") <> " ]  "
  width = (length(Enum.at(matrix, 0)) + 2) * 2 + 1
  Enum.join(
    [ String.pad_trailing(label, width) ] ++ rows, "\n"
  ) <> "\n"
end

def empty_matrix(row, col) do
  for _r <- 1..row do
    for _c <- 1..col, do: 0
  end
end

def increment_row(row, matrix) do
  List.replace_at(matrix, row,
    (for elem <- Enum.at(matrix, row), do: elem + 1)
  )
end

def increment_col(col, matrix) do
  for row <- matrix, do:
    List.replace_at(row, col, Enum.at(row, col) + 1)
end

def count_odd(matrix) do
  {_, count} = Enum.map_reduce(matrix, 0, fn row, count ->
    {_, count} = Enum.map_reduce(row, count, fn elem, count ->
      {
        elem,
        cond do
          Integer.is_odd(elem) -> count + 1
          true -> count
        end
      }
    end)
    { row, count }
  end)
  count
end

def odd_matrix([], matrix, display) do
  {
    count_odd(matrix),
    display <> "\n" <> display_matrix("Final:", matrix)
  }
end

def odd_matrix([loc | locs], matrix, display) do
  {row, col} = {List.first(loc), List.last(loc)}
  display = display <> "\nApply [#{row},#{col}]:\n"
  display = display <> "Increment row #{row}:\n"
  before_var = display_matrix("Before", matrix)
  matrix = increment_row(row, matrix)
  after_var = display_matrix("After", matrix)
  display = display <> display_two(before_var, after_var)

  display = display <> "Increment col #{col}:\n"
  before_var = display_matrix("Before", matrix)
  matrix = increment_col(col, matrix)
  after_var = display_matrix("After", matrix)
  display = display <> display_two(before_var, after_var)

  odd_matrix(locs, matrix, display)
end

def odd_matrix(row, col, locations) do
  matrix = empty_matrix(row, col)
  odd_matrix(
    locations, matrix,
    display_matrix("Initial:", matrix)
  )
end

View the entire Elixir script for this task on GitHub.


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