Perl Weekly Challenge: Week 337

Challenge 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)

We can solve this in Raku by first creating a hash that maps the .sort()ed command-line input to their reversed indices. (found with .kv() and .reverse()) I originally intented to create %sorted all in one go but this causes problems for e.g. example 2 I got 0 (the index of the first element) instead of 3 (the index of the last element.) So I added the key/value pairs one by one as I traversed the sorted and reversed list, skipping (with //=) any keys that were already defined in the hash.

my %sorted;

for @num1.sort({ $^a <=> $^b }).kv.reverse -> $k, $v {
    %sorted{$k} //= $v;
}

Now we can just map the input list to the values in %sorted. The rest of this line is just for formatting the output in the style of the spec.

say q{(}, @num1.map({ %sorted{$_} }).join(q{, }), q{)};

(Full code on Github.)

I was prepared to write additional code to make up for Raku features missing in Perl but modern versions of the language have been adding a lot of nifty new features. indexed() for example, is the equivalent of Raku's .kv(). And now we can have multiple iterators (e.g. my ($k, $v)) in a for loop. In order to enable this features (and suppress warnings about them) we need to add a few lines to the top of the script.

use builtin qw/ indexed /;
no warnings qw/ experimental::builtin experimental::for_list /;

Now, the script looks pretty similiar to the Raku version.

my %sorted;

foreach my ($k, $v) (reverse indexed sort { $a <=> $b } @num1) {
    $sorted{$k} //= $v;
}

say q{(}, (join q{, }, map { $sorted{$_} } @num1), q{)};

(Full code on Github.)

Challenge 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 ]

Once again we get the input from the command line. The first two arguments represent the rows and columns in the matrix and the rest represent the locations in x,y format. So for e.g. example 5, the arguments would be 4 2 1,0 3,1 2,0 0,1. They are copied into $row, $col, and @locations respectively.

Using the xx operator, we create a matrix with $row rows and $col columns and initialize each cell with 0.

my @matrix = [0 xx $col] xx $row;

For each location...

for @locations -> $location {

We .split() it into a row and column.

    my ($r, $c) = $location.split(',')».Int;

We increment every cell in the row. Array slices and the » operator make this code very compact.

    @matrix[$r;*]»++;

We increment every cell in the column in the same way.

    @matrix[*;$c]»++;
}

Finally we count how many odd elements in the matrix (using the function described below) and print the results.

count(@matrix).say;

The count() function takes a 2d array representing a matrix as a parameter.

sub count(@matrix) {

A variable to hold the count of odd cells.

    my $count = 0;

Every row is searched for cells containing odd numbers, the count of such cells is added to $count.

    for @matrix -> $row {
        $count += $row.grep({ $_ mod 2 }).elems;
    }

The totsl count is returned.

    return $count;
}

(Full code on Github.)

This is the Perl version. The major difference is that it uses for-loops to compensate for the lack of xx and » operators.

sub count(@matrix) {
    my $count = 0;

    for my $row (@matrix) {
        $count += scalar grep { $_ % 2 } @{$row};
    }

    return $count;
}

my @matrix;
for my $i (0 .. $row - 1) {
    push @matrix, [ (0) x $col ];
}

for my $location (@locations) {
    my ($r, $c) = split /,/, $location;
    for (@{$matrix[$r]}[0 .. $col - 1]) {
        $_++;
    }
    for (@matrix[0 .. $row - 1]->[$c]) {
        $_++;
    }
}

say count(@matrix);

(Full code on Github.)