( …continues from previous week. )
Welcome to the Perl review pages for Week 165 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.
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 finally 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.
• Task 1 • Task 2 • BLOGS •
TASK 1
Scalable Vector Graphics (SVG)
Submitted by: Ryan J Thompson
Scalable Vector Graphics (SVG) are not made of pixels, but lines, ellipses, and various curves that can be scaled to any size without any loss of quality. If you have ever tried to resize a small JPG or PNG, you know what I mean by “loss of quality”! What many people do not know about SVG files is, they are simply XML files, so they can easily be generated programmatically.
For this task, you may use external library, such as Perl’s SVG library, maintained in recent years by our very own Mohammad S Anwar. You can instead generate the XML yourself; it’s actually quite simple. The source for the example image for Task #2 might be instructive.
Your task is to accept a series of points and lines in the following format, one per line, in arbitrary order:
Point: x,y
Line: x1,y1,x2,y2
Example:
53,10
53,10,23,30
23,30
Then, generate an SVG file plotting all points, and all lines. If done correctly, you can view the output .svg file in your browser.
about the solutions
Adam Russell, Athanasius, Cheok-Yin Fung, Colin Crain, Dave Jacoby, Duncan C. White, E. Choroba, Flavio Poletti, James Smith, Jorg Sommrey, Julien Fiegehenn, Laurent Rosenfeld, Peter Campbell Smith, Roger Bell_West, Ryan Thompson, Saif Ahmed, Ulrich Rieke, W. Luis Mochan, and Wanderdoc
The SVG file format is a document object model for a drawing, described in an XML file. Object primitives are established on a nominally-sized canvas with relevant attributes such as fill color, line width or radius, which are taken by an external rasterizer as directives to reproduce the desired drawing on demand. Attribute dimensions are given in pixels, which are taken in the relative context to the default canvas size. This allows unlimited scaling of the final rendering, as a 10 pixel diameter circle in a 100 pixel wide canvas will always scale at 10% of the drawn width whatever it is resized to.
There were 19 submissions for our first humble task this past week.
A SELECTION of SUBMISSIONS
Dave Jacoby, Ryan Thompson, Cheok-Yin Fung, Julien Fiegehenn, Adam Russell, Laurent Rosenfeld, Duncan C. White, Colin Crain, Flavio Poletti, E. Choroba, and James Smith
The SVG file format, as Ryan notes, is in its basic form straightforward to anyone familiar with XML markup: tags define elements, with balanced tags acting as logical containers passing their attributes on to whatever elements are found inside. Ultimately we end up with lists of elements to be drawn, each with a variety of attributes, specified or default, that are used to produce the stylistic choices desired.
CPAN has an excellent SVG module, which provides a nice object-oriented interface to building an image. However with the simple data provided it was not a very complicated task to concatenate an XML string directly, copying the boilerplate headers. Of course SVG can get complicated in its less common edge cases but none of that was to be seen here.
In a third way, several people built their own object interfaces with a minimal API to get the job done, providing easy expansion should the need ever arise: just write a new method for whatever element is desired.
blog writeup: Plotting Revenge: Weekly Challenge #165 | Committed to Memory
Dave will start us off today with a demonstration of using the SVG module, which provides a convenient object framework for producing the drawing code.
The instantiated object will take care of all the necessary boilerplate in the headers and such, so we can get right to adding line and point elements as we read the input data, here given as a configuration string. How we get the string isn’t really important.
We get two points and a line, and Dave declares an arbitrarily-sized canvas of 100 pixels square. Looping through the input data we find two lists of 2 numbers, and one of 4, so we call add_line()
once and add_point()
twice. The complicated part. if you want to call it that, is building a hash of attributes to adequately describe the element we wish to draw.
When we’re ready we can call xmlify()
on our object which returns a string of XML, that in this case is printed to file. After all, it’s just text.
use SVG;
my $config = <<'END';
53,10
53,10,23,30
23, 30
END
my @config = grep { /\d/ } split /\n/, $config;
my $svg = SVG->new(
height => 100,
width => 100,
);
for my $entry (@config) {
my @coords = map { int $_ } split /,/, $entry;
add_line( $svg, \@coords ) if scalar @coords == 4;
add_point( $svg, \@coords, 'red' ) if scalar @coords == 2;
}
my $output = $svg->xmlify;
say $output;
exit;
sub add_line ( $svg, $coords, $color = 'black' ) {
$svg->line(
x1 => $coords->[0],
y1 => $coords->[1],
x2 => $coords->[2],
y2 => $coords->[3],
style => {
stroke => $color,
'stroke-width' => 0.5,
fill => $color,
}
);
}
sub add_point ( $svg, $coords, $color = 'black' ) {
$svg->circle(
cx => $coords->[0],
cy => $coords->[1],
r => 0.5,
style => {
stroke => $color,
'stroke-width' => 0.5,
fill => $color,
}
);
}
blog writeup: PWC 165 › Simple SVG generator - Ryan J Thompson
Ryan introduces the group container for elements. Groups have their own style attributes, which are inherited by any elements contained within the group. Here we’ve decided what a “point” will look like and so we can define each as and x- and y- coordinate with a radius.
Likewise the lines are given their own group as well with their own style attributes.
You’ll notice there will be a lot of hardcoding attributes into the final script, perhaps more than seems proper. After all, abstract constants and config information, right? Isn’t that just good form? The problem here is that there just are a lot of attributes to consider — it’s aesthetic choices we’re making and there are a large number of options available to us. We could remove them to a YAML file I suppose, or even in some cases CSS, which is pretty cool — don’t get me wrong — but in a sense that’s just complexifying what we need to do whilst kicking the can down the road. So we may as well hard-code it for these demonstrations.
sub plot_svg {
my $svg = SVG->new(width => $o{width}, height => $o{height},
$o{credits} ? () : (-nocredits => 1));
# Style the points and lines
my $lg = $svg->group( id => 'lines',
'stroke-width' => $o{'stroke-width'},
stroke => $o{'line-color'});
my $pg = $svg->group( id => 'points',
fill => $o{'point-color'});
# Plot points and lines
for (@_) {
$lg->line( x1 => $_->[0], y1 => $_->[1],
x2 => $_->[2], y2 => $_->[3]) if @$_ == 4;
$pg->circle(cx => $_->[0], cy => $_->[1],r => $o{radius}) if @$_ == 2;
}
$svg->xmlify;
}
blog writeup: CY’s Take on The Weekly Challenge #165
Let’s look at one more base example using the SVG module, this time from CY. One observation that CY draws attention to is that the SVG drawing described in the XML is built up in the order that the elements are placed; later elements are stacked above those placed earlier and hence will overwrite them in the Z-dimension. The SVG module makes sure to preserve this ordering, which may not be immediately obvious using the object interface.
As such she makes sure to draw her points after her lines, as to not have the possibility of lines overwriting them. This is well-thought-out design.
while (<>) {
chomp;
my @nums = split ",", $_;
if (scalar @nums == 2) {
push @points, [@nums];
}
elsif (scalar @nums == 4) {
new_line(@nums);
}
}
new_point($_->@*) foreach @points; #so that the lines won't be over points
sub new_point {
my ($cx, $cy) = ($_[0], $_[1]);
$img->circle(cx => $cx, cy => $cy, r => 2, style => {fill => 'red'});
}
sub new_line {
my ($x1, $y1, $x2, $y2) = ($_[0], $_[1], $_[2], $_[3]);
$img->line( x1 => $x1, y1 => $y1, x2 => $x2, y2 => $y2,
style => {
'fill-opacity' => 0,
'stroke' => 'green'
});
}
print $img->xmlify(-namespace=>'svg');
additional languages: Typescript
Taking the abstraction one level further we have Julien, who collects all his graphic-construction processing into a personal module, Plot.pm
, that itself then calls SVG
to handle the image format. Wheels within wheels, people. It’s turtles all the way down.
Plot::plot( grep ref, map { chomp; [ split /,/ ]; } <DATA> );
__DATA__
53,10
53,10,23,30
23,30
From Plot.pm
:
sub plot {
my (@input) = @_;
my $svg = SVG->new;
my $points = $svg->group(
id => 'points',
style => {
stroke => 'orange',
fill => 'orange'
},
);
my $lines = $svg->group(
id => 'lines',
style => {
stroke => 'blue',
fill => 'blue',
'stroke-width' => 3,
},
);
foreach my $record (@input) {
if ( @$record == 2 ) {
$points->circle( cx => $record->[0], cy => $record->[1], r => 3 );
}
else {
$lines->line(
x1 => $record->[0],
y1 => $record->[1],
x2 => $record->[2],
y2 => $record->[3]
);
}
# update the image size
$size = max($size, @$record);
}
. . .
# write to disk
open my $fh, '>', 'output.svg' or die $!;
print $fh $svg->xmlify;
}
In passing I will note she also uses our own Choroba’s (versus anyone else’s Choroba) handy ARGV::OrDATA
module, which will preferentially take input to the diamond operator from STDIN
on the command line or in the absence of this open the __DATA__
filehandle and draw from there.
additional languages: Prolog
blog writeup: SVG Plots of Points and Lines — Perl — RabbitFarm
blog writeup: SVG Plots of Points and Lines — Prolog — RabbitFarm
The simple SGV image requested (Image? Design? Drawing plans? We’re really blurring the lines here) only has a few elements and is consequently not very complicated. Thus if we can manage to properly construct only two types of element tags and a set of whatever headers are required then we can just assemble the parts in order to match the format and have our complete list of instructions.
Ok, let’s have at it.
Adam modularizes the process into a collection of subroutines, with a wrapper, svg()
to wrangle them. The first thing on making a new image is to make identifying headers, so svg_begin()
returns these and they are saved as a string.
The data has been read in and placed in an array of arrays, one per line, and it is passed to a loop to be cycled through. A conditional on the length of the sub-array is used to determine whether a line is a point or a line and the appropriate routine is called with the array data, and the result concatenated to the SGV string. On completion, a call to svg_end()
closes the outer tag.
That’s it. That’s all you need.
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd"> <svg height="100%" width="100%" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<circle cx="53" cy="10" r="1" /><line x1="53" x2="23" y1="10" y2="30" style="stroke:#006600;" /><circle cx="23" cy="30" r="1" /></svg>
Yes it’s not exactly the prettiest thing for a human to read, but the machines? They don’t care. Some of the more arrogant ones among them even seem to think this makes them superior. Well, let them say that when I’m holding a power plug in my hand is all I have to say. That usually settles them down. Life, I say, is better when we can just get along.
sub svg_begin{
return <<BEGIN;
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd"> <svg height="100%" width="100%" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
BEGIN
}
sub svg_end{
return "</svg>";
}
sub svg_point{
my($x, $y) = @_;
return "<circle cx=\"$x\" cy=\"$y\" r=\"1\" />";
}
sub svg_line{
my($x0, $y0, $x1, $y1) = @_;
return "<line x1=\"$x0\" x2=\"$x1\" y1=\"$y0\" y2=\"$y1\" style=\"stroke:#006600;\" />";
}
sub svg{
my @lines = @_;
my $svg = svg_begin;
for my $line (@_){
$svg .= svg_point(@{$line}) if @{$line} == 2;
$svg .= svg_line(@{$line}) if @{$line} == 4;
}
return $svg . svg_end;
}
One thing though, is that since Perl 5.26 we can now say
say <<~'BEGIN';
...some stuff that you don't want indented in the output...
BEGIN
and this will ignore any leading whitespace before the closing delimiter on every line in the heredoc. Which is great for embedding heredocs inside structures that are themselves already indented, such as subroutines or loops. Note the placement of the added tilde. Super handy.
additional languages: Raku
blog writeup: Perl Weekly Challenge 165: Line of Best Fit
Here’s another example of direct construction from Laurent. Again, we only have to handle two types of elements so it really isn’t very complicated.
use constant SCALE => 5;
my ( @points, @lines);
my $out = qq{<svg xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink" width="500" height="500">\n};
my @input = qw<53,10 53,10,23,30 23,30 34,35,36>;
for my $val (@input) {
my @items = split /,/, $val;
# say "@items";
if (@items == 2) {
make_point(@items)
} elsif (@items == 4) {
make_line(@items);
} else {
warn "Error on item ", @items;
}
}
$out .= "</svg>";
say $out;
sub make_point {
my @dots = map $_ * SCALE, @_;
my $point = qq{<circle cx= "$dots[0]" cy="$dots[1]" r="3" fill="forestgreen"/>\n};
$out .= $point;
}
sub make_line {
my @dots = map $_ * SCALE, @_;
my $line = qq{<line x1="$dots[0]" y1="$dots[1]" x2="$dots[2]" y2="$dots[3]" };
$line .= qq{stroke="navy" />\n};
$out .= $line
}
additional languages: C
Duncan takes a similar stance of assembling a collection of subroutines to produce the various tags required, with the added abstraction of squirreling them away in their own module, MySVG.pm
. The aim of this module is to print the various SVG tags directly to STDOUT
using say
.
The result is to divide the computational portion of the processing, which in this case comprises processing the input and gathering the maximum height and width values to properly size the canvas, and then handing these variables off to the module functions to process the output. It’s just compartmentalization by use; the subroutines are exported and used directly in the main body of the script.
Duncan uses Function::Parameters
with their fun
keyword to get subroutines signatures.
my( $w, $h ) = @wh{qw(W H)};
$w += (10-$w%10);
$h += (10-$h%10);
my $xscale = $w < 200 ? 200/$w : 1;
my $yscale = $h < 200 ? 200/$h : 1;
warn "maxxy rectangle is w $w, h $h, xscale $xscale, yscale $yscale\n" if $debug;
start_svg( $w * $xscale, $h * $yscale );
setcolour( "blue" );
setlinewidth( 1 );
line( $_->[0]*$xscale, $_->[1]*$yscale,
$_->[2]*$xscale, $_->[3]*$yscale ) foreach @ln;
setcolour( "orange" );
point( $_->[0]*$xscale, $_->[1]*$yscale ) foreach @pt;
end_svg();
For my own solution I choses a similar approach to structuring the processing, inlining a separate SVG package, but this time making a Moo
object.
It was a fun exercise, albeit longer and more complicated than it needed to be to get the job done.
I think you probably get the idea by now.
my $svg = new SVG(200,300);
my $attr;
$attr = { "stroke" => "blue", "stroke-width" => "4" };
$svg->group( "lines", $attr);
$svg->add( $svg->line($_->@*) ) for @lines;
$attr = { "fill"=>"red" };
$svg->group( "circle", $attr);
$svg->add( $svg->circle($_->@*) ) for @points;
my $out = $svg->make_SVG;
additional languages: Raku
blog writeup: PWC165 - Scalable Vector Graphics (SVG) - ETOOBUSY
blog writeup: PWC165 - Line of Best Fit - ETOOBUSY
All in all, though, with all of this conceptual streamlining of the process and considering how the API can mirror the data being represented, there’s always going to be a few that, shall we say, go the other way.
As you may have noticed, I have on several occasions used the term “boilerplate” in referring to the SVG header section of an image. Aside from the the specific height and width information for the canvas, this is largely accurate.
Likewise the circle
and line
elements are self-similar as well, only filling in the various coordinates and radius in the case of the circle.
So why not fill in a template?
Using his own Template::Perlish
module, this is what Flavio does. Bringing in a templating framework might seem excessive, and Flavio himself would likely agree with this sentiment. But should you wish to draw a number of lines atop a field of tiny circles using fixed fill and stroke choices, then just reach for this template — it’ll do just what you ask.
After the setup, of course.
use Template::Perlish;
my $input = shift // "53,10\n53,10,23,30\n23,30";
say svg_for($input);
sub svg_for ($text) {
state $tp2 = Template::Perlish->new->compile_as_sub(
q{<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg height="400" width="400" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg">
[% for my $item (A 'lines') {
if ($item->@* == 2) {
%] <circle r="4" cx="[%= $item->[0] %]" cy="[%= $item->[1] %]" stroke-width="0" fill="#000000" />
[% } else {
%] <polyline points="[%= join ' ', $item->@* %]" stroke="#ff0000" stroke-width="6" />
[% }
}
%]</svg>});
$tp2->({lines => [map {[ split m{,+}mxs ]} split m{\n+}mxs, $text]});
}
On top of this, if we consider Flavio’s effort excusable due to, say, low blood sugar or perhaps an unexpected blow to the head, we have oddly enough received a second templating solution from none other than Choroba. Perhaps there is something airborne wafting across Europe that someone should really know about. Or aliens. Can’t rule that out. It’s… disturbing. I know a guy — I’ll make some calls and get back to you.
In any case here we are, using the full-sized Template
module. Boilerplate, plug in some variables, put some sections on repeat according to arrays of lines and points, and bang you’re in.
Ok, I must say I’m coming around to the idea. Have to say did not see that one coming, though.
my $TEMPLATE = << '__SVG__';
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg height="[% height %]" width="[% width %]" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
[% IF lines -%]
<g id="lines" stroke="#369" stroke-width="4">
[%- FOREACH line IN lines %]
<line x1="[% line.0 %]" x2="[% line.2 %]" y1="[% line.1 %]" y2="[% line.3 %]" />
[%- END %]
</g>
[% END- %]
[%- IF points -%]
<g fill="#f73" id="points">
[%- FOREACH point IN points %]
<circle cx="[% point.0 %]" cy="[% point.1 %]" r="3" />
[%- END %]
</g>
[%- END %]
</svg>
__SVG__
my $template = 'Template'->new;
$template->process(\$TEMPLATE,
{height => $height,
width => $width,
points => \@points,
lines => \@lines}
) or die $template->error;
blog writeup: The Weekly Challenge 165 - straight through the point!
Finally, we’ll close with James, who takes the ideas we’ve seen today and implements all of them. Unsatisfied with solving this task he goes on to solve the next one as well, in the same script.
It’s kind of involved.
The script itself has a lot of parts, as we have noted it includes code for finding a best-fit line as well, which we’ll put aside for now. But as the second task requests that we use the code from the first, both tasks are merged together.
So let’s look at the SGV code portion, shall we?
my $DEF = { 'margin' => 40, 'max_w' => 960, 'max_h' => 540,
'color' => '#000', 'stroke' => 3,
'fill' => '#ccc', 'radius' => 10,
'border' => '#000', 'bg' => '#eee' };
my $LINE_TEMPLATE = '<line x1="%s" y1="%s" x2="%s" y2="%s" />';
my $POINT_TEMPLATE = '<circle cx="%s" cy="%s" r="%s" />';
my $SVG_TEMPLATE = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg height="%s" width="%s" viewBox="%s %s %s %s" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink">
<rect stroke="%s" stroke-width="%s" fill="%s" x="%s" y="%s" width="%s" height="%s" />
<g transform="scale(1,-1) translate(0,%s)">
<g stroke="%s" stroke-width="%s">
%s
</g>
<g fill="%s">
%s
</g>
</g>
</svg>';
sub render_svg {
my( $ps, $ls, $config ) = @_;
my %conf = (%{$DEF}, %{$config});
my( $min_x, $max_x, $min_y, $max_y ) = get_ranges( $ps, $0 eq 'ch-2.pl' ? [] : $ls );
my $margin = $conf{'margin'};
## Adjust height and width so it fits the size from the config.
my($W,$H,$width,$height) = ($conf{'max_w'},$conf{'max_h'},$max_x-$min_x+2*$margin,$max_y-$min_y+2*$margin);
( $width/$height > $W/$H ) ? ( $H = $height/$width*$W ) : ( $W = $width/$height*$H );
## Calculate the scale factor so that we keep spots/lines the same size irrespective of the ranges.
my $sf = $width/$W;
sprintf $SVG_TEMPLATE,
$H, $W, $min_x - $margin, $min_y - $margin, $width, $height, ## svg element
$conf{'border'}, $sf, $conf{'bg'}, $min_x - $margin, $min_y - $margin, $width, $height, ## bg rect
-$min_y-$max_y,
$conf{'color'}, $conf{'stroke'} * $sf, join( qq(\n ), map { sprintf $LINE_TEMPLATE, @{$_} } @{$ls} ), ## lines
$conf{'fill'}, join( qq(\n ), map { sprintf $POINT_TEMPLATE, @{$_}, $conf{'radius'}*$sf } @{$ps} ) ## points
}
As you can see James has hit the middle ground in defining sub-templates for the individual elements as sprintf
formats. Then after gathering all necessary data into variables, a system of nested sprintf
statements renders the final XML code.
It’s a worthy effort. Magnificent, really.
Blogs and Additional Submissions in Guest Languages for Task 1:
blog writeup: Dots, lines and whatever fits best
additional languages: Javascript, Kotlin, Lua, Postscript, Python, Raku, Ruby, Rust
blog writeup: RogerBW’s Blog: The Weekly Challenge 165: Scaling the Fits
blog writeup: Perl Weekly Challenge 165 – W. Luis Mochán
TASK 2
Line of Best Fit
Submitted by: Ryan J Thompson
When you have a scatter plot of points, a line of best fit is the line that best describes the relationship between the points, and is very useful in statistics. Otherwise known as linear regression.
The method most often used is known as the least squares method, as it is straightforward and efficient, but you may use any method that generates the correct result.
Calculate the line of best fit for the following 48 points:
333,129 39,189 140,156 292,134 393,52 160,166 362,122 13,193 341,104 320,113 109,177 203,152 343,100 225,110 23,186 282,102 284,98 205,133 297,114 292,126 339,112 327,79 253,136 61,169 128,176 346,72 316,103 124,162 65,181 159,137 212,116 337,86 215,136 153,137 390,104 100,180 76,188 77,181 69,195 92,186 275,96 250,147 34,174 213,134 186,129 189,154 361,82 363,89
Using your rudimentary graphing engine from Task #1, graph all points, as well as the line of best fit.
about the solutions
Adam Russell, Athanasius, Cheok-Yin Fung, Colin Crain, Dave Jacoby, Duncan C. White, E. Choroba, Flavio Poletti, James Smith, Jorg Sommrey, Julien Fiegehenn, Laurent Rosenfeld, Peter Campbell Smith, Roger Bell_West, Ryan Thompson, Saif Ahmed, Ulrich Rieke, W. Luis Mochan, and Wanderdoc
The Least Squares method is a technique in linear regression for fitting a line through a plot of data such that the difference between the line points and the orignal data points is minimized across the line. As such whilst controlling one axis, the differences between the other in the points and the line model are gathered and squared, and the sum of these squares is minimized, hence the name.
For most datasets, this technique produces a very good approximation of the “best” line through the data, but that statement naturally leads to questions on the meaning of “best”. The context of the data itself, and the conclusions that we wish to draw, ultimately guide the answer. For instance, a linear fit may not even be the most useful visualization at all; parabolic or more complex curves will obviously be a better approximation for some data.
Variants to the linear least squares technique allow for schemes compensating for known properties in error ranges in the data, or when the data is affected by known constraints.
Using the least-squares approximation, the squares of the differences between the known values and those in the approximated line are used because by squaring the deviations larger values are given a higher negative weight than smaller, and the model will look for a line with many small differences and steer away from larger gaps, providing a satisfying middle result. That said it is often beneficial to prune obviously suspect outliers, whose errors might be explained by external forces.
There were 19 submissions for the second task this past week.
A SELECTION of SUBMISSIONS
Colin Crain, Athanasius, Wanderdoc, Saif Ahmed, Ulrich Rieke, Laurent Rosenfeld, Duncan C. White, Cheok-Yin Fung, Roger Bell_West, Jorg Sommrey, and W. Luis Mochan
Even though it was not required, nearly every submission implemented a least-squares regression for their fitting, with only one exception. We’ll certainly look at that. On the other hand, seeing as we’re doing data analysis we did see several submissions using the PDL . We’ll have a look at those too, to spice things up a bit.
We’ll start this out with my own submission. After all, it has the basic building blocks of a least-squares fit all laid out in what I hope is a clear manner.
You’ll have to be the judge of that, though. I try, and my job is to do my best to explain it.
The least-squares line of best fit for a given set of N individual points can be derived using two formulas, one each for the slope (m) and intercept (b), to form a line equation of the form
y = m x + b
Those formulae require four independent summations from the x- and y-coordinates, and give a slope m:
N Σ(xy) − Σx Σy
m = —————————————————
N Σ(x^2) − (Σx)^2
and an intercept b:
Σy − m Σx
b = ———————————
N
The data is delivered as an array of arrays, with each outer element a pair of x and y coordinates. Looping across this list, we compile the four sums:
- all x-values
- all y values
- for each pair, the x-value squared and the results summed
- for each pair, the x-value times the y-value
The size of the data set is already known from the scalar value of the variable @data
.
Once we have the slope and the intercept we can calculate y-values for an appropriately-sized canvas to enclose all the points, in this case 400 (nominal) pixels wide.
A few more commands are required to plug the input data along with the result into the SVG code from task 1.
sub best_fit (@points) {
my ($xsum, $ysum, $sqsum, $xysum);
for (@data) {
$xsum += $_->[0];
$ysum += $_->[1];
$sqsum += $_->[0] ** 2;
$xysum += $_->[0] * $_->[1];
}
my $slope = ((@data * $xysum) - ($xsum * $ysum))
/ ((@data * $sqsum) - ($xsum ** 2));
my $intercept = ($ysum - ($slope * $xsum)) / @data;
## hardwired 400px canvas for now
my $xmax = 400;
## x1, y1, x2, y2
return (0, $intercept, 400, ($slope * 400 + $intercept))
}
In their extremely well laid-out version, the monk Athanasius really works the “one line, one action” philosophy.
I approve.
They also take a moment to gather the minimum and maximum x-values from the data set, allowing them to calculate the endpoints of the fit line with more precision, instead of simply overshooting as I did.
MAIN:
{
my $sum_x = sum( map { $_->[ 0 ] } @POINTS );
my $sum_y = sum( map { $_->[ 1 ] } @POINTS );
my $sum_x_sq = sum( map { $_->[ 0 ] * $_->[ 0 ] } @POINTS );
my $sum_x_y = sum( map { $_->[ 0 ] * $_->[ 1 ] } @POINTS );
my $N = scalar @POINTS;
my $m = (($N * $sum_x_y ) - ($sum_x * $sum_y)) /
(($N * $sum_x_sq) - ($sum_x * $sum_x));
my $B = ($sum_y - ($m * $sum_x)) / $N;
my %elements;
push @{ $elements{ points } }, [ @$_ ] for @POINTS;
my $x1 = (min map { $_->[ 0 ] } @POINTS) - 1; # min x
my $y1 = ($m * $x1) + $B;
my $x2 = (max map { $_->[ 0 ] } @POINTS) + 1; # max x
my $y2 = ($m * $x2) + $B;
push @{ $elements{ lines } }, [ $x1, $y1, $x2, $y2 ];
ch_1::encode_svg( \%elements, $OUTFILE );
print qq[SVG encoded to file "$OUTFILE"\n];
}
We haven’t looked at the work from the wandering doctor in a while — here they demonstrate how to use the reduce()
function from List::Util
to create their own sum()
. The reduce()
function, if you recall, merely provides a framework for systematically accessing the elements in an array, processing each and modifying a single carried accumulator that is ultimately returned. By carefully choosing the block function that is applied, many common list functions can be created. Here they provide several examples, first to only add the first element positions from the list of points, and then in a different pass sum the second. A third version just sums the elements as found.
The common method for determiing the least-squares line calculates the slope and intercept equation; the doc then passes this data to a separate create_line()
function to determine the two endpoints required for an SVG line
tag.
sub calculate_best_fit
{
my @points = @_;
my $N = scalar @points;
# Step 1: For each (x,y) point calculate x^2 and xy:
my @x_sq = map $_->{x} * $_->{x}, @points;
my @xy = map $_->{x} * $_->{y}, @points;
# Step 2: Sum all x, y, x^2 and xy:
my $sum_x = reduce { $a + $b->{x} } 0, @points;
my $sum_y = reduce { $a + $b->{y} } 0, @points;
my $sum_x_sq = reduce {$a + $b } @x_sq;
my $sum_xy = reduce {$a + $b} @xy;
# Step 3: Calculate slope m:
my $m = ($N * $sum_xy - $sum_x * $sum_y) /
($N * $sum_x_sq - ($sum_x * $sum_x));
# Step 4: Calculate Intercept b:
my $intrcpt = ($sum_y - $m * $sum_x) / $N;
return ($m, $intrcpt);
}
sub create_line
{
my ( $x1, $x2, $slp, $itr ) = @_;
my $y1 = int( $slp * $x1 + $itr );
my $y2 = int( $slp * $x2 + $itr );
return {x1 => $x1, y1 => $y1, x2 => $x2, y2 => $y2};
}
New member Saif focuses on the output SVG drawing, treating the calculation of the best-fit line to be another required element in the final XML to be parameterized.
As such the control flow is a little different, focusing first on the construction of the SGV drawing as an elaborate concatenated string. In a first pass over the data, a pair of regular expresions match out line and point coordiantes and append them as elements to a growing string of XML tags. As every point is added, however, its data is also handed off to an addPoint()
routine where the sums for the least-squares calculations are gathered in global variables.
Once every point in the dataset is processed the best-fit line can be calculated from the aggregate analysis variables, which happens in-place as a new SVG tag is being constructed. The tag is added, the SVG format is completed with head and tail tags and the result written to file.
foreach (@data) {
# find and genrate lines
$svg.= " <line x1=\"$2\" y1=\"$3\" x2=\"$4\" y2=\"$5\" stroke=\"red\"\/>\n"
while (/(^|\s)([\-\=]?[\d]+\.?[\d]*),([\-\=]?[\d]+\.?[\d]*),([\-\=]?[\d]+\.?[\d]*),([\-\=]?[\d]+\.?[\d]*)($|\s)/g);
# find and generate points adding to analysis if needed
while (/(^|\s)([\-\=]?[\d]+\.?[\d]*),([\-\=]?[\d]+\.?[\d]*)($|\s)/g){
addPoint($2,$3);
$svg.= " <circle cx=\"$2\" cy=\"$3\" r=\"2\" stroke=\"blue\" \/>\n"
};
}
$svg.= bestFitLine();
# top and tail the svg contents, and flip the data so that the SVG coordinate system becomes cartesian
$svg="<svg viewBox=\"".($minX-8)." ".($minY-8)." ".($maxX-$minX+16)." ".($maxY-$minY+16)."\" style=\"background-color:green\" xmlns=\"http://www.w3.org/2000/svg\">\n".
"<g transform=\"scale(1,-1) translate(0,".(-$maxY-$minY).")\">\n". # flips the points for a cartesian chart
" <rect x=\"".($minX-5)."\" y=\"".($minY-5)."\" width=\"".($maxX-$minX+10)."\" height=\"".($maxY-$minY+10)."\" stroke=\"blue\" fill=\"#D5DBDB\" \/>\n".
$svg.
"\n</g>\n".
"<text x=\"".(($minX+$maxX)/2)."\" y=\"".($minY+20)."\" fill=\"red\">$title</text>\n".
"</svg>";
# each point found is added to the stats
sub addPoint{
my ($x,$y)=@_;
$maxX= $x if ($x>$maxX);
$maxY= $y if ($y>$maxY);
$minX= $x if ($x<$minX);
$minY= $y if ($y<$minY);
$sumX+=$x;
$sumY+=$y;
$sumX2+=$x*$x;
$sumXY+=$x*$y;
$number++;
return 1;
}
In an alternative to reading points from a file, or commonly the inlne __DATA__
filehandle, Ulrich has us enter any number of points from the command line, via <STDIN>
.
On receiving the data, he can then compute the best-fit line, which he does in a separate findline()
routine. A number of map
statements loop over the data to compute the various sums required. The slope and intercept are returned.
He then gets to work immediately on constructing an SVG file, opening a filehandle and then outputting the XML tags to it as they are created. The data has been parsed and readied, so after the header information, the line is output, followed by a list of points.
sub findLine {
my $passedPoints = shift ;
my $sumOfX = sum( map { $_->[0] } @{$passedPoints}) ;
my $sumOfY = sum( map { $_->[1] } @{$passedPoints}) ;
my $sumOfXY = sum( map { $_->[0] * $_->[1] } @{$passedPoints}) ;
my $sumOfXsquare = sum( map { $_->[0] * $_->[0] } @{$passedPoints}) ;
my $len = scalar( @{$passedPoints} ) ;
my $m = ($len * $sumOfXY - $sumOfX * $sumOfY ) / ( $len *
$sumOfXsquare - $sumOfX ** 2 ) ;
my $b = ( $sumOfY - $m * $sumOfX ) / $len ;
return ( $m , $b ) ;
}
. . .
my ( $m , $b ) = findLine( \@points ) ;
say "The points entered are best fitted by a line with the equation y = $m x + $b!" ;
open ( my $fh , ">> testfile.svg" ) or die "Can't append to testfile.svg! $!\n" ;
say $fh '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' ;
print $fh '<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN"' ;
say $fh ' "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">' ;
print $fh '<svg height="300" width="400" xmlns="http://www.w3.org/2000/svg"' ;
say $fh ' xmlns:svg="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">' ;
say $fh '<g id="lines" stroke="#369" stroke-width="4">' ;
my $startx = 0 ;
my $endx = 400 ;
my $starty = $m * $startx + $b ;
my $endy = $m * $endx + $b ;
say $fh " <line x1=\"$startx\" x2=\"$endx\" y1=\"$starty\" y2=\"$endy\" />" ;
say $fh "</g>" ;
say $fh "<g fill=\"#f73\" id=\"points\">" ;
for my $p ( @points ) {
say $fh " <circle cx=\"$p->[0]\" cy=\"$p->[1]\" r=\"3\" />" ;
}
say $fh " </g>" ;
say $fh "</svg>" ;
close $fh ;
additional languages: Raku
blog writeup: Perl Weekly Challenge 165: Line of Best Fit
blog writeup: Perl Weekly Challenge 165: Scalable Vector Graphics
One could argue, I suppose, that the graphical output of an SVG image was only a suggestion, not a requirement. It is sort of tacked on there, at the bottom of the description, and easy to miss. As this happened several times I’ve decided I’ll be gracious about it and look the other way. Life is… variable, as are my moods.
Without that code the computation of the least-squares line is much more compact, as demonstrated here by Laurent. Furthermore, without the need to produce (x,y) endpoints for a line we can directly report the slope/intercept equation.
Slope: -0.299956500261231, intercept = 200.132272535582
The equation of the line of best fit is: y = -0.30 x + 200.13
Laurent’s lsm()
routine is quite direct and to-the-point:
sub lsm {
my @points = @_;
my ($s_x, $s_y, $s_xy, $s_x2) = (0, 0, 0, 0);
for my $point (@points) {
my ($x, $y) = ($point->[0], $point->[1]);
$s_x += $x;
$s_y += $y;
$s_xy += $x * $y;
$s_x2 += $x ** 2;
}
my $n = scalar @points;
my $slope = ($n * $s_xy - $s_x * $s_y) / ($n * $s_x2 - $s_x ** 2);
my $intercept = ($s_y - $slope * $s_x) / $n;
return $slope, $intercept;
}
additional languages: C
With his SVG creation code locked off in a separate personal module, the remaining best-fit calculation code for Duncan’s submission is quite compact as well, only, you know, he actually checks all the boxes for the task. Oh well. Good for him.
Duncan, to those not familiar, is fond of Function::Parameter
to provide subroutine signatures in his code; these structures are rebranded using the fun
function keyword. So that’s what that’s all about.
TIMTOWTDI.
Carrying on, he demonstrates again is a straightforward fashion how creating his line is just an exercise of creating some aggregate data and filling it into find the slope and intercept parameters on a line equation.
The interface to his SGV creation code is quite interesting. I especially likes the bounding rectangle — that was a nice touch.
fun bestfit( @data )
{
my $sumx = 0;
my $sumy = 0;
my $sumxy = 0;
my $sumx2 = 0;
foreach my $pt (@data)
{
my( $x, $y ) = @$pt;
$sumx += $x;
$sumy += $y;
$sumxy += $x * $y;
$sumx2 += $x * $x;
}
my $n = @data;
warn "bestfit: n=$n, sumx=$sumx, sumy=$sumy, sumxy=$sumxy, sumx^2=$sumx2\n" if $debug;
my $m = ($n * $sumxy - $sumx * $sumy) / ($n * $sumx2 - $sumx * $sumx);
my $c = ($sumy - $m * $sumx) / $n;
warn "bestfit: m=$m, c=$c\n" if $debug;
return( $m, $c );
}
my( $m, $c ) = bestfit( @pt );
my $y1 = $m + $c; # for x==1
my $y2 = $m * ($w-1) + $c; # for x==w-1
start_svg( $w * $xscale, $h * $yscale );
setcolour( "black" );
setlinewidth( 1 );
rect( 0, 0, $w-1, $h-1 );
setcolour( "blue" );
setlinewidth( 1 );
line( 1, $y1*$yscale, ($w-1)*$xscale, $y2*$yscale );
setcolour( "orange" );
point( $_->[0]*$xscale, $_->[1]*$yscale ) foreach @pt;
end_svg();
additional languages: Julia
blog writeup: CY’s Take on The Weekly Challenge #165
Mathematical equations are CY’s bread-and-butter, so she was quite at home with this little foray into statistics and implementing equations, right? Ok, sure. No problem. The mathematical realm is a uniquely complicated place, full of incomprehensible beauty and speechless terror. Perhaps statistics is not exactly in her wheelhouse, as she says.
How true her statement is remains uncertain to me, as she makes short work of the required calculations in any case.
In truth I ws hoping for an explanation as to how squaring the data seems to hit an ideal “sweet spot” in weighting towards small divergences and against outlier data far from the model projection. We could, after all, use cubes and get more compression, or logs and get less, but the least squares method seems to be universally accepted. I really haven’t had time to study this question myself, and at the moment, much as I’d like to go down that rabbit-hole I need to exercise some discipline and get this review published.
Maybe next time, or maybe someone could point me to a paper.
my $n = scalar @points;
my $slope =
( $n * sum(map {$_->[0]*$_->[1]} @points) # n sum(xy) - sum(x) sum(y)
- sum(map{$_->[0]} @points) * sum(map{$_->[1]} @points) )
/ ( $n * sum(map{$_->[0]**2} @points) # n sum(sq x) - sq(sum x)
- (sum( map { $_->[0]} @points ))**2) ;
my $intercept =
( sum( map {$_->[1]} @points ) # sum(y) - slope * sum(x)
- $slope * sum( map {$_->[0]} @points) )
/ $n ;
sub y_best_fit {
my $x = $_[0];
return $slope*$x + $intercept;
}
my $minx = min( map {$_->[0]} @points );
my $maxx = max( map {$_->[0]} @points );
my $line_of_best_fit =
join ",", $minx,y_best_fit($minx),$maxx,y_best_fit($maxx);
open FH, "> BEST_FIT_DATA" or die $!;
say FH $line_of_best_fit;
say FH $_->[0],",",$_->[1] for @points;
additional languages: Javascript, Kotlin, Lua, Postscript, Python, Raku, Ruby, Rust
blog writeup: RogerBW’s Blog: The Weekly Challenge 165: Scaling the Fits
The only submission to stray from the Least Squares technique when fitting their lines was Roger, who brings us something known as the “Theil–Sen estimator”.
And what is that? No idea. Let’s find out.
The Theil–Sen estimator works by calculating the slope between every pair of two points in the dataset and then calculating the median of all of these slopes.
Well that does sound like it will work, doesn’t it? And it does, and well. It is both efficient and apparently particularly robust in tolerating outlier points, compared to a standard least squares calculation.
use List::Util qw(min max);
foreach my $i (0..$#points-1) {
foreach my $j ($i+1..$#points) {
if ($points[$i][0] != $points[$j][0]) {
push @slope,($points[$j][1]-$points[$i][1])/($points[$j][0]-$points[$i][0])
}
}
}
my $m = median(\@slope);
my $c = median([map {$_->[1] - $m*$_->[0]} @points]);
my @x=map {$_->[0]} @points;
my @l;
foreach my $xb (min(@x),max(@x)) {
push @l,$xb;
push @l,$xb*$m+$c;
}
print join(',',@l),"\n";
sub median($s0) {
my @s=sort {$a <=> $b} @{$s0};
return $s[int((scalar @s)/2)];
}
Jorg brings us PDL::Slatec
- a PDL interface to the slatec numerical programming library.
Despite the Perl Data Language being built for data processing, it will not do a least-squares analysis out-of-the-box. There are however several module options to supply this functionality, and as noted Jorg has picked PDL::Slatec
, a perl interface to the FORTRAN SLATEC statistical library. For those curious, SLATEC is derived from “Sandia, Los Alamos, [Air Force Weapons Laboratory] Technical Exchange Committee”.
The library provides a method, polyfit()
, which given the point data set, an array of weights, and a maximum exponent to allow, will find the best line available through the points. Note we could for instance give this function a 2 and it would look for the best parabolic curve.
Here though, we are asking for straight line, so we pass it 1.
The polyfit()
function returns a solution in an internal representation to describe the line. We can then gather the two points with minimal and maximal x-coordinates using the minmax
function, and hand them to a second SLATEC function, polyvalue()
to get a vector of y values from the $fit
line function. Very nice.
Now that we have our line as 2 coordinate endpoints, we can draw it.
sub least_square_linear_regression ($fh) {
# Slurp the file content, remove trailing whitespace, convert
# whitespace to semicolons and use the resulting string as a piddle
# constructor argument. This results in a 2xN piddle of coordinate
# pairs.
my $points = do {
local $/;
pdl <$fh> =~ s/\s+\z//r =~ s/\s+/;/gr
};
# Split the points' coordinates in separate piddles along the first
# dimension resulting in piddles for x and y values as required by
# "polyfit". Then find the best fit with a polynomial of degree one
# (i.e. a line). Here only the solution's internal representation
# is needed as the result.
my $fit = (polyfit $points->xchg(0, 1)->dog, ones($points->dim(1)), 1)[3];
# Get the x range.
my $x = pdl minmax $points->slice(0);
# Get the corresponding y values.
my $y = (polyvalue(1, 0, $x, $fit))[0];
# Create a line piddle having x and y as endpoint coordinates.
my $line = pdl($x, $y)->xchg(0, 1);
if (wantarray) {
# Flatten the line piddle.
return $line->list;
} else {
# Convert the line endpoints (2x2) into the shape expected by
# gen_svg (2x2x1) from task 1 and generate an SVG from the given
# points and the regression line.
return gen_svg($points, $line->dummy(2));
}
}
blog writeup: Perl Weekly Challenge 165 – W. Luis Mochán
And finally we’ll end with Luis, with a different approach to using the PDL.
Luis simply builds the required variables from scratch and installs them into equations to find slope and intercept coordinates for our line. Given these he can then plug minimal and maximal x-values into the results to calculate the related ys. Luis has opted not to draw the graphic, but delivered us a list of points and lines that can be fed to the previous program.
use PDL;
my $input=pdl($ARGV[0]);
my $N=$input->dim(1); # number of points
my $sum=$input->transpose->sumover;
my ($sum_x, $sum_y)=$sum->list;
my $sum_2=($input**2)->transpose->sumover; # sum of squares
my ($sum_x_2, $sum_y_2)=$sum_2->list;
my $sum_xy=$input->prodover->sumover; # sum of xy
my $det=$N*$sum_x_2-$sum_x**2;
die "Singular system" if $det==0;
my $slope=($N*$sum_xy-$sum_x*$sum_y)/$det;
my $intercept=($sum_x_2*$sum_y-$sum_x*$sum_xy)/$det;
say join ",", @$_ for @{$input->unpdl}; # output points
my $x=$input->slice("(0)"); # x coords
my ($y0, $y1)=map {$slope*$_+$intercept} (my ($x0,$x1)=($x->minimum,$x->maximum));
say "$x0, $y0, $x1, $y1";
Blogs and Additional Submissions in Guest Languages for Task 2:
additional languages: Prolog
blog writeup: SVG Plots of Points and Lines — Perl — RabbitFarm
blog writeup: SVG Plots of Points and Lines — Prolog — RabbitFarm
blog writeup: Plotting Revenge: Weekly Challenge #165 | Committed to Memory
additional languages: Raku
blog writeup: PWC165 - Line of Best Fit - ETOOBUSY
blog writeup: The Weekly Challenge 165 - straight through the point!
additional languages: Typescript
blog writeup: Dots, lines and whatever fits best
blog writeup: PWC 165 › Simple SVG generator - Ryan J Thompson
_________ 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 … )
Adam Russell
- SVG Plots of Points and Lines — Perl — RabbitFarm ( Perl )
- SVG Plots of Points and Lines — Prolog — RabbitFarm ( Prolog )
Arne Sommer
Cheok-Yin Fung
Dave Jacoby
Flavio Poletti
- PWC165 - Scalable Vector Graphics (SVG) - ETOOBUSY ( Perl & Raku )
- PWC165 - Line of Best Fit - ETOOBUSY ( Perl & Raku )
James Smith
Laurent Rosenfeld
- Perl Weekly Challenge 165: Line of Best Fit ( Perl & Raku )
- Perl Weekly Challenge 165: Scalable Vector Graphics ( Perl & Raku )
Luca Ferrari
- Perl Weekly Challenge 165: SVG ( Raku )
- Perl Weekly Challenge 165: SVG ( PL/Perl )
- Perl Weekly Challenge 165: SVG ( PL/PostgreSQL )
Peter Campbell Smith
- Dots, lines and whatever fits best ( Perl )
Roger Bell_West
- RogerBW’s Blog: The Weekly Challenge 165: Scaling the Fits ( Perl & Raku )
Ryan Thompson
W. Luis Mochan