Colin Crain › Perl Weekly Review #152

Monday, Mar 21, 2022| Tags: perl

( …continues from previous week. )

Welcome to the Perl review pages for Week 152 of The Weekly Challenge! Here we will take the time to discuss the submissions offered up by the team, factor out some common methodologies that came up in those solutions, and highlight some of the unique approaches and unusual code created.


●︎ Why do we do these challenges?

I suppose any reasonable answer to that question would be from a field as wide ranging and varied as the people who choose to join the team. One thing, though, is clear: it’s not a competition, and there are no judges, even if there is a “prize” of sorts. About that – I think of it more as an honorarium periodically awarded to acknowledge the efforts we make towards this strange goal. So there’s no determination to find the fastest, or the shortest, or even, in some abstract way, the best way to go about things, although I’m certain the participants have their own aspirations and personal drives. As Perl is such a wonderfully expressive language, this provides quite a bit of fodder to the core idea of TMTOWTDI, producing a gamut of varied techniques and solutions.

Even the tasks themselves are often open to a certain amount of discretionary interpretation. What we end up with is a situation where each participant is producing something in the manner they find the most interesting or satisfying. Some team members focus on carefully crafted complete applications, thoroughly vetting input data and handling every use case they can think up. Others choose to apply themselves to the logic of the underlying puzzle and making it work in the most elegant way they can. Some eschew modules they would ordinarily reach for, others embrace them, bringing to light wheels perhaps invented years ago that happen to exactly solve the problem in front of them today.

I’ve been considering this question for some time and have found one binding commonality between all of us out solving these challenges each week, in that however we normally live our lives, the task in front of us more than likely has nothing to do with any of that. And I think this has great value. We all do what we do, in the real world, and hopefully we do it well. The Weekly Challenge provides us with an opportunity to do something germane to that life yet distinctly different; if we only do the things we already know how to do then we will only do the same things over and over. This is where the “challenge” aspect comes into play.

So we can consider The Weekly Challenge as providing a problem space outside of our comfort zone, as far out from that comfort as we wish to take things. From those reaches we can gather and learn things, pick and choose and bring what we want back into our lives. Personally, I think that’s what this whole thing is about. YMMV.


Every week there is an enormous global collective effort made by the team, analyzing and creatively coding the submissions, and that effort deserves credit due.

And that, my friends, is why I’m here, to try and figure out ways to do just that.

So, here we are then. I’m ready — let’s get to it and see what we can find.


For Additional Context…

before we begin, you may wish to revisit either the pages for the original tasks or the summary recap of the challenge. But don’t worry about it, the challenge text will be repeated and presented as we progress from task to task.

Oh, and one more thing before we get started:

Getting in Touch with Us

Email › Please feel free to email me (Colin) with any feedback, notes, clarifications or whatnot about this review.

GitHub › Submit a pull request to us for any issues you may find with this page.

Twitter › Join the discussion on Twitter!

I’m always curious as to what the people think of these efforts. Everyone here at the PWC would like to hear any feedback you’d like to give.

...So finally, without further ado...

•       Task 1       •       Task 2       •       BLOGS       •



TASK 1

Triangle Sum Path

Submitted by: Mohammad S Anwar

You are given a triangle array.

Write a script to find the minimum sum path from top to bottom.

Example 1:

    Input: $triangle = [ [1], [5,3], [2,3,4], [7,1,0,2], [6,4,5,2,8] ]

                1
               5 3
              2 3 4
             7 1 0 2
            6 4 5 2 8

    Output: 8

    Minimum Sum Path = 1 + 3 + 2 + 0 + 2 => 8

Example 2:

    Input: $triangle = [ [5], [2,3], [4,1,5], [0,1,2,3], [7,2,4,1,9] ]


                5
               2 3
              4 1 5
             0 1 2 3
            7 2 4 1 9

    Output: 9

    Minimum Sum Path = 5 + 2 + 1 + 0 + 1 => 9

about the solutions

Abigail, Alexander Pankoff, Athanasius, Colin Crain, Dave Jacoby, Duncan C. White, E. Choroba, Flavio Poletti, James Smith, Jorg Sommrey, Laurent Rosenfeld, Lubos Kolouch, Mohammad S Anwar, Peter Campbell Smith, PokGoPun, Robert DiCicco, Roger Bell_West, Simon Green, Ulrich Rieke, and W. Luis Mochan

At first reading of this particular task I found myself a bit confused. I suppose I had trees on my mind, and was visualizing a path being traced downward from the peak of the triangle, passing through elements along he way like a pachinko ball bouncing from pin to pin as wends its way to the bottom.

A careful examination of the example, however, does not bear out this hypothesis. It seems we skip from level to level without regard to linear position within the strata. So rather than the complicated problem of evaluating and selecting the smallest possible sum from numerous choices, we need only determine the locally-minimal element within each subarray sequence, and sum these values as we traverse the outer array.

There were 20 submissions for the first task this past week.

TRIANGLES, TRIANGLES, we don’t need no stinking TRIANGLES…

Laurent Rosenfeld, E. Choroba, PokGoPun, W. Luis Mochan, Roger Bell_West, Ulrich Rieke, Flavio Poletti, Jorg Sommrey, Alexander Pankoff, Athanasius, Dave Jacoby, and James Smith

There was a large degree of conformity in the steps to solve the problem stated, alongside a fairly consistant confusion as to whether the directives were understood correctly. Many members voiced concern that despite the complexity of the setup, with the triangular array-of-arrays, the actual solution had little to do with any of that and could be solved in a straightforward fashion on two-dimensional data of any shape.

Laurent Rosenfeld

  additional languages: Raku

  blog writeup: Perl Weekly Challenge 152: Triangle Sum Path

We’ll open the topic with Laurent’s submission.

To accomplish the task we need two basic pieces of functionality: a minimum function of some sort to find the largest element from an array, and a sum function to gather and accumulate a running total.

For the first component Laurent constructs a subroutine to examine each element in a list and take note of the largest value found, updating a variable as required and ultimately returning it.

Then, using a map, he processes the given list of array references, replacing each element with its maximum value along the way. This list is then looped over and the individual elements are added to a $sum variable for output.

    sub min {
        my $min = shift;
        for (@_) {
            $min = $_ if $_ < $min;
        }
        return $min;
    }

    for my $triangle ([ [1], [5,3], [2,3,4], [7,1,0,2], [6,4,5,2,8] ],
        [ [5], [2,3], [4,1,5], [0,1,2,3], [7,2,4,1,9] ]) {
            my $sum = 0;
            $sum += $_ for map { min(@$_) } @$triangle;
            say join " ", (map { "[@$_]"} @$triangle), " -> $sum";
        }

E. Choroba

The List::Util module has been packaged in the Perl core distribution for quite some time — since version 5.004, in fact. In it we will find two functions, min and sum, that happen to do exactly what we need to do. So let’s use them, no?

It’s simple, clean and straightforward. What more do we need?

    use List::Util qw{ min sum };

    sub triangle_sum_path {
        my ($triangle) = @_;
        return sum(map min(@$_), @$triangle)
    }

PokGoPun

New member PokGoPun, on the other hand, has taken it upon themself to make the problem a little more interesting by creating an elaborate verbose output, drawing the input triangles and the equation for the selected summation.

Of note: to find the array minimum they employ the trick of sorting the array and selecting the first element, which then will be the smallest. This also can work as a maximum function by taking the last element, and a minmax should you need both.

    my @sample;
    ### If scripts' arguments present, convert them to triangle array

    {
        last unless @ARGV;
        push @sample, [ map{[$_=~/(\d+)/g]} @ARGV ];
    }
    ### Add sample triangle arrays if none available

    {
        last if @sample;
        push @sample, [ [1], [5,3], [2,3,4], [7,1,0,2], [6,4,5,2,8] ];
        push @sample, [ [5], [2,3], [4,1,5], [0,1,2,3], [7,2,4,1,9] ];
    }
    #print Dumper \@sample;

    ### Generate array of min path

    sub minPath {
        my $path = shift;
        my @minPath;
        push @minPath, [sort { $a <=> $b } @$_]->[0] foreach @$path;
        return @minPath
    }
    ### Generate output similar to those in examples of the challenge's task

    foreach my $s (@sample) {
        printf "\nInput: \$triangle = [ %s ]\n\n", join(", ", map{"[".join(",",@$_)."]"} @$s );
        print "\t"," " x scalar(@{$s->[-1-$_]}), join(" ",@{$s->[$_]}),"\n" foreach 0..$#$s;
        my @minPath = minPath($s);
        my $sumText = join(" + ",@minPath);
        my $sum = eval($sumText);
        print "\nOutput: $sum\n\n\tMinimum Sum Path = $sumText => $sum\n",
    }

Here’s an example of the output:

    Input: $triangle = [ [1], [5,3], [2,3,4], [7,1,0,2], [6,4,5,2,8] ]

             1
            5 3
           2 3 4
          7 1 0 2
         6 4 5 2 8

    Output: 8

        Minimum Sum Path = 1 + 3 + 2 + 0 + 2 => 8

As you can see, it precisely mirrors the examples given.

W. Luis Mochan

  blog writeup: Perl Weekly Challenge 152 – W. Luis Mochán

Luis breaks away from the pack in a different direction, taking triangles as imput strings in the form of a normal coded Perl array construction. He then uses eval to examine the string as Perl code, producing a proper multidimensional array. The services of Try::Tiny are brought in to provide a try/catch loop on the possibly faulty user input.

    use List::Util qw(min sum0 all);
    use Try::Tiny;
    for my $triangle_string (@ARGV){
        try {
            my $triangle=eval $triangle_string;
            my @rows=@$triangle;
            # Seems unnecesary, but test shape

            die "Wrong row-size in $triangle_string"
                unless all{$_+1==@{$rows[$_]}}(0..@rows-1);
            my @minima=map {min @$_} @rows;
            my $sum=sum0 @minima;
            say "Input: $triangle_string\nOutput: $sum\nPath values: ", join "-", @minima;
        }
        catch {
            say $_;
        }
    }

Roger Bell_West

  additional languages: Javascript, Kotlin, Lua, Postscript, Python, Raku, Ruby, Rust

  blog writeup: RogerBW’s Blog: The Weekly Challenge 152: Triangles, Rectangles, and Objects

I always find looking at the personal preference in choosing which, if any, module functions to include to be an interesting observation to make. Here Roger takes min, from List::Util, but chooses to inline the summing in a small for loop.

    use List::Util qw(min);

    sub tsp {
      my $tree = shift;
      my $mp=0;
      foreach my $r (@{$tree}) {
        $mp+=min(@{$r});
      }
      return $mp;
    }

Ulrich Rieke

  additional languages: C++, Haskell, Raku

Ulrich has devised a scheme for inputting a triangle, one line per level, with individual elements separated by spaces. Careful directions are given to the user along the way, with data being drawn in through the <STDIN> filehandle. Our old friend the min is imported, and once a line has been split on whitespace into a list, the minimum value is selected and added to an aggregate sum.

    my $line = <STDIN> ;
    chomp $line ;
    while ( $line ne "e" ) {
      while ( $line !~ /^(\d\s*)+$/ ) {
          say "Enter only integers separated by spaces, one more per line!" ;
          $line = <STDIN> ;
          chomp $line ;
      }
      unless ( $line eq "e" ) {
          $len = scalar( split( /\s+/ , $line ) ) ;
          while ( $len != $lastLen + 1 ) {
        say "There should be one number more per line!" ;
        $line = <STDIN> ;
        chomp $line ;
        $len = scalar( split( /\s+/ , $line ) ) ;
          }
      }
      $pathSum += min( split( /\s+/ , $line )) ;
      $lastLen = $len ;
      $line = <STDIN> ;
      chomp $line ;
    }
    say $pathSum ;

Flavio Poletti

  additional languages: Raku

  blog writeup: PWC152 - Triangle Sum Path - ETOOBUSY

Flavio notes that, as I did, he at first misread the directives as being far more complex than they were. However he went on to create a triangular graph with vertices at the individual elements and an A* implementation to find the minimal way across it.

But that first effort was written in Raku, and although I am very fond of that particular language I feel it would be distracting to include it here. You can see it, if you like, in his writeup on the challenge.

For Perl, though, he realized the error of his ways before porting over that implementation, and instead supplies us with this short and sweet version:

    use List::Util qw< sum min >;

    my @triangle = map { [split m{,}mxs] } @ARGV;
    say triangle_sum_path(@triangle);

    sub triangle_sum_path (@triangle) { sum map { min $_->@* } @triangle }

Jorg Sommrey

Jorg has been using The Weekly Challenge to tightly focus on a select roster of complex modules, taking them out and really working them to both explore their functionality and properly learn the extant of their capabilities.

With Math::Prime::Util this is an ambitious project, as it provides over two hundred routines and constants relevant to number theory calculations.

Here he brings in two functions from that package, vecsum and vecmin. These work similarly to the List::Util versions sum and min we’ve seen already, but operate on integer types, removing the possibility of floating point errors for very large values.

    use Math::Prime::Util qw(vecmin vecsum);

    # Find the minumum sum picking one element from each row of an AoA.

    sub minsum {
        vecsum map vecmin(@$_), @{+shift}
    }

Alexander Pankoff

  blog writeup: Challenge 152 Task #1 - Summing up minimums

There are in fact two summation functions in List::Util, sum() and sum0(). The difference between them is in their behavior when given an empty list. In the absence of any data whatsoever, sum0 will produce a real 0 value, with sum returning undef. Selection between the two should be based on what you want to happen in this circumstance.

I’ll also note that in the case of a undefined internal element, both functions will continue to find a sum of the remaining elements but will complain with a warning.

There are a variety of reasons, use-cases and preferences for choosing one function over the other. Generally it doesn’t matter much, but occasionally it very much will. Here Alexander had decided on using sum0:

    sub minimum_triangle_sum_path($triangle) {
        return sum0( map { min(@$_) } @$triangle );
    }

Athanasius

  additional languages: Raku

When discussing number theory, “triangle” has a restricted meaning, specifically defining a layered structure where each level has one more element than the one next to it, either ascending or descending. Unconventionally scaled or asymmetric triangles could surely arise, and occasionally do, but when this happens they would need to be specifically described. In the absence of this we can infer a strict, unary progression.

With this observation, the monk brings us an interesting analysis, in that we can enter our triangle data on a single line of input. We know that the length of each sub-array will advance one element with each step taken, and the total length of an array with n levels will be the triangular number T(n). The first level will have 1 element, the second two, then three, et cetera, and the overall length will follow the prgression (1, 3, 6, 10 …).

We can verify the input data list against the triangular numbers before we start to make sure that we can indeed form a complete structure.

The triangular number test used is that 8n + 1 must be a perfect square.

    sub build_triangle
    {
        my ($items)   = @_;
        my  $row_idx  = 0;
        my  $index    = 0;
        my  @triangle;

        while ($index <= $#ARGV)
        {
            my   @row;
            push @row, $items->[ $index++ ] for 1 .. $row_idx + 1;
            push @triangle, [ @row ];

            ++$row_idx;
        }

        return \@triangle;
    }

    sub is_triangular
    {
        my ($num) = @_;
        my  $x    = 8 * $num + 1;
        my  $root = int sqrt $x;

        return $root * $root == $x;
    }

    MAIN:
    {
        my $items    = parse_command_line();
        my $triangle = build_triangle( $items );

        printf "Input:  \$triangle = %s\n", sprint_triangle( $triangle );

        my   @min_sum_path;
        push @min_sum_path, min( $triangle->[ $_ ]->@* ) for 0 .. $#$triangle;

        my $min_sum  = 0;
           $min_sum += $_ for @min_sum_path;

        print "Output: $min_sum\n";

        if ($VERBOSE)
        {
            printf "\n    Minimum Sum Path: %s = %d\n",
                    join( ' + ', @min_sum_path ), $min_sum;
        }
    }

Dave Jacoby

  blog writeup: Functional Paths: Weekly Challenge #152 Pt. 1 | Committed to Memory

Dave seemed outright perplexed by the first task, describing it as “disconcertingly easy”. All I have to add is: Weird, right? I know. Disconcerting is a very good word for it. You find yourself studying the information, going over and over it, looking for the thing you missed. Sometimes, though, it just isn’t there. You’ve killed the dragon, finished the quest and now you’re left scratching your head wondering what to do next.

    my $path = join ' + ', map { min $_->@* } $triangle->@*;
    my $sum  = sum map { min $_->@* } $triangle->@*;

Perhaps to make up for it he does go out of his way to draw us some nice triangles, though. Which is pretty cool, actually.

        Input:  $triangle=[ [1], [5,3], [2,3,4], [7,1,0,2], [6,4,5,2,8] ]
        Output: 8
        Minimum Sum Path = 1 + 3 + 2 + 0 + 2 => 8

          1
         5 3
        2 3 4
       7 1 0 2
      6 4 5 2 8

James Smith

  blog writeup: Perl Weekly Challenge #152

Finally, James went as far as to provide a veritable plethora of solution options, in both the intuitive yet incorrect graph analysis and the somewhat head-scratching and yes, disconcerting pick-any-card correct interpretation.

From the former choices, we have two versions that start at a null imaginary lower sub-level, working upwards selecting the best, lowest cost route to the aggregate summed layer that is moved upwards on level at a time, discarding the largest route at each level, until at the top we have only a single level remaining.

    ## For each entry - we store tuple [ total, "path" ].

    ## We create a "row" below the triangle with "0"s and empty paths...

    ## We work back up the tree, and for each cell in the row we

    ## add the left or right sub-tree depending on which has the lowest

    ## value...


    sub min_path {
      my @p = ( [0,[]] ) x (1 + @{$_[0]});
      @p = map { $p[0][0] < $p[1][0] ? [ $_+$p[0][0], [ $_, @{$p[0][1]} ] ] : [ $_+$p[1][0], [ $_, @{$p[1][1]} ] ], (shift @p) x 0 } @{$_} for reverse @{$_[0]};
      say sprintf 'Minimum value %d: [ %s ]', $p[0][0], join ', ', @{$p[0][1]};
      $p[0][0];
    }

Yea I think I just had to review one solution from the misreading that solved the graph interpretation, even if that wasn’t the question asked. It’s a lot more interesting problem.

As far as our level-wise summing minimums go, though, James also gives us options — for either collecting the values to construct the sum as we go, or only keeping the total, or optionally employing the sort trick we say above to obtain a minimum without the use of a separate intermediate variable.

Here’s the version that gathers the elements selected and uses sort, selecting the first element of the resultant list.

    sub min_path_anydir_sort {
      my($res,@order)= 0;
      (push @order, [sort {$a<=>$b} @{$_}]->[0]), $res+=$order[-1] for @{$_[0]};
      say sprintf 'Minimum value %d: [ %s ]', $res, join ', ', @order;
      $res;
   }

Blogs and Additional Submissions in Guest Languages for Task 1:

Abigail

  additional languages: Awk, Bash, C, Lua, Node, Python, Ruby

Colin Crain

  additional languages: Raku

  blog writeup: The Path in the Pyramid - Programming Excursions in Perl and Raku

Lubos Kolouch

  additional languages: Java, Php, Python

Peter Campbell Smith

  blog writeup: Lots of angles this week

Simon Green

  additional languages: Python

  blog writeup: Weekly Challenge 152



TASK 2

Rectangle Area

Submitted by: Mohammad S Anwar

You are given coordinates bottom-left and top-right corner of two rectangles in a 2D plane.

Write a script to find the total area covered by the two rectangles.

Example 1:

        Input: Rectangle 1 => (-1,0), (2,2)
               Rectangle 2 => (0,-1), (4,4)


    Output: 22

Example 2:

        Input: Rectangle 1 => (-3,-1), (1,3)
               Rectangle 2 => (-1,-3), (2,2)


    Output: 25

about the solutions

Abigail, Alexander Pankoff, Athanasius, Cheok-Yin Fung, Colin Crain, Dave Jacoby, Duncan C. White, E. Choroba, Flavio Poletti, James Smith, Jorg Sommrey, Laurent Rosenfeld, Lubos Kolouch, Peter Campbell Smith, PokGoPun, Robert DiCicco, Roger Bell_West, Simon Green, Ulrich Rieke, and W. Luis Mochan

The description text for task 2 this week is delightfully terse. It sounds complete, but immediately raises questions in its brevity. However on closer reading it turns out we have all the information we need to define the challenge.

To cut to the chase: the missing pieces of information are that the rectangles are orthogonally aligned and that they may overlap.

The first follows from fact that the rectangles are described using only two points. For this to be enough information to uniquely identify all the vertices, we must be able to assume the locations of the others, which can only be done by restricting the edges to the horizontal and vertical axes. Another way to arrive at the same conclusion is to notice that the terms “bottom-left” and “top-right” cannot be assured to have meaning if we allow the rectangle to rotate, as for instance the topmost corner will alternate between being to the left or right of the geometric center.

I’ll also note that should we allow the rectangles to have any orientation on the 2D plane the problems becomes, while not inpossible, seriously more difficult. As in outright hard. I’d probably start with a whole lot of triangles and see where that led me. It sounds really complicated, frankly.

But enough about problems we won’t be solving today. The second inference, that the rectangles overlap, is what amused me by it’s absence. If we don’t allow for this then the problem becomes trivial, so of course that’s the real purpose of the task, left unstated for us to figure out. Which I regard as humorously sneaky.

You may have noted by now that my sense of humor is… peculiar might be a good word for it.

There were 20 submissions for the second task this past week.

don’t look BEHIND the MASK, look AT the MASK

Lubos Kolouch, Simon Green, Robert DiCicco, Abigail, Colin Crain, Cheok-Yin Fung, Duncan C. White, Peter Campbell Smith, James Smith, Dave Jacoby, and Jorg Sommrey

Given two well-defined rectangles, orthogonally aligned but located anywhere on a plane, how can we calculate the total area occluded by both rectangles? We have two basic cases: the two can be separate and distict, or the two can overlap in some manner. There are two more cases that reduce to these as well, in that the two can share a common edge over some length, which is the same as the first as the shared edge has no area, and that one rectangle is completely enclosed by the other, which is just maximal overlap. It might, however, if we can detect this condition, be easier to calculate just the area of the enclosing rectangle and call it a day instead of considering the interior rectangle at all.

Lubos Kolouch

  additional languages: Java, Php, Python

Rectangles are shapes, and shapes are objects, so it makes a certain amount of sense to start this discussion off with a couple of object-oriented approaches.

Lubos abstracts his data through multiple structures. He formalizes his rectangles into proper Rectangle objects, and then in turn defines these by two Point objects, which are themselves specified by the four input coordinates. The Rectangle object has an get_area() method, computing the height times width, and also a get_total_area() method that, when given two rectangles, computes the overlapped area and subtracts it from the sum of the two rectangle areas considered independantly. In this way the operlapped area is only counted once.

    package Rectangle;
    use Moose;
    use List::Util qw/min max/;

    has 'left'  => ( is => 'rw', isa => 'Point' );
    has 'right' => ( is => 'rw', isa => 'Point' );

    sub get_area {
        my $self = shift;
        return abs( $self->left->x - $self->right->x ) * abs( $self->left->y - $self->right->y );
    }

    sub get_total_area {

        my ( $first, $second ) = @_;

        my $area1 = $first->get_area;
        my $area2 = $second->get_area;

        # calculate the overlapping area

        my $x_dist = min( $first->right->x, $second->right->x ) - max( $first->left->x, $second->left->x );
        my $y_dist = min( $first->right->y, $second->right->y ) - max( $first->left->y, $second->left->y );
        my $area_i = 0;
        $area_i = $x_dist * $y_dist if ( $x_dist > 0 ) and ( $y_dist > 0 );

        return $area1 + $area2 - $area_i;
    }

Simon Green

  additional languages: Python

  blog writeup: Weekly Challenge 152

Simon gives us a another, slightly different object abstraction , creating his Rectangle directly from the four given coordinates. A Rectangle also carries an area() method to calculate the height times width.

In the main package, he aquires the area of the two rectangles considered independently, and then determines the possible overlapping ranges along each of the x and y axes. If there are positive in both, then the furthest extents are then used to create a new Rectangle, which has an area that is subtracted from previous sum.

It’s worth noting that the two rectangles can overlap in one axis and completely miss each other in the other, for instance when two equally-sized shapes are stacked vertically one above the other without touching. In that case the x-axis overlap would be maximal, but along the y-axis this would be 0, as we stated they do not meet.

    sub main {
        my $s       = shift;
        my @numbers = ( $s =~ m/(-?\d+)/g );

        # Find out the combined area of the two rectangles

        my $rect1 = Rectangle->new( @numbers[ 0 .. 3 ] );
        my $rect2 = Rectangle->new( @numbers[ 4 .. 7 ] );
        my $area  = $rect1->area + $rect2->area;

        # Calculate any overlap

        my $dx1 = max( $rect1->{x1}, $rect2->{x1} );
        my $dy1 = max( $rect1->{y1}, $rect2->{y1} );
        my $dx2 = min( $rect1->{x2}, $rect2->{x2} );
        my $dy2 = min( $rect1->{y2}, $rect2->{y2} );

        # If there is, remove that from the calculations

        if ( $dx1 < $dx2 and $dy1 < $dy2 ) {
            my $d = Rectangle->new( $dx1, $dy1, $dx2, $dy2 );
            $area -= $d->area();
        }

        say $area;
    }

Robert DiCicco

In the problem we are told that we are given two rectangles defined by four coordinates, but not actually told how this gift is to be accomplished. As really the problem could be considered not to be about that detail, we can then conclude that we already have our rectangles, and not ask too many questions about how exactly they came into our possession.

“No officer, I’ve never seen those rectangles before in my life. They were sitting there when I got here”.

That’s the spirit.

We’ll, since they’re mysteriously here and all, we might as well put them to use. Robert has decided to determine the area of their composite shape.

Now we already know they’re rectangles, and we also know the area of a rectangle is its height times its width. We don’t really need a proper Rectangle object to work with these ideas. Robert chooses not to bother with that, instead getting straight to the task at hand. All he decides he needs are two functions: area() to find the area of a single rectangle thing — defined as an array of four coordinates — and overlap which takes two rectangle array references and finds the scalar value of the overlap, if any. Should there be no overlap this value is simply 0.

As the rectangles are things, arrays of four values, they are like objects to some degree but are not blessed. They could well be objects but theres little no real need for them to be.

    sub area {
        my $box  = $_[0];
        my $xval = abs( @$box[0] ) + abs( @$box[2] );
        my $yval = abs( @$box[1] ) + abs( @$box[3] );
        return ( $xval * $yval );
    }

    # get area of overlapping rectangle

    sub overlap {
        my $r1      = $_[0];
        my $r2      = $_[1];
        my $x_dist  = ( min( @$r1[2], @$r2[2] ) - max( @$r1[0], @$r2[0] ) );
        my $y_dist  = ( min( @$r1[3], @$r2[3] ) - max( @$r1[1], @$r2[1] ) );
        my $overlap = 0;

        if ( $x_dist > 0 and $y_dist > 0 ) {
            $overlap = $x_dist * $y_dist;
        }

        return $overlap;
    }

    my @rec1 = ( ( -3, -1 ), ( 1, 3 ) );
    my @rec2 = ( ( -1, -3 ), ( 2, 2 ) );

    my $a = area( \@rec1 );
    my $b = area( \@rec2 );
    my $ov = overlap( \@rec1, \@rec2 );

    my $total = $a + $b - $ov;

Abigail

  additional languages: Awk, Bash, C, Lua, Node, Python, Ruby

Abigail has found a decidedly minimal solution, doing away with all abstraction whatsoever by directly addressing the array of 8 coordinate values given on the input line. The 8 values represent, of course, four coordinate pairs, each pair of these describing one of two rectangles. They are delivered in a set order, and are accessed directly.

I’ll let them describe their logic:

    We calculate the total area as the sum of the areas of each
    rectangle, minus the area of the overlap.
    The area of a rectangle is the product of the absolute difference of the
    x-coordinates and the absolute difference of the y-coordinates.
    The absolute difference of two numbers can be calculated by subtracting
    the minimum of the two from the maximum of the two.
    To calculate the area of the overlap, we multiply the overlap in the
    x-dimension, and the overlap in the y-dimension. To get the overlap
    in the x-dimension, we take the minimum of the max x-values of both
    rectangles, and subtract the maximim of the min x-values of both
    rectangles. If this value is less than 0, the overlap is 0.
    For the overlap in the y-dimension, we do the same with the y values.

    min (@F [0, 2]):  Smallest x-value for first  rectangle
    max (@F [0, 2]):  Largest  x-value for first  rectangle
    min (@F [1, 3]):  Smallest y-value for first  rectangle
    max (@F [1, 3]):  Largest  y-value for first  rectangle
    min (@F [4, 5]):  Smallest x-value for second rectangle
    max (@F [4, 5]):  Largest  x-value for second rectangle
    min (@F [6, 7]):  Smallest y-value for second rectangle
    max (@F [6, 7]):  Largest  y-value for second rectangle

A source reference is provided in the script.

Here’s how they translated that into Perl code. It’s delightful.

    use List::Util qw [min max];

    while (<>) {my @F = split; # Mimic -a


        say        +(max (@F [0, 2]) - min (@F [0, 2]))  *
                    (max (@F [1, 3]) - min (@F [1, 3]))  +
                    (max (@F [4, 6]) - min (@F [4, 6]))  *
                    (max (@F [5, 7]) - min (@F [5, 7]))  -
        max (0, min (max (@F [0, 2]),  max (@F [4, 6]))  -
                max (min (@F [0, 2]),  min (@F [4, 6]))) *
        max (0, min (max (@F [1, 3]),  max (@F [5, 7]))  -
                max (min (@F [1, 3]),  min (@F [5, 7])));

    }

Colin Crain

  additional languages: Raku

  blog writeup: Who’s Masking the Mask? - Programming Excursions in Perl and Raku

For my own solution I saw the problem in terms of Venn diagrams, or set theory. What we want is the union of the two rectangle areas. Considering the areas to be collections of square units, we wish to count each unit included exactly once and only once, even if the same unit is shared within both rectangles. The union, then, is the area occupied solely by one rectangle, plus the area occupied solely by the other, plus the area occupied by both. However without calculating the excluded areas we can also say that the overlapping area, summing the rectangles, will be counted twice. Thus if we can find out what this area is we can then subtract it from the total to find the proper result.

In other words the union is the area of shape A plus the area of shape B minus the intersection of A and B.

A ∪ B = A + B - ( A ∩ B )

I broke the problem down into four functions: area() to compute an area of a rectangle, union() to apply the equation above, and intersect(), which uses its helper _overlap to determines the positive overlap of two intervals along a numerical axis. In intersect() the x- and y-axis overlaps are multiplied, and if both are positive this will return the area of the intersection.

    sub area ( $rect ) {
        my $x = abs( $rect->[0][0] - $rect->[1][0] );
        my $y = abs( $rect->[0][1] - $rect->[1][1] );
        return $x * $y;
    }

    sub intersect ( $rect1, $rect2 ) {
        _overlap( [ map { $_->[0] } $rect1->@* ],       ## rect 1 x-axis

                  [ map { $_->[0] } $rect2->@* ] )      ## rect 2 x-axis

            *
        _overlap( [ map { $_->[1] } $rect1->@* ],       ## rect 1 y-axis

                  [ map { $_->[1] } $rect2->@* ] );     ## rect 2 y-axis

    }

    sub union ( $rect1, $rect2 ) {
          area ($rect1)
        + area ($rect2)
        - intersect ($rect1, $rect2) ;
    }

    sub _overlap ( $r1, $r2 ) {
    ## ranges are ordered 2-element tuples [start,end] : end > start

    ## no order is assumed between the two ranges $r1 and $r2

    ## returns absolute value of overlapping range

    ## there are five cases total:

    ##  1.  no overlap

    ##  2.  A overlaps start of B

    ##  3.  B overlaps start of A

    ##  4.  A completely encloses B

    ##  5.  B completely encloses A


        ## sort the ranges by start point (merge cases 2+3 and 4+5)

        $r1->[0] > $r2->[0] and ( $r1, $r2 ) = ( $r2, $r1 );

        $r2->[0] > $r1->[1]
            ? 0                             ## no overlap (1)

            : abs( $r2->[0]
                - ( $r2->[1] > $r1->[1]
                    ? $r1->[1]              ## intersection (2+3)

                    : $r2->[1] )  )         ## A encloses B (4+5)

    }

Cheok-Yin Fung

CY has taken a remarkably different approach than what we’ve seen so far. Each dimension is occupied by four points, the limits of the two rectangles. Taking the upper and lowermost values in each of the dimensions will then describe a rectangle that circumscribes the composite shape of the two input rectangles taken together. Each face of this rectangle will contain within it two additional points along it, corresponding to the unused edges of the original shapes. Whether the rectangles overlap or not does not matter, as there will in any case be two internal points. The points may themselves lay atop each other and occupy the same coordinate but will still be considered separately.

Thus the enclosing large shape can be divided into an irregular 3 x 3 grid of smaller rectangles, each one of which can be part of one rectangle, the other, no rectangle or both. Any sub-shape not included in either one rectangle or the other is not added to the sum.

The only complication is really a simplification, for the case when one rectangle completely encloses the other. When this happens only the area of the outer rectangle is used.

    sub rect_area {
        my @A, my @B;
        # ($lA,   $bA,   $rA,   $tA,   $lB,   $bB,   $rB,   $rB)

          ($A[0], $A[1], $A[2], $A[3], $B[0], $B[1], $B[2], $B[3]) = @_;

        my @w = sort {$a<=>$b} ($A[0], $A[2], $B[0], $B[2]);
        my @h = sort {$a<=>$b} ($A[1], $A[3], $B[1], $B[3]);

        my $area = 0;

        for my $i (0..2) { for my $j (0..2) {
            my $inA =    ($w[$j] >= $A[0]) && ($A[2] >= $w[$j+1])
                      && ($h[$i+1] <= $A[3]) && ($A[1] <= $h[$i]);

            my $inB =    ($w[$j] >= $B[0]) && ($B[2] >= $w[$j+1])
                      && ($h[$i+1] <= $B[3]) && ($B[1] <= $h[$i]);

            if ($inA || $inB) {
                $area += ($w[$i+1]-$w[$i])*($h[$j+1]-$h[$j]);
            }
        }}

    =pod
        Except the case that one rectangle is inside the other rectangle,
        the two rectangles can be enscribed into a larger rectangle
        Dividing the large rectangle into 9 small rectangular regions,
        then check one by one if a small region is inside rect A or rect B.
        If yes, add the area of the small region into $area.
    =cut

        return $area;
    }

Duncan C. White

Duncan has decided he wants his input configured as four comma-separated coordinate pairs, two for each rectangle separated by spaces. These are assigned to variables $x and $y sub-named 1 through 4. The rectangles are ordered, and their points reassigned so they read left-to-right, which makes tracking the overlap considerably less complicated.

    # make sure that x1 <= x2. y1 <= y2, x3 <= x4, y3 <= y4

    ($x1,$x2) = ($x2,$x1) if $x1 > $x2;
    ($y1,$y2) = ($y2,$y1) if $y1 > $y2;
    ($x3,$x4) = ($x4,$x3) if $x3 > $x4;
    ($y3,$y4) = ($y4,$y3) if $y3 > $y4;

    my $r1area = ($x2-$x1)*($y2-$y1);
    my $r2area = ($x4-$x3)*($y4-$y3);
    say "r1 area: $r1area, r2 area: $r2area" if $debug;

    # overlap:

    my $oxd = min($x2,$x4) - max($x1,$x3);
    my $oyd = min($y2,$y4) - max($y1,$y3);
    my $oarea = ($oxd>0 && $oyd>0) ? $oxd * $oyd : 0;

    say "overlapping area: $oarea" if $debug;

    my $total = $r1area + $r2area - $oarea;

    say $total;

Peter Campbell Smith

  blog writeup: Lots of angles this week

Peter stores his rectangles as nested hashes, which makes the assignment of coordinates easy to follow.

Well, that and the nicely commented code, which is always welcome from my end. I think the action is well and clearly laid out so I’ll just leave this here:

       # Assume no overlap for now

       $area = area('one') + area('two');

       # The rectangles may overlap if the bottom of rect1 is below the top of rect2

       # and the top of rect1 is above the bottom of rect2

       $vert_overlap = $rect{one}{b} < $rect{two}{t} && $rect{one}{t} > $rect{two}{b};

       # They may also overlap if the right of rect1 is to the right of the left of rect2

       # and the left of rect1 is to the left of the right side of rect2

       $horiz_overlap = $rect{one}{r} > $rect{two}{l} && $rect{one}{l} < $rect{two}{r};

       # They do overlap if both of these conditions are true

       if ($vert_overlap && $horiz_overlap) {

           # ... and the overlap area is bounded by the greater of the bottoms,

           # the lesser of the tops, the leftmost of the rights and the rightmost

           # of the lefts

           $rect{overlap}{b} = $rect{one}{b} > $rect{two}{b} ? $rect{one}{b} : $rect{two}{b};
           $rect{overlap}{t} = $rect{one}{t} > $rect{two}{t} ? $rect{two}{t} : $rect{one}{t};
           $rect{overlap}{l} = $rect{one}{l} > $rect{two}{l} ? $rect{one}{l} : $rect{two}{l};
           $rect{overlap}{r} = $rect{one}{r} > $rect{two}{r} ? $rect{two}{r} : $rect{one}{r};

           $area -= area('overlap');
       } else {
           say qq[   No overlap];
       }

       say qq[Output: $area];

James Smith

  blog writeup: Perl Weekly Challenge #152

James is back with a brutally-compressed block of… symbols of some sort.

Let’s look at the lines:

  1. (l)eft, (b)ottom, (r)ight, and (t)op, in two flavors, lower- and UPPERCASE
  2. overlap range along the horizontal axis
  3. overlap range along the vertical axis
  4. return rectangle area sum minus overlap area, but only if both overlaps are positive.

The conditionals coupled to the assignment in line 4 will return 0 if they fail, so in the case of a negative overlap — that is the rectangles are separated and do not meet by some distance, nothing is subtracted.

It could be construed as a little intimidating, but dissects nicely.

    sub my_area {
      my ($l,$b,$r,$t,$L,$B,$R,$T) = map { @{$_} } @_;
      my $w3 = (my $w1=$r-$l)+(my $w2=$R-$L)+($l<$L?$l:$L)-($r<$R?$R:$r);
      my $h3 = (my $h1=$t-$b)+(my $h2=$T-$B)+($b<$B?$b:$B)-($t<$T?$T:$t);
      $w1*$h1 + $w2*$h2 - ($w3>0 && $h3>0 && $w3*$h3);
    }

Athanasius

  additional languages: Raku

One quality of the monk’s submissions is their generally extensive notes to their process. Here they have included an ASCII diagram as a visual aid, something I considered doing in the introduction but apparently never got around to. In any case it’s welcome here, along with a detailed explanation of the logic behind selecting out the overlap based on the horizontal and vertical ranges.

    Consider 2 rectangles:

        +------------a1
        |             |
        |       c-----K--------b1
        |       |     |         |
        a0------J-----d         |
                |               |
                |               |
                b0--------------+

    The overlapping area, rectangle cJdK, has corners (J, K) where:
        the x-coordinate of J is the larger  of a0x and b0x;
        the y-coordinate of J is the larger  of a0y and b0y;
        the x-coordinate of K is the smaller of a1x and b1x; and
        the y-coordinate of K is the smaller of a1y and b1y.

    This holds true for all configurations of the 2 rectangles, provided that K is
    above and to the right of J; otherwise, the rectangles do not overlap.

    The combined area is the sum of the areas of the 2 rectangles less the area of
    their overlap (if any).

With that explanation out in front, following the steps in the implementation is quite straightforward.

    MAIN:
    {
        my ($a0x, $a0y, $a1x, $a1y, $b0x, $b0y, $b1x, $b1y) = parse_command_line();

        print "Input:  Rectangle 1 => ($a0x, $a0y), ($a1x, $a1y)\n";
        print "        Rectangle 2 => ($b0x, $b0y), ($b1x, $b1y)\n";

        my $area1 = ($a1x - $a0x) * ($a1y - $a0y);
        my $area2 = ($b1x - $b0x) * ($b1y - $b0y);

        # Common/shared rectangle

        my $c0x   = max( $a0x, $b0x );
        my $c0y   = max( $a0y, $b0y );
        my $c1x   = min( $a1x, $b1x );
        my $c1y   = min( $a1y, $b1y );
        my $area3 = ($c1x > $c0x &&  $c1y > $c0y) ?
                    ($c1x - $c0x) * ($c1y - $c0y) : 0;

        my $total =  $area1 + $area2 - $area3;

        print "\nOutput: $total\n";

        print "\nExplanation\n-----------\n",
                "The rectangles have areas of $area1 and $area2, ",
                "respectively,\n",
                "and a shared (i.e., overlapping) area of $area3\n",
                "Total area is $area1 + $area2 - $area3 = $total\n";
    }

Jorg Sommrey

Finally we’ll end with a look at Jorg’s submission, because, well, it’s different. Not completley different, mind you, as it shares a basic analysis with CY’s solution above.

In CY’s breakdown, if you recall, we circumscribed the compound shape of the two possibly-overlapping rectangles with a new outer boundary rectangle, then sliced this along the remaining interior edges to form an irregular lattice of 9 sub-spaces.

For any of you finding that description hard to follow, Jorg has provided a reference using box-drawing characters:

    Original rectangles in bold lines within the surrounding rectangle and
    the product of the subrectangle's area and the OR'ed characteristic
    functions inside the subrectangle.

    4 ┌───┲━━━━━━━┯━━━━━━━┓
      │   ┃       │       ┃
    3 │ 0 ┃   4   │   4   ┃
      │   ┃       │       ┃
    2 ┢━━━╋━━━━━━━╅───────┨
      ┃   ┃       ┃       ┃
    1 ┃ 2 ┃   4   ┃   4   ┃
      ┃   ┃       ┃       ┃
    0 ┡━━━╋━━━━━━━╃───────┨
      │ 0 ┃   2   │   2   ┃
    -1└───┺━━━━━━━┷━━━━━━━┛
     -1   0   1   2   3   4

See it now?

What Jorg has done next is to generalize the process to an arbitrary number of rectangles.

    Generalizing the task to N rectangles.
    These rectangles, given by their bottom left and upper right vertices,
    are contained within a unique minimal rectangle. This outer rectangle
    may be divided into (N + 1)² (possibly empty) subrectangles given by
    all possible x and y coordinates from the original rectangles.  Each
    of these subrectangles has a clear relation to any of the original
    rectangles: either they are disjoint or the subrectangle is a subset
    of the original.  In other words: the characteristic function of any
    given rectangle never changes inside such subrectangle. For each
    subrectangle, its center coordinates and its area is recorded.  Then
    the sum is taken over the areas of subrectangles having their center
    within any of the original rectangles.  See picture [ed note: above].
    This is essentially the surface integral of the OR'ed characteristic
    functions over the xy-plane.

Ok that’s getting a little hairy again, I admit. The “characteristic function” is coderef to an anonymous function closed around the boundary points of a specific rectangle. Handing this a different rectangle will return a 1/0 evaluation of whether it overlaps with the rectangle defined in the closure.

If a rectangle overlaps with any other rectangle then it’s part of the larger shape and its area is added to the total. Otherwise its area is kept to 0 when all the subrectangles are summed.

    # Find the total area of N (possibly overlapping) rectangles.

    sub rectangle_area (@vertices) {
        # Find grid subrectangles: center and area.

        my $subrect = subrect(@vertices);
        # Create characteristic functions for all given rectangles.

        my @chi = pairmap {gen_chi($a, $b)} @vertices;
    say $_ for @chi;
    say $chi[0]->($b->[0]->@*);

        # Get the sum of subrectangle areas within original rectangles.

        reduce {$a + $b->[1] * any {$_->($b->[0]->@*)} @chi} 0, @$subrect;
    }

    # Generate the characteristic function for a rectangle with given bottom

    # left and top right vertices.

    sub gen_chi ($bl, $tr) {
        sub ($x, $y) {
            $bl->[0] <= $x &&
            $x <= $tr->[0] &&
            $bl->[1] <= $y &&
            $y <= $tr->[1];
        }
    }

    # For N given rectangles, record center coordinates and area for all

    # subrectangles build from the rectangles' coordinate grid.

    sub subrect (@vertices) {
        my @x = sort {$a <=> $b} map $_->[0], @vertices;
        my @y = sort {$a <=> $b} map $_->[1], @vertices;

        gather {
            for (my $ix = 0; $ix < $#x; $ix++) {
                my $dx = $x[$ix + 1] - $x[$ix];
                for (my $iy = 0; $iy < $#y; $iy++) {
                    my $dy = $y[$iy + 1] - $y[$iy];
                    take [[$x[$ix] + $dx / 2, $y[$iy] + $dy / 2],
                        $dx * $dy];
                }
            }
        }
    }

Blogs and Additional Submissions in Guest Languages for Task 2:

Alexander Pankoff

  blog writeup: Challenge 152 Task #2 - Untangling the rects

Dave Jacoby

  blog writeup: Think Inside The Box: Weekly Challenge #152 Pt. 2 | Committed to Memory

Flavio Poletti

  additional languages: Raku

  blog writeup: PWC152 - Rectangle Area - ETOOBUSY

Laurent Rosenfeld

  additional languages: Raku

  blog writeup: Perl Weekly Challenge 152: Triangle Sum Path

Roger Bell_West

  additional languages: Javascript, Kotlin, Lua, Postscript, Python, Raku, Ruby, Rust

  blog writeup: RogerBW’s Blog: The Weekly Challenge 152: Triangles, Rectangles, and Objects

Ulrich Rieke

  additional languages: C++, Haskell, Raku

W. Luis Mochan

  blog writeup: Perl Weekly Challenge 152 – W. Luis Mochán



 

 

 

 

_________ THE BLOG PAGES _________


That’s it for me this week, people! Warped by the rain, driven by the snow, resolute and unbroken by the torrential influx, by some miracle I somehow continue to maintain my bearings.

Looking forward to next wave, the perfect wave, I am: your humble servant.

But if Your Unquenchable THIRST for KNOWLEDGE is not SLAKED,

then RUN (dont walk!) to the WATERING HOLE

and FOLLOW these BLOG LINKS:

( …don’t think, trust your training, you’re in the zone, just do it … )

Alexander Pankoff

Arne Sommer

Colin Crain

Dave Jacoby

Flavio Poletti

James Smith

Laurent Rosenfeld

Luca Ferrari

Peter Campbell Smith

Roger Bell_West

Simon Green

W. Luis Mochan

SO WHAT DO YOU THINK ?

If you have any suggestions or ideas then please do share with us.

Contact with me