aboutsummaryrefslogtreecommitdiff
path: root/challenge-165
diff options
context:
space:
mode:
authorMohammad S Anwar <Mohammad.Anwar@yahoo.com>2022-05-23 01:17:51 +0100
committerGitHub <noreply@github.com>2022-05-23 01:17:51 +0100
commit39a81edb43e1e69d9952d8e9fa7323b494031150 (patch)
treea852efa74f9012e3f19649c65f73f50ac20ceb57 /challenge-165
parent4549a58c8f81306554d7097405ca3cfacc6f3522 (diff)
parent965ff36347baff514295f48e7bf8178bd3e30b54 (diff)
downloadperlweeklychallenge-club-39a81edb43e1e69d9952d8e9fa7323b494031150.tar.gz
perlweeklychallenge-club-39a81edb43e1e69d9952d8e9fa7323b494031150.tar.bz2
perlweeklychallenge-club-39a81edb43e1e69d9952d8e9fa7323b494031150.zip
Merge pull request #6141 from dcw803/master
imported my solutions to the SVG tasks, in C and Perl
Diffstat (limited to 'challenge-165')
-rw-r--r--challenge-165/duncan-c-white/C/.build3
-rw-r--r--challenge-165/duncan-c-white/C/Makefile18
-rw-r--r--challenge-165/duncan-c-white/C/MySVG.c91
-rw-r--r--challenge-165/duncan-c-white/C/MySVG.h9
-rw-r--r--challenge-165/duncan-c-white/C/ch-1.c211
-rw-r--r--challenge-165/duncan-c-white/C/ch-2.c221
-rw-r--r--challenge-165/duncan-c-white/README75
-rw-r--r--challenge-165/duncan-c-white/bestfitdata48
-rw-r--r--challenge-165/duncan-c-white/perl/MySVG.pm123
-rwxr-xr-xchallenge-165/duncan-c-white/perl/ch-1.pl113
-rwxr-xr-xchallenge-165/duncan-c-white/perl/ch-2.pl145
11 files changed, 1027 insertions, 30 deletions
diff --git a/challenge-165/duncan-c-white/C/.build b/challenge-165/duncan-c-white/C/.build
index 7a8295430e..295e8bb4c9 100644
--- a/challenge-165/duncan-c-white/C/.build
+++ b/challenge-165/duncan-c-white/C/.build
@@ -1,2 +1 @@
-BUILD = ch-1
-#CFLAGS = -g
+BUILD = ch-1 ch-2
diff --git a/challenge-165/duncan-c-white/C/Makefile b/challenge-165/duncan-c-white/C/Makefile
new file mode 100644
index 0000000000..fddc090ab6
--- /dev/null
+++ b/challenge-165/duncan-c-white/C/Makefile
@@ -0,0 +1,18 @@
+CC = gcc
+CFLAGS = -Wall
+BUILD = ch-1 ch-2
+
+all: $(BUILD)
+
+clean:
+ /bin/rm -f $(BUILD) *.o core
+
+MySVG.o: MySVG.c MySVG.h
+
+ch-1: ch-1.o MySVG.o
+
+ch-1.o: ch-1.c MySVG.h
+
+ch-2: ch-2.o MySVG.o
+
+ch-2.o: ch-2.c MySVG.h
diff --git a/challenge-165/duncan-c-white/C/MySVG.c b/challenge-165/duncan-c-white/C/MySVG.c
new file mode 100644
index 0000000000..96ec7ce87a
--- /dev/null
+++ b/challenge-165/duncan-c-white/C/MySVG.c
@@ -0,0 +1,91 @@
+// MySVG: simple SVG routines
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "MySVG.h"
+
+/*
+ * start_svg( w, h );
+ * Generate the xml, doctype and svg tags, width $w by height $h
+ */
+void start_svg( int w, int h )
+{
+ printf( "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n" );
+ printf( "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n" );
+ printf( "<svg width=\"%d\" height=\"%d\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\">\n", w, h );
+}
+
+
+
+/*
+ * end_svg();
+ * Generate the end svg tag at the end of our svg document
+ */
+void end_svg( void )
+{
+ printf( "</svg>\n" );
+}
+
+
+static char colour[100] = "red";
+static int linewidth = 4;
+
+
+/*
+ * setcolour( colourname );
+ * Set the current colour.
+ */
+void setcolour( char *colourname )
+{
+ strcpy( colour, colourname );
+}
+
+
+
+/*
+ * setlinewidth( lw );
+ * Set the line width.
+ */
+void setlinewidth( int lw )
+{
+ linewidth = lw;
+}
+
+
+
+
+
+/*
+ * point( x, y );
+ * Plot a point.
+ */
+void point( int x, int y )
+{
+ printf( " <circle cx=\"%d\" cy=\"%d\" r=\"1\" fill=\"%s\" stroke-width=\"0\" />\n", x, y, colour );
+}
+
+
+/*
+ * line( x1, y1, x2, y2 );
+ * Draw a line from (x1,y1) to (x2,y2)
+ */
+void line( int x1, int y1, int x2, int y2 )
+{
+ printf( " <line x1=\"%d\" y1=\"%d\" x2=\"%d\" y2=\"%d\" stroke=\"%s\" stroke-width=\"%d\" />\n",
+ x1, y1, x2, y2, colour, linewidth );
+}
+
+
+/*
+ * rect( x1, y1, x2, y2 );
+ * Draw a rectangle from (x1,y1) to (x2,y2)
+ */
+void rect( int x1, int y1, int x2, int y2 )
+{
+ int w = 1+x2-x1;
+ int h = 1+y2-y1;
+ printf( " <rect x1=\"%d\" y1=\"%d\" width=\"%d\" height=\"%d\" fill=\"white\" stroke=\"%s\" stroke-width=\"%d\" />\n",
+ x1, y1, w, h, colour, linewidth );
+}
diff --git a/challenge-165/duncan-c-white/C/MySVG.h b/challenge-165/duncan-c-white/C/MySVG.h
new file mode 100644
index 0000000000..d74a927127
--- /dev/null
+++ b/challenge-165/duncan-c-white/C/MySVG.h
@@ -0,0 +1,9 @@
+// MySVG: simple SVG routines
+
+extern void start_svg( int w, int h );
+extern void end_svg( void );
+extern void setcolour( char *colourname );
+extern void setlinewidth( int lw );
+extern void point( int x, int y );
+extern void line( int x1, int y1, int x2, int y2 );
+extern void rect( int x1, int y1, int x2, int y2 );
diff --git a/challenge-165/duncan-c-white/C/ch-1.c b/challenge-165/duncan-c-white/C/ch-1.c
new file mode 100644
index 0000000000..b175154f51
--- /dev/null
+++ b/challenge-165/duncan-c-white/C/ch-1.c
@@ -0,0 +1,211 @@
+/*
+ * TASK 1: Scalable Vector Graphics (SVG)
+ *
+ * Example:
+ *
+ * 53,10
+ * 53,10,23,30
+ * 23,30
+ *
+ * GUEST LANGUAGE: This is my attempt to translate ch-1.pl and MySVG.pm into C
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdbool.h>
+#include <ctype.h>
+#include <assert.h>
+
+#include "MySVG.h"
+
+bool debug=0;
+
+// process_noarg( argc, argv );
+// Process the -d flag, and check that there are NO
+// arguments left.
+void process_noarg( int argc, char **argv )
+{
+ int arg=1;
+ if( argc>1 && strcmp( argv[arg], "-d" ) == 0 )
+ {
+ debug = true;
+ arg++;
+ }
+
+ int left = argc-arg;
+ if( left != 0 )
+ {
+ fprintf( stderr,
+ "Usage: mk-svg [-d] < inputfile\n" );
+ exit(1);
+ }
+}
+
+
+typedef struct { int x, y; } datapoint;
+typedef struct { int x1, y1, x2, y2; } dataline;
+
+#define MAXITEMS 1024
+
+int npoints = 0;
+datapoint pt[MAXITEMS]; // input points
+int nlines = 0;
+dataline ln[MAXITEMS]; // lines, each line an [x1,y1,x2,y2] tuple
+
+datapoint wh; // wh.x == width of our data, wh.y == height of our data
+
+
+/*
+ * updatewh( x, y );
+ * Update wh to include x and y.
+ */
+void updatewh( int x, int y )
+{
+ assert( x>=0 );
+ assert( y>=0 );
+ if( x > wh.x ) wh.x = x;
+ if( y > wh.y ) wh.y = y;
+}
+
+
+/*
+ * bool lineread = readline( in, buf, maxlen );
+ * read a single line from in (a FILE * open for input)
+ * into a character buffer (char *), reading no more than
+ * maxlen characters, removing a trailing '\n' from the
+ * buffer (if there is one).
+ *
+ * If the input line is longer than that, the REMAINDER OF
+ * THE LINE IS LEFT IN the file's input buffer for later reading.
+ * If readline() is called again immediately, the remainder of
+ * the line will be treated as a separate line.
+ *
+ * In caller-space, buf must be a writable character buffer of
+ * size >= maxlen, i.e. typically either a char [n]
+ * or a malloc(n * sizeof(char)) chunk,
+ * where (in both cases) n >= maxlen
+ */
+bool readline( FILE *in, char *buf, int maxlen )
+{
+ if( fgets( buf, maxlen, in ) == 0 )
+ {
+ return false;
+ }
+ int l = strlen(buf);
+ if( l>0 && buf[l-1]=='\n' )
+ {
+ buf[l-1] = '\0';
+ }
+ return true;
+}
+
+
+/*
+ *
+ * read_data( in );
+ * Read data (points and lines) from in.
+ */
+void read_data( FILE *in )
+{
+ #define MAXLINELEN 100
+ char line[MAXLINELEN];
+
+ npoints = 0;
+ nlines = 0;
+ while( readline(in, line, MAXLINELEN ) )
+ {
+ int x1, y1, x2, y2;
+ char *s;
+ char *p;
+
+ s = line;
+ assert( isdigit(*s) );
+ p = strchr(s,',');
+ assert( p != NULL );
+ *p = '\0';
+ x1 = atoi(s);
+
+ s = p+1;
+ assert( isdigit(*s) );
+ y1 = atoi(s);
+ p = strchr(s,',');
+
+ if( p != NULL ) // must be a line
+ {
+ *p = '\0';
+
+ s = p+1;
+ assert( isdigit(*s) );
+ p = strchr(s,',');
+
+ assert( p != NULL );
+ *p = '\0';
+ x2 = atoi(s);
+
+ s = p+1;
+ assert( isdigit(*s) );
+ y2 = atoi(s);
+
+ //printf( "debug: read line %d,%d, %d,%d\n", x1, y1, x2, y2 );
+ ln[nlines].x1 = x1;
+ ln[nlines].y1 = y1;
+ ln[nlines].x2 = x2;
+ ln[nlines].y2 = y2;
+ nlines++;
+ updatewh( x1, y1 );
+ updatewh( x2, y2 );
+ } else
+ {
+ //printf( "debug: read point %d,%d\n", x1, y1 );
+ pt[npoints].x = x1;
+ pt[npoints].y = y1;
+ npoints++;
+ updatewh( x1, y1 );
+ }
+ }
+
+}
+
+int main( int argc, char **argv )
+{
+ process_noarg( argc, argv );
+
+ wh.x = -1;
+ wh.y = -1;
+
+ read_data( stdin );
+
+ int w = wh.x;
+ int h = wh.y;
+
+ w += (10-w%10);
+ h += (10-h%10);
+
+ int xscale = w < 200 ? 200/w : 1;
+ int yscale = h < 200 ? 200/h : 1;
+
+ fprintf( stderr, "maxxy rectangle is w %d, h %d, xscale %d, yscale %d\n", w, h, xscale, yscale );
+
+ start_svg( w * xscale, h * yscale );
+
+ setcolour( "blue" );
+ setlinewidth( 1 );
+ for( int i=0; i<nlines; i++ )
+ {
+ // printf( "debug: generating line for ln[%d]: %d,%d, %d,%d\n",
+ // i, ln[i].x1, ln[i].y1, ln[i].x2, ln[i].y2 );
+ line( ln[i].x1*xscale, ln[i].y1*yscale,
+ ln[i].x2*xscale, ln[i].y2*yscale );
+ }
+
+ setcolour( "orange" );
+ for( int i=0; i<npoints; i++ )
+ {
+ point( pt[i].x*xscale, pt[i].y*yscale );
+ }
+
+ end_svg();
+
+ return 0;
+}
diff --git a/challenge-165/duncan-c-white/C/ch-2.c b/challenge-165/duncan-c-white/C/ch-2.c
new file mode 100644
index 0000000000..7a71c5f957
--- /dev/null
+++ b/challenge-165/duncan-c-white/C/ch-2.c
@@ -0,0 +1,221 @@
+/*
+ * Task 2: Line of Best Fit
+ *
+ * GUEST LANGUAGE: This is my attempt to translate ch-2.pl into C
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdbool.h>
+#include <ctype.h>
+#include <assert.h>
+
+#include "MySVG.h"
+
+bool debug=0;
+
+// char *s = process_singlearg( argc, argv );
+// Process the -d flag, and check that there is a single
+// remaining argument, return it
+char * process_singlearg( int argc, char **argv )
+{
+ int arg=1;
+ if( argc>1 && strcmp( argv[arg], "-d" ) == 0 )
+ {
+ debug = true;
+ arg++;
+ }
+
+ int left = argc-arg;
+ if( left != 1 )
+ {
+ fprintf( stderr,
+ "Usage: best-fit [-d] filename\n" );
+ exit(1);
+ }
+
+ // element is in argv[arg]
+
+ if( debug )
+ {
+ printf( "debug: remaining argument is in pos %d, and is %s\n",
+ arg, argv[arg] );
+ }
+
+ return argv[arg];
+}
+
+
+typedef struct { int x, y; } datapoint;
+typedef struct { int x1, y1, x2, y2; } dataline;
+
+#define MAXITEMS 1024
+
+int npoints = 0;
+datapoint pt[MAXITEMS]; // input points
+
+datapoint wh; // wh.x == width of our data, wh.y == height of our data
+
+
+/*
+ * updatewh( x, y );
+ * Update wh to include x and y.
+ */
+void updatewh( int x, int y )
+{
+ assert( x>=0 );
+ assert( y>=0 );
+ if( x > wh.x ) wh.x = x;
+ if( y > wh.y ) wh.y = y;
+}
+
+
+/*
+ * bool lineread = readline( in, buf, maxlen );
+ * read a single line from in (a FILE * open for input)
+ * into a character buffer (char *), reading no more than
+ * maxlen characters, removing a trailing '\n' from the
+ * buffer (if there is one).
+ *
+ * If the input line is longer than that, the REMAINDER OF
+ * THE LINE IS LEFT IN the file's input buffer for later reading.
+ * If readline() is called again immediately, the remainder of
+ * the line will be treated as a separate line.
+ *
+ * In caller-space, buf must be a writable character buffer of
+ * size >= maxlen, i.e. typically either a char [n]
+ * or a malloc(n * sizeof(char)) chunk,
+ * where (in both cases) n >= maxlen
+ */
+bool readline( FILE *in, char *buf, int maxlen )
+{
+ if( fgets( buf, maxlen, in ) == 0 )
+ {
+ return false;
+ }
+ int l = strlen(buf);
+ if( l>0 && buf[l-1]=='\n' )
+ {
+ buf[l-1] = '\0';
+ }
+ return true;
+}
+
+
+/*
+ *
+ * read_data( in );
+ * Read data (points) from in.
+ */
+void read_data( FILE *in )
+{
+ #define MAXLINELEN 100
+ char line[MAXLINELEN];
+
+ npoints = 0;
+ while( readline(in, line, MAXLINELEN ) )
+ {
+ int x, y;
+ char *s;
+ char *p;
+
+ s = line;
+ assert( isdigit(*s) );
+ p = strchr(s,',');
+ assert( p != NULL );
+ *p = '\0';
+ x = atoi(s);
+
+ s = p+1;
+ assert( isdigit(*s) );
+ y = atoi(s);
+
+ //fprintf( stderr, "debug: read point %d,%d\n", x, y );
+ pt[npoints].x = x;
+ pt[npoints].y = y;
+ npoints++;
+ updatewh( x, y );
+ }
+}
+
+
+/*
+ * bestfit( points, npoints, &m, &c );
+ * Calculate y=mx+c, the best fit line of the data (each data point an [x,y] pair)
+ */
+void bestfit( datapoint *pt, int npoints, double *m, double *c )
+{
+ int sumx = 0;
+ int sumy = 0;
+ int sumxy = 0;
+ int sumx2 = 0;
+ for( int i=0; i<npoints; i++ )
+ {
+ int x = pt[i].x;
+ int y = pt[i].y;
+ sumx += x;
+ sumy += y;
+ sumxy += x * y;
+ sumx2 += x * x;
+ }
+
+ *m = (npoints * sumxy - sumx * sumy) / (double) (npoints * sumx2 - sumx * sumx);
+ *c = (sumy - *m * sumx) / (double)npoints;
+
+ if( debug )
+ {
+ fprintf( stderr, "bestfit: n=%d, sumx=%d, sumy=%d, sumxy=%d, sumx^2=%d\n",
+ npoints, sumx, sumy, sumxy, sumx2 );
+ fprintf( stderr, "bestfit: m=%.6f, c=%.6f\n", *m, *c );
+ }
+}
+
+
+int main( int argc, char **argv )
+{
+ char *filename = process_singlearg( argc, argv );
+
+ wh.x = -1;
+ wh.y = -1;
+
+ FILE *in = fopen( filename, "r" );
+ assert( in != NULL );
+ read_data( in );
+ fclose( in );
+
+ int w = wh.x;
+ int h = wh.y;
+
+ w += (10-w%10);
+ h += (10-h%10);
+
+ int xscale = w < 200 ? 200/w : 1;
+ int yscale = h < 200 ? 200/h : 1;
+
+ if( debug )
+ {
+ fprintf( stderr, "maxxy rectangle is w %d, h %d, xscale %d, yscale %d\n", w, h, xscale, yscale );
+ }
+
+ double m, c;
+ bestfit( pt, npoints, &m, &c );
+ int y1 = (int) (m + c); // for x==1
+ int y2 = (int) (m * (w-1) + c); // for x==w-1
+
+ start_svg( w * xscale, h * yscale );
+
+ setcolour( "blue" );
+ setlinewidth( 1 );
+ line( 1, y1*yscale, (w-1)*xscale, y2*yscale );
+
+ setcolour( "orange" );
+ for( int i=0; i<npoints; i++ )
+ {
+ point( pt[i].x*xscale, pt[i].y*yscale );
+ }
+
+ end_svg();
+
+ return 0;
+}
diff --git a/challenge-165/duncan-c-white/README b/challenge-165/duncan-c-white/README
index 06b7a108f4..c04719179d 100644
--- a/challenge-165/duncan-c-white/README
+++ b/challenge-165/duncan-c-white/README
@@ -1,45 +1,64 @@
-TASK #1 - Prime Palindrome
+TASK 1: Scalable Vector Graphics (SVG)
-Write a script to find all prime numbers less than 1000, which are also
-palindromes in base 10. Palindromic numbers are numbers whose digits
-are the same in reverse. For example, 313 is a palindromic prime, but
-337 is not, even though 733 (337 reversed) is also prime.
+Scalable Vector Graphics (SVG) are not made of pixels, but lines,
+ellipses, and 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.
-MY NOTES: ok. Pretty easy.
+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.
+
+MY NOTES: ok. Having read the SVG Wikipedia page, it seems very easy (although I hate XML).
GUEST LANGUAGE: As a bonus, I also had a go at translating ch-1.pl
into C (look in the C directory for that).
-TASK #2 - Happy Numbers
+Task 2: Line of Best Fit
-Write a script to find the first 8 Happy Numbers in base 10. For more
-information, please check out https://en.wikipedia.org/wiki/Happy_number
+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, here is an
+example of what such a line might look like:
-Starting with any positive integer, replace the number by the sum of the
-squares of its digits, and repeat the process until the number equals 1
-(where it will stay), or it loops endlessly in a cycle which does not
-include 1.
+[image showing red best fit line on graph of black data points]
-Those numbers for which this process end in 1 are happy numbers, while
-those numbers that do not end in 1 are unhappy numbers.
+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.
-Example
+Calculate the line of best fit for the following 48 points:
-19 is a Happy Number in base 10, as shown:
+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
-19 => 1^2 + 9^2
- => 1 + 81
- => 82 => 8^2 + 2^2
- => 64 + 4
- => 68 => 6^2 + 8^2
- => 36 + 64
- => 100 => 1^2 + 0^2 + 0^2
- => 1 + 0 + 0
- => 1
+Using your rudimentary graphing engine from Task #1, graph all points, as well as the line of best fit.
-MY NOTES: seems very easy. Loop detection is surely just a set of values
-that we've already seen.
+MY NOTES: seems pretty simple, https://www.mathsisfun.com/data/least-squares-regression.html has a good
+explanation of the method. I've put the above data, one point per line, in "bestfitdata" in the
+top-level directory.
GUEST LANGUAGE: As a bonus, I also had a go at translating ch-2.pl
into C (look in the C directory for that).
diff --git a/challenge-165/duncan-c-white/bestfitdata b/challenge-165/duncan-c-white/bestfitdata
new file mode 100644
index 0000000000..132a3da095
--- /dev/null
+++ b/challenge-165/duncan-c-white/bestfitdata
@@ -0,0 +1,48 @@
+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
diff --git a/challenge-165/duncan-c-white/perl/MySVG.pm b/challenge-165/duncan-c-white/perl/MySVG.pm
new file mode 100644
index 0000000000..23e081c7ac
--- /dev/null
+++ b/challenge-165/duncan-c-white/perl/MySVG.pm
@@ -0,0 +1,123 @@
+package MySVG;
+
+use strict;
+use warnings;
+use feature 'say';
+use Function::Parameters;
+
+require Exporter;
+our @ISA = qw(Exporter);
+our @EXPORT = qw(
+ start_svg
+ expand_macros
+ setcolour
+ setlinewidth
+ point
+ line
+ rect
+ end_svg
+ );
+
+
+=pod
+
+=head2 start_svg( $w, $h );
+
+Generate the xml, doctype and svg tags, width $w by height $h
+
+=cut
+fun start_svg( $w, $h )
+{
+ say qq(<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg width="$w" height="$h" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">);
+}
+
+
+=pod
+
+=head2 end_svg();
+
+Generate the end svg tag at the end of our svg document
+
+=cut
+fun end_svg( )
+{
+ say qq(</svg>);
+}
+
+
+my $colour = "red";
+my $linewidth = 4;
+
+
+=pod
+
+=head2 setcolour( $colourname );
+
+Set the current colour.
+
+=cut
+fun setcolour( $colourname )
+{
+ $colour = $colourname;
+}
+
+
+=pod
+
+=head2 setlinewidth( $n );
+
+Set the line width.
+
+=cut
+fun setlinewidth( $n )
+{
+ $linewidth = $n;
+}
+
+
+
+=pod
+
+=head2 point( $x, $y );
+
+Plot a point.
+
+=cut
+fun point( $x, $y )
+{
+ #say qq( <line x1="$x" y1="$y" x2="$x" y2="$y" stroke="$colour" stroke-width="$linewidth" />);
+ say qq( <circle cx="$x" cy="$y" r="1" fill="$colour" stroke-width="0" />);
+}
+
+
+=pod
+
+=head2 line( $x1, $y1, $x2, $y2 );
+
+Draw a line from (x1,y1) to (x2,y2)
+
+=cut
+fun line( $x1, $y1, $x2, $y2 )
+{
+ say qq( <line x1="$x1" y1="$y1" x2="$x2" y2="$y2" stroke="$colour" stroke-width="$linewidth" />);
+}
+
+
+=pod
+
+=head2 rect( $x1, $y1, $x2, $y2 );
+
+Draw a rectangle from (x1,y1) to (x2,y2)
+
+=cut
+fun rect( $x1, $y1, $x2, $y2 )
+{
+ my $w = 1+$x2-$x1;
+ my $h = 1+$y2-$y1;
+ say qq( <rect x1="$x1" y1="$y1" width="$w" height="$h" fill="white" stroke="$colour" stroke-width="$linewidth" />);
+}
+
+
+1;
diff --git a/challenge-165/duncan-c-white/perl/ch-1.pl b/challenge-165/duncan-c-white/perl/ch-1.pl
new file mode 100755
index 0000000000..6ec5572613
--- /dev/null
+++ b/challenge-165/duncan-c-white/perl/ch-1.pl
@@ -0,0 +1,113 @@
+#!/usr/bin/perl
+#
+# TASK 1: Scalable Vector Graphics (SVG)
+#
+# Scalable Vector Graphics (SVG) are not made of pixels, but lines,
+# ellipses, and 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.
+#
+# MY NOTES: ok. Having read the SVG Wikipedia page, it seems very easy to do this myself (although I hate XML).
+#
+# GUEST LANGUAGE: As a bonus, I also had a go at translating ch-1.pl
+# into C (look in the C directory for that).
+#
+
+use strict;
+use warnings;
+use feature 'say';
+use Getopt::Long;
+use Function::Parameters;
+#use Data::Dumper;
+
+use lib qw(.);
+use MySVG;
+
+
+my $debug=0;
+die "Usage: mk-svg [--debug] [inputfile]\n"
+ unless GetOptions( "debug"=>\$debug ) && @ARGV<2;
+
+my @pt; # points, each point an [x,y] pair
+my @ln; # lines, each line an [x1,y1,x2,y2] tuple
+
+ # %wh will be the width and height of our data
+my %wh = ( W => -1, H => -1 );
+
+=pod
+
+=head2 updatewh( $x, $y );
+
+Update %wh rectangle to include $x and $y.
+
+=cut
+fun updatewh( $x, $y )
+{
+ die "x is negative: $x\n" if $x<0;
+ die "y is negative: $y\n" if $y<0;
+ $wh{W} = $x if $x > $wh{W};
+ $wh{H} = $y if $y > $wh{H};
+}
+
+
+while( <> )
+{
+ chomp;
+ s/\s+//g;
+ my @x = split(/,/);
+ if( @x==2 )
+ {
+ push @pt, \@x;
+ updatewh( @x );
+ } elsif( @x == 4 )
+ {
+ push @ln, \@x;
+ updatewh( @x[0,1] );
+ updatewh( @x[2,3] );
+ } else
+ {
+ die "Error at line $.: 2 or 4 numbers expected: $_\n";
+ }
+}
+
+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();
diff --git a/challenge-165/duncan-c-white/perl/ch-2.pl b/challenge-165/duncan-c-white/perl/ch-2.pl
new file mode 100755
index 0000000000..f820af06be
--- /dev/null
+++ b/challenge-165/duncan-c-white/perl/ch-2.pl
@@ -0,0 +1,145 @@
+#!/usr/bin/perl
+#
+# Task 2: Line of Best Fit
+#
+# 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, here is an
+# example of what such a line might look like:
+#
+# [image showing red best fit line on graph of black data points]
+#
+# 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.
+#
+# MY NOTES: seems pretty simple, https://www.mathsisfun.com/data/least-squares-regression.html has a good
+# explanation of the method. I've put the above data, one point per line, in "bestfitdata" in the
+# top-level directory.
+#
+# GUEST LANGUAGE: As a bonus, I also had a go at translating ch-2.pl
+# into C (look in the C directory for that).
+#
+
+use strict;
+use warnings;
+use feature 'say';
+use Getopt::Long;
+use Function::Parameters;
+#use Data::Dumper;
+
+use lib qw(.);
+use MySVG;
+
+
+my $debug=0;
+die "Usage: best-fit-points [--debug] [inputfile]\n"
+ unless GetOptions( "debug"=>\$debug ) && @ARGV<2;
+
+my @pt; # points, each point an [x,y] pair
+
+ # %wh will be the width and height of our data
+my %wh = ( W => -1, H => -1 );
+
+=pod
+
+=head2 updatewh( $x, $y );
+
+Update %wh rectangle to include $x and $y.
+
+=cut
+fun updatewh( $x, $y )
+{
+ die "x is negative: $x\n" if $x<0;
+ die "y is negative: $y\n" if $y<0;
+ $wh{W} = $x if $x > $wh{W};
+ $wh{H} = $y if $y > $wh{H};
+}
+
+
+while( <> )
+{
+ chomp;
+ s/\s+//g;
+ my @x = split(/,/);
+ if( @x==2 )
+ {
+ push @pt, \@x;
+ updatewh( @x );
+ } else
+ {
+ die "Error at line $.: x,y numbers expected: $_\n";
+ }
+}
+
+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 "debug: maxxy rectangle is w $w, h $h, xscale $xscale, yscale $yscale\n" if $debug;
+
+=pod
+
+=head2 my( $m, $c ) = bestfit( @data );
+
+Calculate y=mx+c, the best fit line of the data (each data point an [x,y] pair)
+
+=cut
+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( "oran