with Raku

This is my response to the Perl Weekly Challenge #151.

You are given binary tree.

Write a script to find the minimum depth.

The minimum depth is the number of nodes from the root to the nearest leaf node (node without any children).

Example 1:

Write a script to find the minimum depth.

The minimum depth is the number of nodes from the root to the nearest leaf node (node without any children).

Example 1:

Input: '1 | 2 3 | 4 5'
1
/ \
2 3
/ \
4 5
Output: 2

Example 2:
Input: '1 | 2 3 | 4 * * 5 | * 6'
1
/ \
2 3
/ \
4 5
\
6
Output: 3

The first part of the program is an exact copy of my «binary-tree-diameter» - except the default tree - from my answer to Challenge #125.2: Binary Tree Diameter. I even used stars to indicate dead ends in a tree, as done here.

File: binary-tree-depth#! /usr/bin/env raku
unit sub MAIN (Str $tree = "1 | 2 3 | 4 * * 5 | * 6", :v(:$verbose));
class BinaryNode
{
has Int $.value;
has BinaryNode $.left;
has BinaryNode $.right;
}
my @btree = $tree.split("|")>>.words;
my @old-nodes;
my @new-nodes;
for @btree.reverse -> $row
{
my @current = @$row;
@old-nodes = @new-nodes;
@new-nodes = ();
for @current -> $value
{
if $value eq "*"
{
@new-nodes.push("*");
next;
}
my $left = @old-nodes.shift // "*"; $left = Nil if $left eq "*";
my $right = @old-nodes.shift // "*"; $right = Nil if $right eq "*";
@new-nodes.push(BinaryNode.new(value => $value.Int,
left => $left // Nil,
right => $right // Nil));
}
}
my $btree = @new-nodes[0]; # [1]
my @paths; # [2]
traverse2($btree, ()); # [3]
sub traverse2 ($current, @path is copy) # [3]
{
@path.push: $current.value; # [4]
if ($current.left or $current.right) # [5]
{
traverse2($current.left, @path) if $current.left; # [5a]
traverse2($current.right, @path) if $current.right; # [5b]
}
else # [6]
{
say ": Path: [{ @path.join(", ") }] with length { @path.elems }"
if $verbose;
@paths.push: @path; # [6a]
return; # [6b]
}
}
say @paths>>.elems.min; # [7]

[1] The top of the tree.

[2] We are going to collect the different paths from the top to each leaf node here.

[3] Off we go, recursively, from the top.

[4] Add the current node value.

[5] Do we have any child nodes? If so, traverse them [5a and 5b].

[6] If not; this is leaf node. We have a path [6a], and are done here

[7] For each of the paths, get the number of elements, then get the lowest one and print it.

Running it:

$ ./binary-tree-depth "1 | 2 3 | 4 5"
2
$ ./binary-tree-depth -v
3

With verbose mode:

$ ./binary-tree-depth -v "1 | 2 3 | 4 5"
: Path: [1, 2, 4] with length 3
: Path: [1, 2, 5] with length 3
: Path: [1, 3] with length 2
2
$ ./binary-tree-depth -v
: Path: [1, 2, 4, 6] with length 4
: Path: [1, 3, 5] with length 3
3

Some more. just for fun:

$ ./binary-tree-depth "1"
1
$ ./binary-tree-depth "1 | *"
1
$ ./binary-tree-depth "1 | * *"
1

The tree from challenge #125.2:

$ ./binary-tree-depth -v "1 | 2 5 | 3 4 6 7 | * * * * * * 8 10 | 9"
: Path: [1, 2, 3] with length 3
: Path: [1, 2, 4] with length 3
: Path: [1, 5, 6] with length 3
: Path: [1, 5, 7, 8, 9] with length 5
: Path: [1, 5, 7, 10] with length 4
3

You are planning to rob a row of houses, always starting with the first and moving in the
same direction. However, you can’t rob two adjacent houses.

Write a script to find the highest possible gain that can be achieved.

Example 1:

Write a script to find the highest possible gain that can be achieved.

Example 1:

Input: @valuables = (2, 4, 5);
Output: 7
If we rob house (index=0) we get 2 and then the only house we can rob is
house (index=2) where we have 5. So the total valuables in this case is
(2 + 5) = 7.

Example 2:
Input: @valuables = (4, 2, 3, 6, 5, 3);
Output: 13
The best choice would be to first rob house (index=0) then rob house
(index=3) then finally house (index=5). This would give us 4 + 6 + 3 =13.

The morality of this task is dubious, so
let us say that we are *painting* houses instead. The point of not painting two
adjacent houses is that the paint job is done to impress the neighbours. This will
not work out if all the houses are painted, so we make a point of promising not to
paint the house on either side.

We can do this by creating lists of indices. The first house (with index 0) is always
selected, as specified in the challenge. (So the "value" of the second house does not
matter at all, as we cannot choose it.) The jump (so to speak), as we cannot *visit*
adjacent houses, turns out to be quite easy:

The first row shows an example with 9 houses. The second row shows what happens if we choose every second house (a jump with value 2), and the third row has every third house chosen. The fourth and last row shows what happens if we do every fourth house, but this is the same as two jumps with value 2, except that we get a smaller total sum (as we have skipped the house with index 2), so we can disregard it. The only relevant jump values are 2 and 3, as long as we are in for maximising the sum.

File: rob-the-house#! /usr/bin/env raku
unit sub MAIN (*@valuables where @valuables.elems &&
all(@valuables) ~~ Numeric, :v(:$verbose)); # [1]
my $seq := gather { recurse( (0,), 0, @valuables.elems -1); } # [2]
sub recurse(@done is copy, $index, $todo is copy) # [2a]
{
if $todo < 2 # [3]
{
say ": Added candidate: @done[]" if $verbose;
take @done; # [3a]
return; # [3b]
}
for 2, 3 -> $add # [4]
{
if $todo >= $add # [5]
{
my @done1 = @done.clone; # [6]
@done1.push: $index + $add; # [6a]
recurse(@done1, $index + $add, $todo - $add); # [7]
}
}
}
my @candidates = $seq; # [8]
if $verbose
{
for @candidates -> @list
{
say ": Candidate indices: [{ @list.join(",") }] with sum: \
{ @valuables[@list].sum }";
}
}
say @candidates.map({ @valuables[@$_].sum }).max; # [9]

[1] All the house values must be numeric. Note that I do not exclude negative values.

[2] Setting up the sequence of indices with
`gather`

/`take`

is ideal here. I have hidden the `take`

calls in a procedure, which works quite well. The first argument to the recursive procedure
is a list of indices so far (i.e. houses to visit). The second is the current index, and
the third is the number of houses left (i.e. remaining after the current one).

See my Raku Gather,
I Take article or
docs.raku.org/syntax/gather take for more information about
`gather`

/`take`

.

[3] Less than 2 houses left? If so, return the list of indices (with `take`

[3a]) and finish the recursion (with `return`

[3b]).

[4] For each jump value,

[5] Do we have enough houses left to do the jump?

[6] If so, add the new house to the list of indices. Note the use of
`clone`

to get a copy of the array - as we modify it twice (in the loop)

See
docs.raku.org/routine/clone for
more information about the `clone`

method.

[7] Off we go, recurively, with the upfawtes list of indices, current position, and remaining number of houses.

[8] Get the sequence as an array.

[9] Iterate over the candidates (with `map`

), use the candidate indices as an array
slice into the valuables array to get those values, and get the sum of each (with `sum`

.
The result is a list of values, one for each candidate list of indices. Then we get the largest
one (with `max`

) and print it.

Running it:

$ ./rob-the-house 4 2 3 6 5 3
13
$ ./rob-the-house -v 4 2 3 6 5 3
: Added candidate: 0 2 4
: Added candidate: 0 2 5
: Added candidate: 0 3 5
: Candidate indices: [0,2,4] with sum: 12
: Candidate indices: [0,2,5] with sum: 10
: Candidate indices: [0,3,5] with sum: 13
13

Looking good.

And that's it.

P.S. Do not rob houses.