aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--challenge-178/duncan-c-white/C/.cbuild3
-rw-r--r--challenge-178/duncan-c-white/C/Makefile14
-rw-r--r--challenge-178/duncan-c-white/C/README12
-rw-r--r--challenge-178/duncan-c-white/C/args.c207
-rw-r--r--challenge-178/duncan-c-white/C/args.h11
-rw-r--r--challenge-178/duncan-c-white/C/ch-1.c294
-rw-r--r--challenge-178/duncan-c-white/C/ch-2a.c242
-rw-r--r--challenge-178/duncan-c-white/README67
-rwxr-xr-xchallenge-178/duncan-c-white/perl/ch-1.pl171
-rwxr-xr-xchallenge-178/duncan-c-white/perl/ch-2.pl62
-rwxr-xr-xchallenge-178/duncan-c-white/perl/ch-2a.pl187
-rw-r--r--challenge-179/duncan-c-white/C/.cbuild3
-rw-r--r--challenge-179/duncan-c-white/C/Makefile12
-rw-r--r--challenge-179/duncan-c-white/C/README8
-rw-r--r--challenge-179/duncan-c-white/C/args.c207
-rw-r--r--challenge-179/duncan-c-white/C/args.h11
-rw-r--r--challenge-179/duncan-c-white/C/ch-1.c261
-rw-r--r--challenge-179/duncan-c-white/README51
-rwxr-xr-xchallenge-179/duncan-c-white/perl/ch-1.pl156
19 files changed, 1915 insertions, 64 deletions
diff --git a/challenge-178/duncan-c-white/C/.cbuild b/challenge-178/duncan-c-white/C/.cbuild
index aebcd2040a..78560d4ced 100644
--- a/challenge-178/duncan-c-white/C/.cbuild
+++ b/challenge-178/duncan-c-white/C/.cbuild
@@ -1,3 +1,4 @@
-BUILD = ch-1 ch-2
+BUILD = ch-1 ch-2a
CFLAGS = -Wall -g
+#LDFLAGS = -lm
#CFLAGS = -g
diff --git a/challenge-178/duncan-c-white/C/Makefile b/challenge-178/duncan-c-white/C/Makefile
new file mode 100644
index 0000000000..be7b4f9d29
--- /dev/null
+++ b/challenge-178/duncan-c-white/C/Makefile
@@ -0,0 +1,14 @@
+BUILD = ch-1 ch-2a
+CC = gcc
+CFLAGS = -Wall -g
+#LDLIBS = -lm
+
+all: $(BUILD)
+
+clean:
+ /bin/rm -f $(BUILD) *.o core a.out
+
+ch-1: ch-1.o args.o
+ch-1.o: ch-1.c args.h
+ch-2a: ch-2a.o args.o
+ch-2a.o: ch-2a.c args.h
diff --git a/challenge-178/duncan-c-white/C/README b/challenge-178/duncan-c-white/C/README
new file mode 100644
index 0000000000..b79f2ee9cb
--- /dev/null
+++ b/challenge-178/duncan-c-white/C/README
@@ -0,0 +1,12 @@
+Thought I'd also have a go at translating ch-1.pl and ch-2.c into C..
+
+Both ch-1.c and ch-2.c produce identical (non-debugging) output to my
+Perl originals.
+
+Both use the command-line argument processing module args.[ch].
+I translated IsPrime.pm to isprime.[ch], and wrote an array printing
+function in prarray.[ch].
+
+Both tasks were very straightforward to translate, it's just that
+we have to write a lot more low-level support code that Perl does
+for us..
diff --git a/challenge-178/duncan-c-white/C/args.c b/challenge-178/duncan-c-white/C/args.c
new file mode 100644
index 0000000000..d4a2d38b9a
--- /dev/null
+++ b/challenge-178/duncan-c-white/C/args.c
@@ -0,0 +1,207 @@
+#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 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-178/duncan-c-white/C/args.h b/challenge-178/duncan-c-white/C/args.h
new file mode 100644
index 0000000000..8844a8f9c4
--- /dev/null
+++ b/challenge-178/duncan-c-white/C/args.h
@@ -0,0 +1,11 @@
+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_unsigned_real( char * val, double * n );
diff --git a/challenge-178/duncan-c-white/C/ch-1.c b/challenge-178/duncan-c-white/C/ch-1.c
new file mode 100644
index 0000000000..a3ad9aee2e
--- /dev/null
+++ b/challenge-178/duncan-c-white/C/ch-1.c
@@ -0,0 +1,294 @@
+//
+// Task 1: Quater-imaginary Base conversion
+//
+// C version.
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <string.h>
+#include <ctype.h>
+#include <assert.h>
+
+#include "args.h"
+
+#define BUFSIZE 100
+
+
+//
+// reverse( str );
+// reverse str in place
+void reverse( char *str )
+{
+ char *p = str + strlen(str) - 1;
+ for( char *q=str; q<p; q++, p-- )
+ {
+ char t = *p;
+ *p = *q;
+ *q = t;
+ }
+}
+
+
+//
+// char b4str[BUFSIZE];
+// convert_to_base_neg4( x, b4str );
+// Convert +int x to base -4, storing the base-4 numeral into b4str.
+//
+void convert_to_base_neg4( int x, char *b4str )
+{
+ int origx = x;
+ *b4str = '\0';
+ char *p = b4str;
+ while( abs(x)>0 )
+ {
+ int d = x/-4;
+ int m = x + 4*d;
+ if( m < 0 )
+ {
+ m += 4;
+ d++;
+ }
+ if( debug )
+ {
+ printf( "debug: c_t_b-4(%d): x=%d, d=%d, m=%d\n",
+ origx, x, d, m );
+ }
+ *p++ = m+'0';
+ x = d;
+ }
+ *p = '\0';
+
+ if( p == b4str )
+ {
+ *p++ = '0';
+ *p = '\0';
+ }
+
+ reverse( b4str );
+
+ if( debug )
+ {
+ printf( "debug: c_t_b-4(%d): result = %s\n", origx, b4str );
+ }
+}
+
+
+//
+// my $qistr = convert_to( $n );
+// Convert +int $n to q-i, return the q-i numeral.
+//
+// char qistr[BUFSIZE];
+// convert_to( x, qistr );
+// Convert +int x to qi base, storing the qi numeral into qistr.
+//
+void convert_to( int x, char *qistr )
+{
+ char b4str[BUFSIZE];
+ convert_to_base_neg4( x, b4str );
+
+ if( debug )
+ {
+ printf( "debug: to(%d) base-4 = %s\n", x, b4str );
+ }
+
+ // copy the b4str to qistr, adding '0' between every pair of digits
+ char *s = b4str;
+ char *d=qistr;
+
+ *d++ = *s++;
+
+ for( ; *s; s++ )
+ {
+ *d++ = '0';
+ *d++ = *s;
+ }
+}
+
+
+//
+// int n = convert_from_base_neg4( char *bstr );
+// Given bstr, a base -4 numeral representing a +int,
+// return that int.
+//
+int convert_from_base_neg4( char *bstr )
+{
+ int n = 0;
+ int currpow = 1;
+ int len = strlen(bstr)-1;
+ for( char *p= bstr+len; p>=bstr; p-- )
+ {
+ int d = *p-'0';
+ if( debug )
+ {
+ printf( "debug: c_f_b-4(%s): digit %d, currpow %d, "
+ "n %d\n", bstr, d, currpow, n );
+ }
+ n += d * currpow;
+ currpow *= -4;
+ }
+ if( debug )
+ {
+ printf( "debug: c_f_b-4(%s): result is %d\n", bstr, n );
+ }
+ return n;
+}
+
+
+//
+// int n = convert_from( char *qinum );
+// Given a q-i numeral qinum (a string using only digits 0..3),
+// convert it from q-i to a plain +int.
+//
+int convert_from( char *qinum )
+{
+ char bstr[BUFSIZE];
+
+ // convert qinum into a base -4 string, checking that the
+ // imaginary columns in qinum are all 0 as we go.
+ *bstr = '\0';
+ bool odd = false;
+ char *d = bstr;
+ char *s;
+ for( s = qinum; *s; s++ ) /*EMPTY*/;
+ s--;
+ for( ; s>=qinum; s-- )
+ {
+ char dig = *s;
+ if( odd )
+ {
+ if( dig != '0' )
+ {
+ fprintf( stderr, "convert_from( %s ): digit %c"
+ " should be zero\n", qinum, dig );
+ exit(1);
+ }
+ } else
+ {
+ *d++ = dig;
+ }
+ odd = !odd;
+ }
+ *d = '\0';
+ if( bstr[0] == '\0' )
+ {
+ bstr[0] = '0';
+ bstr[1] = '\0';
+ }
+
+ reverse( bstr );
+
+ if( debug )
+ {
+ printf( "debug: cf(%s), bstr = %s\n", qinum, bstr );
+ }
+
+ // now convert that..
+ return convert_from_base_neg4( bstr );
+}
+
+
+//
+// bool ok = validate_all0to3( val );
+// Return true iff val only contains [0-3] digits.
+//
+bool validate_all0to3( char *val )
+{
+ int bad=0;
+ for( char *p=val; *p; p++ )
+ {
+ if( *p < '0' || *p > '3' ) bad++;
+ }
+ return bad==0;
+}
+
+
+int main( int argc, char **argv )
+{
+ int argno = process_flag_n_args( "quater-imaginary-base", argc, argv,
+ 2, "to/tobase-4/from/frombase-4/check val" );
+ char *dir = argv[argno++]; // to/tobase-4/from/frombase-4/check
+ char *val = argv[argno]; // value string (either +int or qistr)
+
+ if( strcmp( dir, "to" ) == 0 )
+ {
+ int n;
+ if( !check_unsigned_int(val,&n) )
+ {
+ fprintf( stderr, "quater-imaginary-base: to %s: "
+ "must be +int\n", val );
+ exit(1);
+ }
+ if( debug )
+ {
+ printf( "debug: to(%d)\n", n );
+ }
+ char cistr[BUFSIZE];
+ convert_to( n, cistr );
+ printf( "%s\n", cistr );
+ } else if( strcmp( dir, "tobase-4" ) == 0 )
+ {
+ int n;
+ if( !check_unsigned_int(val,&n) )
+ {
+ fprintf( stderr, "quater-imaginary-base: tobase-4 "
+ "%s: must be +int\n", val );
+ exit(1);
+ }
+ if( debug )
+ {
+ printf( "debug: to(%d)\n", n );
+ }
+ char b4str[BUFSIZE];
+ convert_to_base_neg4( n, b4str );
+ printf( "%s\n", b4str );
+ } else if( strcmp( dir, "from" ) == 0 )
+ {
+ bool ok = validate_all0to3( val );
+ if( !ok )
+ {
+ fprintf( stderr, "quater-imaginary-base: from %s: "
+ "all digits must be 0..3\n", val );
+ exit(1);
+ }
+ int n = convert_from( val );
+ printf( "qi value %s = %d\n", val, n );
+ } else if( strcmp( dir, "frombase-4" ) == 0 )
+ {
+ bool ok = validate_all0to3( val );
+ if( !ok )
+ {
+ fprintf( stderr, "quater-imaginary-base: frombase-4 "
+ "%s: all digits must be 0..3\n", val );
+ exit(1);
+ }
+ int n = convert_from_base_neg4( val );
+ printf( "base -4 value %s = %d\n", val, n );
+ } else if( strcmp( dir, "check" ) == 0 )
+ {
+ int n;
+ if( !check_unsigned_int(val,&n) )
+ {
+ fprintf( stderr, "quater-imaginary-base: check %s: "
+ "must be +int\n", val );
+ exit(1);
+ }
+ printf( "checking first %d conversions back-convert ok\n", n );
+ for( int x=0; x<n; x++ )
+ {
+ char cistr[BUFSIZE];
+ convert_to( x, cistr );
+ //printf( "%s\n", cistr );
+
+ int back = convert_from( cistr );
+ assert( x==back );
+ }
+ } else
+ {
+ fprintf( stderr, "quater-imaginary-base: direction %s must "
+ "be to/tobase-4/from/frombase-4/check\n", dir );
+ exit(1);
+ }
+ return 0;
+}
diff --git a/challenge-178/duncan-c-white/C/ch-2a.c b/challenge-178/duncan-c-white/C/ch-2a.c
new file mode 100644
index 0000000000..96f56386a0
--- /dev/null
+++ b/challenge-178/duncan-c-white/C/ch-2a.c
@@ -0,0 +1,242 @@
+//
+// Task 2: Business Date
+//
+// C version.
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <string.h>
+
+#define __USE_XOPEN
+#include <time.h>
+
+#include "args.h"
+
+
+#define STARTHOUR 9
+#define ENDHOUR 18
+// experimented with much shorter day: #define STARTHOUR 14
+// experimented with much shorter day: #define ENDHOUR 17
+
+#define DAYLEN (ENDHOUR - STARTHOUR)
+
+
+//
+// bool ok = is_business_day_and_time( date );
+// Given a date object date, return 1 iff date is a business day
+// and within business hours, else return 0.
+//
+bool is_business_day_and_time( struct tm date )
+{
+ int dow = date.tm_wday;
+ if( debug )
+ {
+ printf( "debug ibdt: dow=%d\n", dow );
+ }
+ if( dow<1 || dow>5 ) return false;
+
+ int h = date.tm_hour;
+ int m = date.tm_min;
+ if( debug )
+ {
+ printf( "debug ibdt: h=%d, m=%d\n", h, m );
+ }
+ if( h >= STARTHOUR && h < ENDHOUR ) return true;
+ if( h == ENDHOUR && m == 0 ) return true;
+ return false;
+}
+
+
+//
+// double rem = remainder_of_business_day( h, m );
+// Given a time (hour h, minute m) that is within working hours,
+// return the (real) number of hours remaining in that business day.
+//
+double remainder_of_business_day( int h, int m )
+{
+ double t = h + ((double)m) / 60;
+ return ENDHOUR - t;
+}
+
+
+//
+// next_business_day( &date );
+// Move date to the next business day.
+//
+void next_business_day( struct tm *date )
+{
+
+ int dow = date->tm_wday;
+ if( debug )
+ {
+ printf( "debug nbd: dow=%d\n", dow );
+ }
+
+ do
+ {
+ // move to next physical day
+ date->tm_mday++;
+ date->tm_isdst = -1;
+ mktime( date );
+ //dow = date->tm_wday;
+ dow++;
+ if( dow==7 ) dow = 0;
+ } while( dow < 1 || dow > 5 );
+ if( debug )
+ {
+ char buf[255];
+ strftime( buf, sizeof(buf), "%a %d %b %Y %H:%M", date );
+ printf( "debug nbd: nbd=%s\n", buf );
+ }
+}
+
+
+//
+// add_business_duration( &date, duration );
+// Add a +ve duration duration (real, business hours) to the given date,
+// modifying the resultant date. This must skip from ENDHOUR:00 to
+// STARTHOUR:00 on the next business day, where necessary.
+//
+void add_business_duration( struct tm *date, double duration )
+{
+ int h = date->tm_hour;
+ int m = date->tm_min;
+ if( debug )
+ {
+ printf( "debug abd: h=%d, m=%d\n", h, m );
+ }
+
+ double rem = remainder_of_business_day( h, m );
+ if( debug )
+ {
+ printf( "debug abd: remainder of busday: %g hours\n", rem );
+ }
+
+ if( duration <= rem )
+ {
+ int id = duration;
+ int md = (int)((duration-(double)id)*60.0);
+ if( debug )
+ {
+ printf( "debug abd: duration=%g, id=%d, md=%d\n",
+ duration, id, md );
+ }
+
+ h += id;
+ m += md;
+ if( m > 60 )
+ {
+ m -= 60;
+ h++;
+ }
+ date->tm_hour = h;
+ date->tm_min = m;
+ } else
+ {
+ // duration goes onto another day; move to end of the day
+ duration -= rem;
+
+ int id = duration;
+ int md = (int)((duration-(double)id)*60.0);
+ if( debug )
+ {
+ printf( "debug abd: remaining duration=%g, id=%d, md=%d\n",
+ duration, id, md );
+ }
+
+ // now advance date to next business day
+ next_business_day( date );
+
+ // now advance business days while id>=daylen
+ while( id >= DAYLEN )
+ {
+ next_business_day( date );
+ id -= DAYLEN;
+ }
+
+ int y = date->tm_year;
+ int mon = date->tm_mon;
+ int d = date->tm_mday;
+ if( debug )
+ {
+ printf( "debug abd: next business day y=%d, m=%d, "
+ "d=%d, duration=%g, id=%d, md=%d\n", y, mon, d,
+ duration, id, md );
+ }
+
+ h = date->tm_hour = STARTHOUR + id;
+ m = date->tm_min = md;
+ if( debug )
+ {
+ printf( "debug abd: time = %02d:%02d\n", h, m );
+ }
+ }
+}
+
+
+int main( int argc, char **argv )
+{
+ int argno = process_flag_n_args( "business-date", argc, argv,
+ 2, "datetime duration" );
+ char *dt = argv[argno++];
+ char *duration = argv[argno];
+
+ struct tm datetime;
+ memset( &datetime, 0, sizeof(struct tm) );
+ datetime.tm_min = -1;
+ char *result = strptime( dt, "%Y-%m-%d %H:%M", &datetime );
+ if( datetime.tm_min == -1 || (result != NULL && *result != '\0') )
+ {
+ fprintf( stderr, "business-date: can't parse datetime %s\n", dt );
+ exit(1);
+ }
+ if( ! is_business_day_and_time( datetime ) )
+ {
+ fprintf( stderr, "business-date: date %s - is "
+ "not a business day/time\n", dt );
+ exit(1);
+ }
+
+ char buf[255];
+
+ #if 0
+ strftime( buf, sizeof(buf), "%a %d %b %Y %H:%M", &datetime );
+ printf( "validated date/time: %s\n", buf);
+ #endif
+
+ double dur;
+ if( ! check_unsigned_real( duration, &dur ) )
+ {
+ fprintf( stderr, "business-date: can't parse duration %s\n",
+ duration );
+ exit(1);
+ }
+ #if 0
+ printf( "duration: %g hours\n", dur );
+ #endif
+
+ #if 0
+ int h = datetime.tm_hour;
+ int m = datetime.tm_min;
+ double rem = remainder_of_business_day( h, m );
+ printf( "remainder of busday: %g hours\n", rem );
+ #endif
+
+ #if 0
+ next_business_day( &datetime );
+ strftime( buf, sizeof(buf), "%a %d %b %Y %H:%M", &datetime );
+ printf( "next bus day date/time: %s\n", buf);
+ #endif
+
+ if( dur > 0 )
+ {
+ dur += 0.01; // fuzz factor to make rounding work better:-)
+ add_business_duration( &datetime, dur );
+ }
+ strftime( buf, sizeof(buf), "%a %d %b %Y %H:%M", &datetime );
+ printf( "final bus day date/time: %s\n", buf);
+
+ return 0;
+}
diff --git a/challenge-178/duncan-c-white/README b/challenge-178/duncan-c-white/README
index 500870a343..00a9b78b5a 100644
--- a/challenge-178/duncan-c-white/README
+++ b/challenge-178/duncan-c-white/README
@@ -1,46 +1,57 @@
-Task 1: Permuted Multiples
+Task 1: Quater-imaginary Base
-Write a script to find the smallest positive integer x such that x, 2x,
-3x, 4x, 5x and 6x are permuted multiples of each other.
+Write a script to convert a given number (base 10) to quater-imaginary
+base number and vice-versa. For more informations, please checkout
+the wiki page: https://en.wikipedia.org/wiki/Quater-imaginary_base
-For example, the integers 125874 and 251748 are permutated multiples of
-each other as
-
-251784 = 2 x 125874
-
-and also both have the same digits but in different order.
-
-Output
+For example,
- 142857
+$number_base_10 = 4
+$number_quater_imaginary_base = 10300
-MY NOTES: ok, sounds pretty easy. Compare permutation either by forming
-bags of digits and comparing them, or simply by sorting the digits
-numerically (a signature) and comparing signatures.
+MY NOTES: seriously? base -2i, and for task 1? what was Knuth smoking
+in 1960? First, we have to define "number" more carefully. I'm going to
+choose "positive integer", because that reduces the problem from base -2i
+to base -4 with zeroes between every pair of digits, and that's already
+much too horrible for a "task 1".
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: Reversible Numbers
+Task 2: Business Date
-Write a script to find out all Reversible Numbers below 100.
+You are given $timestamp (date with time) and $duration in hours.
-A number is said to be a reversible if sum of the number and its reverse
-had only odd digits.
+Write a script to find the time that occurs $duration business hours
+after $timestamp. For the sake of this task, let us assume the working
+hours is 9am to 6pm, Monday to Friday. Please ignore timezone too.
For example,
-36 is reversible number as 36 + 63 = 99 i.e. all digits are odd.
-17 is not reversible as 17 + 71 = 88, none of the digits are odd.
+Suppose the given timestamp is 2022-08-01 10:30 and the duration is 4 hours:
+then the next business date would be 2022-08-01 14:30.
-Output
+Similar if the given timestamp is 2022-08-01 17:00 and the duration
+is 3.5 hours, then the next business date would be 2022-08-02 11:30.
-10, 12, 14, 16, 18, 21, 23, 25, 27,
-30, 32, 34, 36, 41, 43, 45, 50, 52,
-54, 61, 63, 70, 72, 81, 90
+MY NOTES: ok, at least this is more straightforward. We sort of "wrap
+around" from date D, time 18:00 to date D+1 time 09:00 (when D is
+Mon..Thur), and similarly wrap around from Friday 18:00 to the following
+Monday 09:00..
-MY NOTES: Unusually, this seems even easier than task 1.
+My first version (ch-2.pl) shows how to cheat using Date::Manip, which
+already has a concept of business days which does of the work.
-GUEST LANGUAGE: As a bonus, I also had a go at translating ch-2.pl
-into C (look in the C directory for that).
+My second version (ch-2a.pl) shows an alternative where we do most of the
+work ourselves, needing only routines to:
+ - parse a calendar date and time, and
+ - move to the next calendar date, and
+ - determine which day of the week (Mon..Sun, 1..7) a date is
+ - break a date down into (year, month, day, hour, minutes)
+
+GUEST LANGUAGE: obviously ch-2.pl could only be translated into C
+by translating all the built-in business day logic of Date::Manip
+to C as well. But, having effectively done all that in the Perl
+universe in ch-2a.pl, I then had a go at translating ch-2a.pl into C
+(look in the C directory for that)
diff --git a/challenge-178/duncan-c-white/perl/ch-1.pl b/challenge-178/duncan-c-white/perl/ch-1.pl
new file mode 100755
index 0000000000..852cfdab45
--- /dev/null
+++ b/challenge-178/duncan-c-white/perl/ch-1.pl
@@ -0,0 +1,171 @@
+#!/usr/bin/perl
+#
+# Task 1: Quater-imaginary Base
+#
+# Write a script to convert a given number (base 10) to quater-imaginary
+# base number and vice-versa. For more informations, please checkout
+# the wiki page: https://en.wikipedia.org/wiki/Quater-imaginary_base
+#
+# For example,
+#
+# $number_base_10 = 4
+# $number_quater_imaginary_base = 10300
+#
+# MY NOTES: seriously? base -2i, and for task 1? what was Knuth smoking
+# in 1960? First, we have to define "number" more carefully. I'm going to
+# choose "positive integer", because that reduces the problem from base -2i
+# to base -4 with zeroes between every pair of digits, and that's already
+# much too horrible for a "task 1".
+#
+# 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;
+
+
+my $debug=0;
+die "Usage: to-from-quater-imaginary-base [--debug] to|from|check N\n"
+ unless GetOptions( "debug"=>\$debug ) && @ARGV==2;
+
+my $dir = shift; # to/from/check..
+my $n = shift;
+
+
+#
+# my $b4 = convert_to_base_neg4( $x );
+# Convert +int $x to base -4, return the base-4 numeral.
+#
+fun convert_to_base_neg4( $x )
+{
+ my $origx = $x;
+ my $b4 = "";
+ while( abs($x)>0 )
+ {
+ my $d = int($x/-4);
+ my $m = $x + 4*$d;
+ if( $m < 0 )
+ {
+ $m += 4;
+ $d++;
+ }
+ say "debug: c_t_b-4($origx): x=$x, d=$d, m=$m" if $debug;
+ $b4 .= $m;
+ $x = $d;
+ }
+ $b4 = reverse($b4);
+ $b4 = '0' if $b4 eq '';
+ say "debug: c_t_b-4($origx): result = $b4" if $debug;
+ return $b4;
+}
+
+
+#
+# my $qistr = convert_to( $n );
+# Convert +int $n to q-i, return the q-i numeral.
+#
+fun convert_to( $n )
+{
+ my $b4 = convert_to_base_neg4( $n );
+ my $str = $b4;
+ $str =~ s/(\d)/${1}0/g;
+ chop $str;
+ return $str;
+}
+
+
+#
+# my $n = convert_from( $qinum );
+# Given a q-i numeral $qinum (a string using only digits 0..3),
+# convert it from q-i to a plain +int.
+#
+fun convert_from( $qinum )
+{
+ my $n = 0;
+ my $len = length($qinum)-1;
+
+ # convert $qinum into a base -4 string, checking that the
+ # imaginary columns in $qinum are all 0 as we go.
+ my $bstr = "";
+ for( my $pos=$len; $pos>=0; $pos-- )
+ {
+ my $d = substr($qinum,$pos,1);
+ if( $pos%2 == 1 )
+ {
+ die "convert_from( $qinum ): digit $d at pos $pos ".
+ "should be zero\n" unless $d eq '0';
+ } else
+ {
+ $bstr .= $d;
+ }
+ }
+ $bstr = reverse($bstr);
+ $bstr = '0' if $bstr eq '';
+ say "debug: cf($qinum), bstr = $bstr" if $debug;
+
+ # now convert that..
+ return convert_from_base_neg4( $bstr );
+}
+
+
+#
+# my $n = convert_from_base_neg4( $bstr );
+# Given $bstr, a base -4 numeral representing a +int,
+# return that int.
+#
+fun convert_from_base_neg4( $bstr )
+{
+ my $n = 0;
+ my $currpow = 1;
+ my $len = length($bstr)-1;
+ for( my $pos=$len; $pos>=0; $pos-- )
+ {
+ my $d = substr($bstr,$pos,1);
+ say "debug: c_f_b-4($bstr): digit $d, currpow $currpow, ".
+ "n $n" if $debug;
+ $n += $d * $currpow;
+ $currpow *= -4;
+ }
+ say "debug: c_f_b-4($bstr): result is $n" if $debug;
+ return $n;
+}
+
+
+#say convert_to_base_neg4( $n );
+#exit 0;
+
+if( $dir eq "to" )
+{
+ die "to-from-quater-imaginary-base: to $n: must be +int\n"
+ unless $n>=0 && $n == int($n);
+ my $cistr = convert_to($n);
+ say $cistr;
+ #say "converted back to int: ". convert_from($cistr);
+} elsif( $dir eq "from" )
+{
+ die "to-from-quater-imaginary-base: from $n: all digits must be 0..3\n"
+ unless $n =~ /^[0-3]+$/;
+ say convert_from($n);
+} elsif( $dir eq "check" )
+{
+ die "to-from-quater-imaginary-base: check $n: must be +int\n"
+ unless $n>=0 && $n == int($n);
+ say "checking first $n conversions back-convert ok";
+ foreach my $x (0..$n)
+ {
+ my $cist