aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authordcw <d.white@imperial.ac.uk>2023-05-15 00:14:02 +0100
committerdcw <d.white@imperial.ac.uk>2023-05-15 00:14:02 +0100
commitb682ef222c56e28ed69b8266fa94c0382539cbcd (patch)
tree8bf2d08829da989d94f95fee8b511f4c366be996
parenta3e809db1e8a1e2168d9ceaaa2e8a24d25419bdb (diff)
downloadperlweeklychallenge-club-b682ef222c56e28ed69b8266fa94c0382539cbcd.tar.gz
perlweeklychallenge-club-b682ef222c56e28ed69b8266fa94c0382539cbcd.tar.bz2
perlweeklychallenge-club-b682ef222c56e28ed69b8266fa94c0382539cbcd.zip
imported my solutions to this week's challenge - both tasks in Perl, task 1 in C as well - task 2 in C to follow tomorrow
-rw-r--r--challenge-216/duncan-c-white/C/.cbuild2
-rw-r--r--challenge-216/duncan-c-white/C/Makefile16
-rw-r--r--challenge-216/duncan-c-white/C/README9
-rw-r--r--challenge-216/duncan-c-white/C/args.c234
-rw-r--r--challenge-216/duncan-c-white/C/args.h12
-rw-r--r--challenge-216/duncan-c-white/C/ch-1.c83
-rw-r--r--challenge-216/duncan-c-white/C/parseints.c114
-rw-r--r--challenge-216/duncan-c-white/C/parseints.h1
-rw-r--r--challenge-216/duncan-c-white/C/printarray.c39
-rw-r--r--challenge-216/duncan-c-white/C/printarray.h1
-rw-r--r--challenge-216/duncan-c-white/README96
-rwxr-xr-xchallenge-216/duncan-c-white/perl/ch-1.pl80
-rwxr-xr-xchallenge-216/duncan-c-white/perl/ch-2.pl203
13 files changed, 844 insertions, 46 deletions
diff --git a/challenge-216/duncan-c-white/C/.cbuild b/challenge-216/duncan-c-white/C/.cbuild
index c168e34403..835981f6f1 100644
--- a/challenge-216/duncan-c-white/C/.cbuild
+++ b/challenge-216/duncan-c-white/C/.cbuild
@@ -1,5 +1,5 @@
BUILD = ch-1 ch-2
-#BUILD = ch-1
+BUILD = ch-1
CFLAGS = -Wall -g
#LDFLAGS = -lm
#CFLAGS = -g
diff --git a/challenge-216/duncan-c-white/C/Makefile b/challenge-216/duncan-c-white/C/Makefile
new file mode 100644
index 0000000000..d193658c72
--- /dev/null
+++ b/challenge-216/duncan-c-white/C/Makefile
@@ -0,0 +1,16 @@
+# Makefile rules generated by CB
+CC = gcc
+CFLAGS = -Wall -g
+BUILD = ch-1
+
+all: $(BUILD)
+
+clean:
+ /bin/rm -f $(BUILD) *.o core a.out
+
+args.o: args.c
+ch-1: ch-1.o args.o parseints.o printarray.o
+ch-1.o: ch-1.c args.h parseints.h printarray.h
+parseints.o: parseints.c args.h parseints.h printarray.h
+printarray.o: printarray.c
+
diff --git a/challenge-216/duncan-c-white/C/README b/challenge-216/duncan-c-white/C/README
new file mode 100644
index 0000000000..f73271428e
--- /dev/null
+++ b/challenge-216/duncan-c-white/C/README
@@ -0,0 +1,9 @@
+Thought I'd also have a go at translating ch-1.pl and (shortly) ch-2.pl into C..
+
+Both C versions produce very similar (non-debugging and debugging)
+output to the Perl originals.
+
+These C versions use some of my regular support modules:
+- my command-line argument processing module args.[ch],
+- my csvlist-of-int parsing module parseints.[ch], and
+- my int-array printing module printarray.[ch].
diff --git a/challenge-216/duncan-c-white/C/args.c b/challenge-216/duncan-c-white/C/args.c
new file mode 100644
index 0000000000..20c21e6c30
--- /dev/null
+++ b/challenge-216/duncan-c-white/C/args.c
@@ -0,0 +1,234 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <string.h>
+#include <ctype.h>
+#include <assert.h>
+
+
+bool debug = false;
+
+
+// process_flag_noarg( name, argc, argv );
+// Process the -d flag, and check that there are no
+// remaining arguments.
+void process_flag_noarg( char *name, 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: %s [-d]\n", name );
+ exit(1);
+ }
+}
+
+
+// int argno = process_flag_n_args( name, argc, argv, n, argmsg );
+// Process the -d flag, and check that there are exactly
+// n remaining arguments, return the index position of the first
+// argument. If not, generate a fatal Usage error using the argmsg.
+//
+int process_flag_n_args( char *name, int argc, char **argv, int n, char *argmsg )
+{
+ int arg=1;
+ if( argc>1 && strcmp( argv[arg], "-d" ) == 0 )
+ {
+ debug = true;
+ arg++;
+ }
+
+ int left = argc-arg;
+ if( left != n )
+ {
+ fprintf( stderr, "Usage: %s [-d] %s\n Exactly %d "
+ "arguments needed\n", name, argmsg, n );
+ exit(1);
+ }
+ return arg;
+}
+
+
+// int argno = process_flag_n_m_args( name, argc, argv, min, max, argmsg );
+// Process the -d flag, and check that there are between
+// min and max remaining arguments, return the index position of the first
+// argument. If not, generate a fatal Usage error using the argmsg.
+//
+int process_flag_n_m_args( char *name, int argc, char **argv, int min, int max, char *argmsg )
+{
+ int arg=1;
+ if( argc>1 && strcmp( argv[arg], "-d" ) == 0 )
+ {
+ debug = true;
+ arg++;
+ }
+
+ int left = argc-arg;
+ if( left < min || left > max )
+ {
+ fprintf( stderr, "Usage: %s [-d] %s\n Between %d and %d "
+ "arguments needed\n", name, argmsg, min, max );
+ exit(1);
+ }
+ return arg;
+}
+
+
+// process_onenumarg_default( name, argc, argv, defvalue, &n );
+// Process the -d flag, and check that there is a single
+// remaining numeric argument (or no arguments, in which case
+// we use the defvalue), putting it into n
+void process_onenumarg_default( char *name, int argc, char **argv, int defvalue, int *n )
+{
+ char argmsg[100];
+ sprintf( argmsg, "[int default %d]", defvalue );
+ int arg = process_flag_n_m_args( name, argc, argv, 0, 1, argmsg );
+
+ *n = arg == argc ? defvalue : atoi( argv[arg] );
+}
+
+
+// process_onenumarg( name, argc, argv, &n );
+// Process the -d flag, and check that there is a single
+// remaining numeric argument, putting it into n
+void process_onenumarg( char *name, int argc, char **argv, int *n )
+{
+ int arg = process_flag_n_args( name, argc, argv, 1, "int" );
+
+ // argument is in argv[arg]
+ *n = atoi( argv[arg] );
+}
+
+
+// process_twonumargs( name, argc, argv, &m, &n );
+// Process the -d flag, and check that there are 2
+// remaining numeric arguments, putting them into m and n
+void process_twonumargs( char *name, int argc, char **argv, int *m, int *n )
+{
+ int arg = process_flag_n_args( name, argc, argv, 2, "int" );
+
+ // arguments are in argv[arg] and argv[arg+1]
+ *m = atoi( argv[arg++] );
+ *n = atoi( argv[arg] );
+}
+
+
+// process_twostrargs() IS DEPRECATED: use process_flag_n_m_args() instead
+
+
+// int arr[100];
+// int nel = process_listnumargs( name, argc, argv, arr, 100 );
+// Process the -d flag, and check that there are >= 2
+// remaining numeric arguments, putting them into arr[0..nel-1]
+// and returning nel.
+int process_listnumargs( char *name, int argc, char **argv, int *arr, int maxel )
+{
+ int arg=1;
+ if( argc>1 && strcmp( argv[arg], "-d" ) == 0 )
+ {
+ debug = true;
+ arg++;
+ }
+
+ int left = argc-arg;
+ if( left < 2 )
+ {
+ fprintf( stderr, "Usage: %s [-d] list_of_numeric_args\n", name );
+ exit(1);
+ }
+ if( left > maxel )
+ {
+ fprintf( stderr, "%s: more than %d args\n", name, maxel );
+ exit(1);
+ }
+
+ // elements are in argv[arg], argv[arg+1]...
+
+ if( debug )
+ {
+ printf( "debug: remaining arguments are in arg=%d, "
+ "firstn=%s, secondn=%s..\n",
+ arg, argv[arg], argv[arg+1] );
+ }
+
+ int nel = 0;
+ for( int i=arg; i<argc; i++ )
+ {
+ arr[nel++] = atoi( argv[i] );
+ }
+ arr[nel] = -1;
+ return nel;
+}
+
+
+//
+// bool isint = check_unsigned_int( char *val, int *n );
+// Given an string val, check that there's an unsigned integer
+// in it (after optional whitespace). If there is a valid
+// unsigned integer value, store that integer value in *n and
+// return true; otherwise return false (and don't alter *n).
+bool check_unsigned_int( char *val, int *n )
+{
+ // skip whitespace in val
+ char *p;
+ for( p=val; isspace(*p); p++ )
+ {
+ /*EMPTY*/
+ }
+ if( ! isdigit(*p) ) return false;
+ *n = atoi(p);
+ return true;
+}
+
+
+//
+// bool isint = check_int( char *val, int *n );
+// Given an string val, check that there's an integer
+// in it (after optional whitespace). If there is a valid
+// integer value, store that integer value in *n and
+// return true; otherwise return false (and don't alter *n).
+bool check_int( char *val, int *n )
+{
+ // skip whitespace in val
+ char *p;
+ for( p=val; isspace(*p); p++ )
+ {
+ /*EMPTY*/
+ }
+ int sign = 1;
+ if( *p == '+' ) p++;
+ else if( *p == '-' )
+ {
+ sign = -1;
+ p++;
+ }
+ if( ! isdigit(*p) ) return false;
+ *n = atoi(p) * sign;
+ return true;
+}
+
+
+//
+// bool ok = check_unsigned_real( char *val, double *n );
+// Given an string val, check that there's an unsigned real
+// in it (after optional whitespace). If there is a valid
+// unsigned real value, store that value in *n and
+// return true; otherwise return false (and don't alter *n).
+bool check_unsigned_real( char *val, double *n )
+{
+ // skip whitespace in val
+ char *p;
+ for( p=val; isspace(*p); p++ )
+ {
+ /*EMPTY*/
+ }
+ if( ! isdigit(*p) ) return false;
+ *n = atof(p);
+ return true;
+}
diff --git a/challenge-216/duncan-c-white/C/args.h b/challenge-216/duncan-c-white/C/args.h
new file mode 100644
index 0000000000..df765fa21e
--- /dev/null
+++ b/challenge-216/duncan-c-white/C/args.h
@@ -0,0 +1,12 @@
+extern bool debug;
+
+extern void process_flag_noarg( char * name, int argc, char ** argv );
+extern int process_flag_n_args( char * name, int argc, char ** argv, int n, char * argmsg );
+extern int process_flag_n_m_args( char * name, int argc, char ** argv, int min, int max, char * argmsg );
+extern void process_onenumarg_default( char * name, int argc, char ** argv, int defvalue, int * n );
+extern void process_onenumarg( char * name, int argc, char ** argv, int * n );
+extern void process_twonumargs( char * name, int argc, char ** argv, int * m, int * n );
+extern int process_listnumargs( char * name, int argc, char ** argv, int * arr, int maxel );
+extern bool check_unsigned_int( char * val, int * n );
+extern bool check_int( char * val, int * n );
+extern bool check_unsigned_real( char * val, double * n );
diff --git a/challenge-216/duncan-c-white/C/ch-1.c b/challenge-216/duncan-c-white/C/ch-1.c
new file mode 100644
index 0000000000..ba8185dba8
--- /dev/null
+++ b/challenge-216/duncan-c-white/C/ch-1.c
@@ -0,0 +1,83 @@
+// Task 1: Registration Number
+//
+// C translation
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <string.h>
+#include <ctype.h>
+#include <assert.h>
+
+#include "args.h"
+#include "parseints.h"
+#include "printarray.h"
+
+
+//
+// bool containsall = wordcontainsall( word, set );
+// Return true iff word contains every letter in set.
+//
+bool wordcontainsall( char *word, char *set )
+{
+ for( ; *set; set++ )
+ {
+ char letter = *set;
+ if( isupper(letter) ) letter = tolower(letter);
+ if( strchr( word, letter ) == NULL ) return false;
+ }
+ return true;
+}
+
+
+int main( int argc, char **argv )
+{
+ int argno = process_flag_n_m_args( "reg-nos", argc, argv,
+ 2, 1000, "reg wordlist" );
+
+ char *reg = argv[argno++];
+
+ if( debug )
+ {
+ printf( "debug: reg: %s, list: ", reg );
+ for( int i=argno; i<argc; i++ )
+ {
+ if( i>argno ) putchar( ',' );
+ printf( "%s", argv[i] );
+ }
+ putchar( '\n' );
+ }
+
+ char regset[argc+1];
+ char *d = regset;
+ for( char *s = reg; *s; s++ )
+ {
+ if( ! isalpha(*s) ) continue;
+ if( strchr(regset, *s) == NULL )
+ {
+ *d++ = *s;
+ *d = '\0';
+ }
+ }
+
+ if( debug )
+ {
+ printf( "debug: regset=%s\n", regset );
+ //exit(1);
+ }
+
+ int n = 0;
+ for( int i=argno; i<argc; i++ )
+ {
+ char *word = argv[i];
+ if( wordcontainsall( word, regset ) )
+ {
+ if( n>0 ) putchar( ',' );
+ n++;
+ printf( "%s", word );
+ }
+ }
+ putchar( '\n' );
+
+ return 0;
+}
diff --git a/challenge-216/duncan-c-white/C/parseints.c b/challenge-216/duncan-c-white/C/parseints.c
new file mode 100644
index 0000000000..3e820eb334
--- /dev/null
+++ b/challenge-216/duncan-c-white/C/parseints.c
@@ -0,0 +1,114 @@
+// Simple routine to parse one or more arguments,
+// looking for individual ints or comma-separated
+// lists of ints.
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <string.h>
+#include <ctype.h>
+#include <assert.h>
+
+#include "args.h"
+#include "printarray.h"
+#include "parseints.h"
+
+typedef struct
+{
+ int nel; // current number of elements
+ int maxel; // maximum number of elements allocated
+ int *list; // malloc()d list of integers
+} intlist;
+
+
+//
+// intlist il.. then initialize il.. then:
+// add_one( element, &il );
+//
+static void add_one( int x, intlist *p )
+{
+ if( p->nel > p->maxel )
+ {
+ p->maxel += 128;
+ p->list = realloc( p->list, p->maxel );
+ assert( p->list != NULL );
+ }
+ #if 0
+ if( debug )
+ {
+ printf( "PIA: appending %d to result at "
+ "pos %d\n", x, p->nel );
+ }
+ #endif
+ p->list[p->nel++] = x;
+}
+
+
+//
+// intlist il.. then initialize il.. then:
+// add_one_arg( argstr, &il );
+//
+static void add_one_arg( char *argstr, intlist *p )
+{
+ int x;
+ if( !check_int(argstr,&x) )
+ {
+ fprintf( stderr, "PIA: arg %s must be int\n", argstr );
+ exit(1);
+ }
+ add_one( x, p );
+}
+
+
+//
+// int nel;
+// int *ilist = parse_int_args( argc, argv, argno, &nel );
+// process all arguments argv[argno..argc-1], extracting either
+// single ints or comma-separated lists of ints from those arguments,
+// accumulate all integers in a dynarray list, storing the total number
+// of elements in nel. This list must be freed by the caller.
+// Note that the list of elements used to be terminated by a -1 value,
+// but I've commented this out from now on.
+//
+int *parse_int_args( int argc, char **argv, int argno, int *nel )
+{
+ int *result = malloc( 128 * sizeof(int) );
+ assert( result != NULL );
+ intlist il = { 0, 128, result };
+
+ #if 0
+ if( debug )
+ {
+ printf( "PIA: parsing ints from args %d..%d\n", argno, argc-1 );
+ }
+ #endif
+ for( int i=argno; i<argc; i++ )
+ {
+ assert( strlen(argv[i]) < 1024 );
+ char copy[1024];
+ strcpy( copy, argv[i] );
+ char *com;
+ char *s;
+ for( s=copy; (com = strchr(s,',')) != NULL; s=com+1 )
+ {
+ *com = '\0';
+ add_one_arg( s, &il );
+ }
+ add_one_arg( s, &il );
+ }
+
+ //add_one( -1, &il );
+
+ #if 0
+ if( debug )
+ {
+ printf( "PIA: final list is " );
+ print_int_array( 80, il.nel, il.list, ',', stdout );
+ putchar( '\n' );
+ }
+ #endif
+
+ *nel = il.nel;
+ return il.list;
+}
diff --git a/challenge-216/duncan-c-white/C/parseints.h b/challenge-216/duncan-c-white/C/parseints.h
new file mode 100644
index 0000000000..da5e145a86
--- /dev/null
+++ b/challenge-216/duncan-c-white/C/parseints.h
@@ -0,0 +1 @@
+extern int * parse_int_args( int argc, char ** argv, int argno, int * nel );
diff --git a/challenge-216/duncan-c-white/C/printarray.c b/challenge-216/duncan-c-white/C/printarray.c
new file mode 100644
index 0000000000..ddee597df3
--- /dev/null
+++ b/challenge-216/duncan-c-white/C/printarray.c
@@ -0,0 +1,39 @@
+#include <stdio.h>
+#include <string.h>
+
+
+// print_int_array( maxw, nelements, results[], sep, outfile );
+// format results[0..nelements-1] as a <sep> separated
+// list onto outfile with lines <= maxw chars long.
+// produces a whole number of lines of output - without the trailing '\n'
+void print_int_array( int maxw, int nel, int *results, char sep, FILE *out )
+{
+ int linelen = 0;
+ for( int i=0; i<nel; i++ )
+ {
+ char buf[100];
+ sprintf( buf, "%d", results[i] );
+ int len = strlen(buf);
+ if( linelen + len + 2 > maxw )
+ {
+ fputc( '\n', out );
+ linelen = 0;
+ } else if( i>0 )
+ {
+ fputc( ' ', out );
+ linelen++;
+ }
+
+ linelen += len;
+ fprintf( out, "%s", buf );
+ if( i<nel-1 )
+ {
+ fputc( sep, out );
+ linelen++;
+ }
+ }
+ //if( linelen>0 )
+ //{
+ // fputc( '\n', out );
+ //}
+}
diff --git a/challenge-216/duncan-c-white/C/printarray.h b/challenge-216/duncan-c-white/C/printarray.h
new file mode 100644
index 0000000000..40efb83277
--- /dev/null
+++ b/challenge-216/duncan-c-white/C/printarray.h
@@ -0,0 +1 @@
+extern void print_int_array( int maxw, int nel, int * results, char sep, FILE * out );
diff --git a/challenge-216/duncan-c-white/README b/challenge-216/duncan-c-white/README
index b035eae705..a64621c29b 100644
--- a/challenge-216/duncan-c-white/README
+++ b/challenge-216/duncan-c-white/README
@@ -1,76 +1,82 @@
-Task 1: Odd one Out
+Task 1: Registration Number
-You are given a list of words (alphabetic characters only) of same size.
-Write a script to remove all words not sorted alphabetically and print
-the number of words in the list that are not alphabetically sorted.
+You are given a list of words and a random registration number.
+Write a script to find all the words in the given list that has every
+letter in the given registration number.
Example 1
- Input: @words = ('abc', 'xyz', 'tsu')
- Output: 1
+ Input: @words = ('abc', 'abcd', 'bcd'), $reg = 'AB1 2CD'
+ Output: ('abcd')
- The words 'abc' and 'xyz' are sorted and can't be removed.
- The word 'tsu' is not sorted and hence can be removed.
+ The only word that matches every alphabets in the given registration number is 'abcd'.
Example 2
- Input: @words = ('rat', 'cab', 'dad')
- Output: 3
-
- None of the words in the given list are sorted.
- Therefore all three needs to be removed.
+ Input: @words = ('job', 'james', 'bjorg'), $reg = '007 JB'
+ Output: ('job', 'bjorg')
Example 3
- Input: @words = ('x', 'y', 'z')
- Output: 0
+ Input: @words = ('crack', 'road', 'rac'), $reg = 'C7 RA2'
+ Output: ('crack', 'rac')
-MY NOTES: strangely worded, all the stuff about removing words you don't want
-and then counting what's left (but not displaying what's left) can be
-simplified to: count what you want (the non-sorted words), and that's easy.
-But on second thought, let's remove the words anyway, even if we only print
-them out in debug mode.
+MY NOTES: I think the meaning is: form a set of all letters from the
+registration. Select all words that contain every member of the set.
+That's easy.
GUEST LANGUAGE: As a bonus, I've had a go at translating ch-1.pl into C,
look in the C/ directory for that.
-Task 2: Number Placement
-
-You are given a list of numbers having just 0 and 1. You are also given
-placement count (>=1).
+Task 2: Word Stickers
-Write a script to find out if it is possible to replace 0 with 1 in the
-given list. The only condition is that you can only replace when there
-is no 1 on either side. Print 1 if it is possible otherwise 0.
+You are given a list of word stickers and a target word.
+Write a script to find out how many word stickers is needed to make up
+the given target word.
Example 1:
- Input: @numbers = (1,0,0,0,1), $count = 1
- Output: 1
+ Input: @stickers = ('perl','raku','python'), $word = 'peon'
+ Output: 2
- You are asked to replace only one 0 as given count is 1.
- We can easily replace middle 0 in the list i.e. (1,0,1,0,1).
+ We just need 2 stickers i.e. 'perl' and 'python'.
+ 'pe' from 'perl' and
+ 'on' from 'python' to get the target word.
Example 2:
- Input: @numbers = (1,0,0,0,1), $count = 2
- Output: 0
+ Input: @stickers = ('love','hate','angry'), $word = 'goat'
+ Output: 3
- You are asked to replace two 0's as given count is 2.
- It is impossible to replace two 0's.
+ We need 3 stickers i.e. 'angry', 'love' and 'hate'.
+ 'g' from 'angry'
+ 'o' from 'love' and
+ 'at' from 'hate' to get the target word.
Example 3:
- Input: @numbers = (1,0,0,0,0,0,0,0,1), $count = 3
- Output: 1
+ Input: @stickers = ('come','nation','delta'), $word = 'accommodation'
+ Output: 4
-MY NOTES: ok, so it means "can you replace COUNT consecutive
-zeros with ones, where neither the preceding or following number is 1".
-i.e. where there is either NO preceding or following number (the COUNT
-consecutive zeros start or end of the sequence), or a 0 precedes and
-follows our COUNT consecutive zeros.
-So I guess we just run a start-point along the sequence and check.
+ We just need 2 stickers of 'come' and one each of 'nation' & 'delta'.
+ 'a' from 'delta'
+ 'ccommo' from 2 stickers 'come'
+ 'd' from the same sticker 'delta' and
+ 'ation' from 'nation' to get the target word.
-GUEST LANGUAGE: As a bonus, I had a go at translating ch-2.pl into C,
-look in the C/ directory for that.
+Example 4:
+
+ Input: @stickers = ('come','country','delta'), $word = 'accommodation'
+ Output: 0
+
+as there's no "i" in the inputs.
+
+
+MY NOTES: ok, so the only tricky part is that each sticker may be used any
+number of times (if any of it's letters remain in the word). It will be
+worth checking that every letter in the word is present in one or more stickers
+before starting the main search process.
+
+GUEST LANGUAGE: I will have a go at translating ch-2.pl into C tomorrow;
+look in the C/ directory when that's done.
diff --git a/challenge-216/duncan-c-white/perl/ch-1.pl b/challenge-216/duncan-c-white/perl/ch-1.pl
new file mode 100755
index 0000000000..0962893e57
--- /dev/null
+++ b/challenge-216/duncan-c-white/perl/ch-1.pl
@@ -0,0 +1,80 @@
+#!/usr/bin/perl
+#
+# Task 1: Registration Number
+#
+# You are given a list of words and a random registration number.
+# Write a script to find all the words in the given list that has every
+# letter in the given registration number.
+#
+# Example 1
+#
+# Input: @words = ('abc', 'abcd', 'bcd'), $reg = 'AB1 2CD'
+# Output: ('abcd')
+#
+# The only word that matches every alphabets in the given registration number is 'abcd'.
+#
+# Example 2
+#
+# Input: @words = ('job', 'james', 'bjorg'), $reg = '007 JB'
+# Output: ('job', 'bjorg')
+#
+# Example 3
+#
+# Input: @words = ('crack', 'road', 'rac'), $reg = 'C7 RA2'
+# Output: ('crack', 'rac')
+#
+# MY NOTES: I think the meaning is: form a set of all letters from the
+# registration. Select all words that contain every member of the set.
+# That's easy.
+#
+# GUEST LANGUAGE: As a bonus, I've 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 Data::Dumper;
+
+my $debug=0;
+die "Usage: reg-no [--debug] reg wordlist\n"
+ unless GetOptions( "debug"=>\$debug ) && @ARGV>1;
+
+my $reg = shift;
+my @list = split( /,/, join(',',@ARGV) );
+
+say "debug: reg: $reg, list: ", join(',',@list) if $debug;
+
+#
+# my $containsall = wordcontainsall( $word, %set );
+# Return true iff $word contains every letter in %set.
+#
+sub wordcontainsall
+{
+ my( $word, %set ) = @_;
+ foreach my $letter (keys %set)
+ {
+ return 0 unless $word =~ /$letter/i;
+ }
+ return 1;
+}
+
+
+my %regset;
+$regset{$_}++ for grep { /[A-Z]/i } split(//,$reg);
+
+#die Dumper(\%regset);
+
+my @results;
+foreach my $word (@list)
+{
+ if( wordcontainsall( $word, %regset ) )
+ {
+ push @results, $word;
+ say "debug: found word $word that contains all reg letters"
+ if $debug;
+ }
+}
+
+say join( ',', map { qq('$_') } @results );
diff --git a/challenge-216/duncan-c-white/perl/ch-2.pl b/challenge-216/duncan-c-white/perl/ch-2.pl
new file mode 100755
index 0000000000..ef9597efa9
--- /dev/null
+++ b/challenge-216/duncan-c-white/perl/ch-2.pl
@@ -0,0 +1,203 @@
+#!/usr/bin/perl
+#
+# Task 2: Word Stickers
+#
+# You are given a list of word stickers and a target word.
+# Write a script to find out how many word stickers is needed to make up
+# the given target word.
+#
+# Example 1:
+#
+# Input: @stickers = ('perl','raku','python'), $word = 'peon'
+# Output: 2
+#
+# We just need 2 stickers i.e. 'perl' and 'python'.
+# 'pe' from 'perl' and
+# 'on' from 'python' to get the target word.
+#
+# Example 2:
+#
+# Input: @stickers = ('love','hate','angry'), $word = 'goat'
+# Output: 3
+#
+# We need 3 stickers i.e. 'angry', 'love' and 'hate'.
+# 'g' from 'angry'
+# 'o' from 'love' and
+# 'at' from 'hate' to get the target word.
+#
+# Example 3:
+#
+# Input: @stickers = ('come','nation','delta'), $word = 'accommodation'
+# Output: 4
+#
+# We just need 2 stickers of 'come' and one each of 'nation' & 'delta'.
+# 'a' from 'delta'
+# 'ccommo' from 2 stickers 'come'
+# 'd' from the same sticker 'delta' and
+# 'ation' from 'nation' to get the target word.
+#
+# Example 4:
+#
+# Input: @stickers = ('come','country','delta'), $word = 'accommodation'
+# Output: 0
+#
+# as there's no "i" in the inputs.
+#
+# MY NOTES: ok, so the only tricky part is that each sticker may be used any
+# number of times (if any of it's letters remain in the word). It will be
+# worth checking that every letter in the word is present in one or more
+# stickers before starting the main search process.
+#
+# GUEST LANGUAGE: I will have a go at translating ch-2.pl into C tomorrow;
+# look in the C/ directory when that's done.
+#
+
+use strict;
+use warnings;
+use feature 'say';
+use Getopt::Long;
+use Data::Dumper;
+use Function::Parameters;
+
+my $debug=0;
+die "Usage: word-stickers [--debug] word stickerwordlist\n"
+ unless GetOptions( "debug"=>\$debug ) && @ARGV > 1;
+my $word = lc(shift);
+my @sticker = split( /,/, lc(join(',',@ARGV)) );
+
+say "debug: word: $word, sticker: ", join(',',@sticker) if $debug;
+
+
+#
+# my $ismissing = lettermissing( $letter, @sticker );
+# Return 1 iff $letter is missing from all the stickers in @sticker.
+# Return 0 otherwise.
+#
+fun lettermissing( $letter, @sticker )
+{
+ foreach my $s (@sticker)
+ {
+ return 0 if $s =~ /$letter/i;
+ }
+ return 1;
+}
+
+
+#
+# my $missing = findmissing( $word, @sticker );
+# Find and return a string of all the letters that are in $word
+# but which are missing from all stickers.
+#
+fun findmissing( $word, @sticker )
+{
+ my $missing = "";
+ foreach my $letter (split(//,$word))
+ {
+ if( lettermissing($letter,@sticker) )
+ {
+ $missing .= $letter;
+ say "debug: found missing letter $letter" if $debug;
+ }
+ }
+ return $missing;
+}
+
+
+#
+# my $lettersincommon = lettersincommon( $word, $sticker );
+# Find and return a string containing all the letters that
+# $word has in common with $sticker (using each letter in
+# $sticker only once)
+#
+fun lettersincommon( $word, $sticker )
+{
+ my $common = "";
+ while( $sticker )
+ {
+ $sticker =~ s/^(.)//;
+ my $letter = $1;
+ if( $word =~ s/$letter// )
+ {
+ $common .= $letter;
+ }
+ }
+ return $common;
+}
+
+
+
+my $minstickers=1000000;
+my @minstickers_used;
+
+
+#
+# findall( $word, $stickersused, @allsticker );
+# We have already used @$stickersused;
+# Find all combinations of all stickers @allsticker that
+# contain all letters in $word, and find the best (minimum)
+# number of stickers in $minstickers, and stickers themselves
+# in @minstickers_used.
+# How? well, stickers can be used (or not) whenever it contains
+# any letters in common with the word.
+#
+fun findall( $word, $stickersused, @allsticker )
+{
+ if( $word eq '' )
+ {
+ say "found solution, stickersused = ", join(',',@$stickersused) if $debug;
+ my $nwords = @$stickersused;
+ if( $nwords < $minstickers )
+ {
+ $minstickers = $nwords;
+ @minstickers_used = @$stickersused;
+ say "found new best solution: min stickers ".
+ "$minstickers, stickersused = ",
+ join(',',@minstickers_used) if $debug;
+ }
+ return;
+ }
+ foreach my $sticker (@allsticker)
+ {
+ my $common = lettersincommon( $word, $sticker );
+ next if $common eq '';
+ say "lettersincommon( $word, sticker $sticker ) = $common" if $debug;
+
+ # there are two possibilities: use this sticker or don't;
+ # try both..
+
+ # try using the sticker
+ say "USE sticker $sticker, against $word, letters in common $common" if $debug;
+ my @newsu = @$stickersused;
+ push @newsu, $sticker;
+ my $newword = $word;
+ $newword =~ s/$_// for split(//,$common);
+ say " - new word is <$newword>" if $debug;
+ findall( $newword, \@newsu, @allsticker );
+
+ # or try without the sticker
+ }
+}
+
+
+my $missingletters = findmissing( $word, @sticker );
+if( $missingletters ne '' )
+{
+ say "0";
+ if( $debug )
+ {
+ say "debug: There are no solutions as letters $missingletters ".
+ "are missing from all stickers";
+ }
+ exit(0);
+}
+
+$minstickers=1000000;
+@minstickers_used = ();
+
+findall( $word, [], @sticker );
+
+say $minstickers;
+if( $debug )
+{
+ say "debug: stickers used: ", join(',',@minstickers_used);
+}