Perl Weekly Challenge 151: trees and robberies
It is sad that, after more than two years of me doing Raku, I still don’t have any production code project to work on. Therefore, in order to keep my coding and Raku-ing (is that a term?) knowdledge, I try to solve every Perl Weekly Challenge tasks.In the following, the assigned tasks for Challenge 151.
And this week, as for the previous PWC, I had time to quickly implement the tasks also on PostgreSQL
plpgsql
language:
PWC 151 - Task 1
I hate trees!The task provided a stringified version of a tree, then it was asked to find the shortest path to a leaf. The problematic part was to build the tree starting from the string.
I created a simple
Node
class:
class Node {
has Int $.value;
has Node $.left is rw;
has Node $.right is rw;
has Node $.parent is rw;
}
Then, the string must be translated into a workable tree, that I placed into a
@tree
aray where each tree level is an array of nodes:
sub MAIN( Str $nodes, Str $delim = '|' ) {
my @nodes-as-text = $nodes.split( $delim );
my @tree;
for @nodes-as-text -> $current-nodes {
my @current-level;
my $index = -1;
my @values = $current-nodes.words;
@values.push: '*' if @values.elems !%% 2;
for @values -> $left, $right {
say "$left, $right";
$index++;
my Node $left-node = Node.new: value => $left.Int if $left !~~ '*';
my Node $right-node = Node.new: value => $right.Int if $right !~~ '*';
@current-level.push: $left-node if $left-node;
@current-level.push: $right-node if $right-node;
next if ! @tree;
@tree[ * - 1 ][ $index ].left = $left-node if $left-node;
$left-node.parent = @tree[ * - 1 ][ $index ] if $left-node;
@tree[ * - 1 ][ $index ].right = $right-node if $right-node;
$right-node.parent = @tree[ * - 1 ][ $index ] if $right-node;
}
@tree.push: @current-level;
}
my @paths;
for @tree.reverse -> @leaves {
for @leaves -> $current-leaf is rw {
next if $current-leaf.left || $current-leaf.right;
my $path = 0;
while ( $current-leaf ) {
$path++;
$current-leaf = $current-leaf.parent;
}
@paths.push: $path;
}
}
@paths.min.say;
}
Once the
@tree
array is built, I start from the @leaves
, the last level of the array, and start to count going upper.
Every path count is placed into @paths
, so that I can quickly compute the min
.
PWC 151 - Task 2
Robbering houses in a row skipping an house after another:sub MAIN( Str $houses, Bool $verbose = True ) {
my %rob;
my @values = $houses.words;
my $current-index = 0;
# bootstrap
%rob{ $current-index } = @values[ $current-index ];
while ( $current-index < ( @values.elems - 2 ) ) {
my %current;
%current = key => $_,
value => @values[ $_ ]
if ! %current || @values[ $_ ] > %current<value>
for ( $current-index + 2 ) ..^ @values.elems;
%rob{ %current<key> } = %current<value>;
$current-index = %current<key>;
}
# print the result
%rob.values.sum.say;
# print the status
if $verbose {
"House { .key } = { .value } ".say for %rob;
}
}
I use a
%rob
hash with the key as the index of the houses, and the value the robbery gain.
Then I use a Pair
to store the max value available from remaining houses, skipping the next to the current one.
At the end, I print the sum
of the robbery gain.
PWC 151 - Task 1 in PostgreSQL
Implementing the first task in PostgreSQL was a little long. First of all, I needed a table to represent theNode
of the tree, where each row is a node of the tree. Then there is the need for a parsing of the stringified version of the tree. Therefore, alongside the tables, I created a couple of functions:
f_flat_tree
accepts the string and provides a table like node structures with values and tree level;f_generate_tree
is aPROCEDURE
and takes the output of the previous function and populates thenode
table accordingly.
CREATE SCHEMA IF NOT EXISTS pwc151;
CREATE TABLE IF NOT EXISTS pwc151.node(
pk int generated always as identity
, val int
, parent_pk int
, left_pk int
, right_pk int
, level int
, PRIMARY KEY( pk )
, FOREIGN KEY( parent_pk ) REFERENCES pwc151.node( pk )
, FOREIGN KEY( left_pk ) REFERENCES pwc151.node( pk )
, FOREIGN KEY( right_pk ) REFERENCES pwc151.node( pk )
);
TRUNCATE pwc151.houses CASCADE;
/**
* Produces a flat tree like:
testdb=> SELECT * FROM pwc151.f_flat_tree( '1 | 2 3 | 4 5 ');
DEBUG: Inspecting [1]
DEBUG: Inspecting [|]
DEBUG: Changing level!
DEBUG: Inspecting [2]
DEBUG: Inspecting [3]
DEBUG: Inspecting [|]
DEBUG: Changing level!
DEBUG: Inspecting [4]
DEBUG: Inspecting [5]
val | lev
-----+-----
1 | 0
2 | 1
3 | 1
4 | 2
5 | 2
(5 rows)
*/
CREATE OR REPLACE FUNCTION
pwc151.f_flat_tree( tree text, delim char default '|' )
RETURNS TABLE( val int, lev int )
AS $CODE$
DECLARE
needle text;
BEGIN
lev := 0;
FOR needle IN SELECT v FROM regexp_split_to_table( tree, delim ) v LOOP
CONTINUE WHEN needle ~ '^\s+$';
RAISE DEBUG 'Inspecting [%]', needle;
IF needle = delim THEN
RAISE DEBUG 'Changing level!';
lev := lev + 1;
CONTINUE;
END IF;
IF needle = '*' THEN
val := NULL;
ELSE
val := needle::int;
END IF;
RETURN NEXT;
END LOOP;
RETURN;
END
$CODE$
LANGUAGE plpgsql;
CREATE OR REPLACE PROCEDURE
pwc151.f_generate_tree( tree text, delim text default '|' )
AS $CODE$
DECLARE
current_tuple pwc151.node%rowtype;
parent_tuple pwc151.node%rowtype;
lev int := 1;
val int := 0;
max_lev int;
counter int := 0;
last_pk int := -1;
BEGIN
TRUNCATE pwc151.node CASCADE;
INSERT INTO pwc151.node( val, level )
SELECT * FROM pwc151.f_flat_tree( tree, delim );
COMMIT;
SELECT max( level )
INTO max_lev
FROM pwc151.node;
RAISE DEBUG 'Max level found %', lev;
WHILE lev <= max_lev LOOP
RAISE DEBUG '========= Level % ==============', lev;
FOR parent_tuple IN SELECT * FROM pwc151.node WHERE level = lev - 1 ORDER BY pk LOOP
counter := 0;
FOR current_tuple IN SELECT * FROM pwc151.node
WHERE level = lev
AND parent_pk IS NULL
AND pk >= last_pk
ORDER BY pk LOOP
last_pk := current_tuple.pk;
EXIT WHEN counter >= 2;
RAISE DEBUG 'Step %: Adjusting node [%] [%]', counter, current_tuple.val, current_tuple.pk;
CONTINUE WHEN lev = 0; -- root node
RAISE DEBUG 'Parent node [%] -> [%] = [%]', parent_tuple.val, ( counter % 2 = 0 ), current_tuple.val;
counter := counter + 1;
CONTINUE WHEN current_tuple.val IS NULL;
IF counter % 2 <> 0 THEN
RAISE DEBUG 'Left node [%] child of [%]', current_tuple.val, parent_tuple.val;
UPDATE pwc151.node
SET left_pk = current_tuple.pk
WHERE pk = parent_tuple.pk;
ELSE
RAISE DEBUG 'Right node [%] child of [%]', current_tuple.val, parent_tuple.val;
UPDATE pwc151.node
SET right_pk = current_tuple.pk
WHERE pk = parent_tuple.pk;
END IF;
UPDATE pwc151.node
SET parent_pk = parent_tuple.pk
WHERE pk = current_tuple.pk;
COMMIT;
END LOOP;
END LOOP;
lev := lev + 1;
END LOOP;
END
$CODE$
LANGUAGE plpgsql;
WITH RECURSIVE min_depth AS
(
-- root
SELECT pk, val, level, 'root' as description, 1 as depth
FROM pwc151.node
WHERE level = 0
AND parent_pk IS NULL
UNION
SELECT n.pk, n.val, n.level, description || '->' || n.val, p.depth + 1
FROM min_depth p, pwc151.node n
WHERE n.level = p.level + 1
AND n.parent_pk = p.pk
)
SELECT description, depth
FROM min_depth
WHERE depth = ( SELECT min( depth ) FROM min_depth WHERE level > 0 );
Last, a recursive CTE does the trick of finding out all the shortest paths and their length.
PWC 151 - Task 2 in PostgreSQL
The second task is pretty much a re-implementation of the Raku solution with a table, namedhouses
that stores the values.
CREATE SCHEMA IF NOT EXISTS pwc151;
CREATE TABLE IF NOT EXISTS pwc151.houses(
pk int generated always as identity
, index int
, value int
, PRIMARY KEY( pk )
);
TRUNCATE pwc151.houses;
INSERT INTO pwc151.houses( index, value )
VALUES ( 0, 4), ( 1, 2 ), (2, 3), (4, 6), (5,5),(6,3);
CREATE OR REPLACE FUNCTION
pwc151.f_sum()
RETURNS int
AS $CODE$
DECLARE
current_row pwc151.houses%rowtype;
s int := 0;
max_index int;
BEGIN
-- bootstrap
SELECT *
INTO current_row
FROM pwc151.houses
ORDER BY index
LIMIT 1;
s := s + current_row.value;
SELECT max( index )
INTO max_index
FROM pwc151.houses;
WHILE current_row.index < max_index LOOP
SELECT *
INTO current_row
FROM pwc151.houses
WHERE index > current_row.index + 1
ORDER BY value DESC
LIMIT 1;
s := s + current_row.value;
END LOOP;
RETURN s;
END
$CODE$
LANGUAGE plpgsql;
The function
f_sum
iterates on every row found with the index within the right boundaries and computes the max value using the trick of ordering by value
and limiting the result set to the first row, so to get the max value without the need for a subquery.