From 3a88c896214c8a3d239ac059a176373fe717b63b Mon Sep 17 00:00:00 2001 From: dcw Date: Sun, 28 Aug 2022 20:34:44 +0100 Subject: imported my belated attempt at challenge 178, I was still away last week.. base -2i is very weird, the business date task was quite interesting to do without pre-built business hours support. Translated both tasks to C as well. --- challenge-178/duncan-c-white/C/.cbuild | 3 +- challenge-178/duncan-c-white/C/Makefile | 14 ++ challenge-178/duncan-c-white/C/README | 12 ++ challenge-178/duncan-c-white/C/args.c | 207 ++++++++++++++++++++ challenge-178/duncan-c-white/C/args.h | 11 ++ challenge-178/duncan-c-white/C/ch-1.c | 294 +++++++++++++++++++++++++++++ challenge-178/duncan-c-white/C/ch-2a.c | 242 ++++++++++++++++++++++++ challenge-178/duncan-c-white/README | 67 ++++--- challenge-178/duncan-c-white/perl/ch-1.pl | 171 +++++++++++++++++ challenge-178/duncan-c-white/perl/ch-2.pl | 62 ++++++ challenge-178/duncan-c-white/perl/ch-2a.pl | 187 ++++++++++++++++++ 11 files changed, 1241 insertions(+), 29 deletions(-) create mode 100644 challenge-178/duncan-c-white/C/Makefile create mode 100644 challenge-178/duncan-c-white/C/README create mode 100644 challenge-178/duncan-c-white/C/args.c create mode 100644 challenge-178/duncan-c-white/C/args.h create mode 100644 challenge-178/duncan-c-white/C/ch-1.c create mode 100644 challenge-178/duncan-c-white/C/ch-2a.c create mode 100755 challenge-178/duncan-c-white/perl/ch-1.pl create mode 100755 challenge-178/duncan-c-white/perl/ch-2.pl create mode 100755 challenge-178/duncan-c-white/perl/ch-2a.pl 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 +#include +#include +#include +#include +#include + + +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 +#include +#include +#include +#include +#include + +#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; q0 ) + { + 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 +#include +#include +#include + +#define __USE_XOPEN +#include + +#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 $cistr = convert_to($x); + #say $cistr; + my $back = convert_from($cistr); + die "check: x=$x, qi($x)=$cistr, back($cistr)=$back\n" + unless $x==$back; + } +} else +{ + die "to-from-quater-imaginary-base: direction $dir must be ". + "to or from or check\n"; +} diff --git a/challenge-178/duncan-c-white/perl/ch-2.pl b/challenge-178/duncan-c-white/perl/ch-2.pl new file mode 100755 index 0000000000..6fe141cde3 --- /dev/null +++ b/challenge-178/duncan-c-white/perl/ch-2.pl @@ -0,0 +1,62 @@ +#!/usr/bin/perl +# +# Task 2: Business Date +# +# You are given $timestamp (date with time) and $duration in hours. +# +# 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, +# +# 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. +# +# 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. +# +# 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.. +# +# Investigating what Date and Time module to use, I realise that our old friend +# Date::Manip allows us to cheat: it has a nearly matching concept of business +# dates (Monday..Friday, 08:00..17:00), so we can just use that, altering the +# start and end business hours via: +# $date->config( "WorkDayBeg", "09:00:00", "WorkDayEnd", "18:00:00" ); +# and then doing the calculation via the magic incantation: +# $delta->parse("in $duration business hours"); +# +# ch-2a.pl shows an alternative where we do it ourselves. +# + +use strict; +use warnings; +use feature 'say'; +use Getopt::Long; +use Data::Dumper; +use Date::Manip::Date; +use Date::Manip::Delta; + +my $debug=0; +die "Usage: business-date [--debug] DATETIME DURATION\n" + unless GetOptions( "debug"=>\$debug ) && @ARGV==2; + +my $dt = shift; +my $duration = shift; + +my $date = new Date::Manip::Date; +$date->config( "WorkDayBeg", "09:00:00", "WorkDayEnd", "18:00:00" ); +my $err = $date->parse( $dt ); +die "business date: date $dt - parse error $err\n" if $err; +die "business date: date $dt - is not a business day/time\n" unless + $date->is_business_day( 1 ); + +my $delta = new Date::Manip::Delta; +$err = $delta->parse("in $duration business hours"); +die "business date: duration $delta - parse error $err\n" if $err; + +my $d = $date->calc( $delta ); +say $d->printf("%Y-%m-%d %H:%M"); diff --git a/challenge-178/duncan-c-white/perl/ch-2a.pl b/challenge-178/duncan-c-white/perl/ch-2a.pl new file mode 100755 index 0000000000..9ae6b43392 --- /dev/null +++ b/challenge-178/duncan-c-white/perl/ch-2a.pl @@ -0,0 +1,187 @@ +#!/usr/bin/perl +# +# Task 2: Business Date +# +# You are given $timestamp (date with time) and $duration in hours. +# +# 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, +# +# 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. +# +# 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. +# +# MY NOTES: ok, this is relatively straightforward. We have to "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 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. +# +# This 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) + +use strict; +use warnings; +use feature 'say'; +use Getopt::Long; +use Data::Dumper; +use Date::Manip::Date; +use Date::Manip::Delta; +use Function::Parameters; + +my $starthour = 9; +my $endhour = 18; +# experimented with much shorter day: $starthour = 14; +# experimented with much shorter day: $endhour = 17; + +my $daylen = $endhour - $starthour; + +my $debug=0; +die "Usage: business-date [--debug] DATETIME DURATION\n" + unless GetOptions( "debug"=>\$debug ) && @ARGV==2; + +my $dt = shift; +my $duration = shift; + +my $date = Date::Manip::Date->new; +my $err = $date->parse( $dt ); +die "business date: date $dt - parse error $err\n" if $err; +die "business date: date $dt - is not a business day/time\n" unless + is_business_day_and_time( $date ); + + +# +# my $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. +# +fun is_business_day_and_time( $date ) +{ + my $dow = $date->printf("%w"); + say "debug ibdt: dow=$dow" if $debug; + return 0 unless $dow>=1 && $dow<6; + + my( $y, $m, $d, $h, $mn ) = $date->value; + say "debug ibdt: y=$y, m=$m, d=$d, h=$h, mn=$mn" if $debug; + return 1 if $h>=$starthour && $h<$endhour; + return 1 if $h==$endhour && $mn==0; + return 0; +} + + +# +# my $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. +# +fun remainder_of_business_day( $h, $m ) +{ + my $t = $h + $m / 60; + return $endhour - $t; +} + + +# +# $date = next_business_day( $date ); +# Move $date to the next business day. +# +fun next_business_day( $date ) +{ + my $oneday = Date::Manip::Delta->new; + my $err = $oneday->parse( "24 hours" ); + die "BUG: oneday '24 hours' - parse error $err\n" if $err; + + my $dow = $date->printf("%w"); + say "debug nbd: dow=$dow" if $debug; + + do + { + # move to next physical day + $date = $date->calc( $oneday ); + $dow++; + $dow = 1 if $dow>7; + } while( $dow > 5 ); + return $date; +} + + +# +# $date = add_business_duration( $date, $duration ); +# Add a +ve duration $duration (real, business hours) to a date $date, +# returning the resultant date. This must skip from $endhour:00 to +# $starthour:00 on the next business day, where necessary. +# +fun add_business_duration( $date, $duration ) +{ + my( $y, $m, $d, $h, $mn ) = $date->value; + say "debug abd: y=$y, m=$m, d=$d, h=$h, mn=$mn" if $debug; + + my $rem = remainder_of_business_day( $h, $mn ); + say "debug abd: remainder of day = $rem hours" if $debug; + + if( $duration <= $rem ) + { + my $id = int($duration); + my $md = int(($duration-$id)*60); + say "debug abd: duration=$duration, id=$id, md=$md" if $debug; + + $h += $id; + $mn += $md; + if( $mn > 60 ) + { + $mn -= 60; + $h++; + } + } else + { + # duration goes onto another day; move to end of the day + $duration -= $rem; + + my $id = int($duration); + my $md = int(($duration-$id)*60); + say "debug abd: remaining duration=$duration, id=$id, md=$md" + if $debug; + + # now advance $date to next business day + $date = next_business_day( $date ); + + # now advance business days while id>=$daylen + while( $id >= $daylen ) + { + $date = next_business_day( $date ); + $id -= $daylen; + } + + ( $y, $m, $d ) = $date->value; + say "debug abd: next business day y=$y, m=$m, d=$d, ". + "duration=$duration, id=$id, md=$md" if $debug; + + $h = $starthour + $id; + $mn = $md; + say "debug abd: time = $h:". sprintf("%02d",$mn) if $debug; + } + + my $dstr = "$y-$m-$d $h:". sprintf( "%02d", $mn ); + my $err = $date->parse( $dstr ); + die "add_business: date $dstr - parse error $err\n" if $err; + return $date; +} + + +if( $duration > 0 ) +{ + $duration += 0.01; # fuzz factor to make rounding work better:-) + $date = add_business_duration( $date, $duration ); +} +say $date->printf("%Y-%m-%d %H:%M"); -- cgit From 023cace3dc87483c840617e2a2a85df30d2c2817 Mon Sep 17 00:00:00 2001 From: dcw Date: Sun, 28 Aug 2022 23:46:27 +0100 Subject: just had a go at task 1, nice challenge, did it in C and Perl. as to the 2nd task, I'm not a fan of Unicode, I don't know what a "Sparkline" is, and I'm tired:-) --- challenge-179/duncan-c-white/C/.cbuild | 3 +- challenge-179/duncan-c-white/C/Makefile | 12 ++ challenge-179/duncan-c-white/C/README | 8 + challenge-179/duncan-c-white/C/args.c | 207 ++++++++++++++++++++++++ challenge-179/duncan-c-white/C/args.h | 11 ++ challenge-179/duncan-c-white/C/ch-1.c | 261 ++++++++++++++++++++++++++++++ challenge-179/duncan-c-white/README | 51 ++---- challenge-179/duncan-c-white/perl/ch-1.pl | 156 ++++++++++++++++++ 8 files changed, 674 insertions(+), 35 deletions(-) create mode 100644 challenge-179/duncan-c-white/C/Makefile create mode 100644 challenge-179/duncan-c-white/C/README create mode 100644 challenge-179/duncan-c-white/C/args.c create mode 100644 challenge-179/duncan-c-white/C/args.h create mode 100644 challenge-179/duncan-c-white/C/ch-1.c create mode 100755 challenge-179/duncan-c-white/perl/ch-1.pl diff --git a/challenge-179/duncan-c-white/C/.cbuild b/challenge-179/duncan-c-white/C/.cbuild index aebcd2040a..624a95ebfb 100644 --- a/challenge-179/duncan-c-white/C/.cbuild +++ b/challenge-179/duncan-c-white/C/.cbuild @@ -1,3 +1,4 @@ -BUILD = ch-1 ch-2 +BUILD = ch-1 CFLAGS = -Wall -g +#LDFLAGS = -lm #CFLAGS = -g diff --git a/challenge-179/duncan-c-white/C/Makefile b/challenge-179/duncan-c-white/C/Makefile new file mode 100644 index 0000000000..3574b1eed4 --- /dev/null +++ b/challenge-179/duncan-c-white/C/Makefile @@ -0,0 +1,12 @@ +BUILD = ch-1 +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 diff --git a/challenge-179/duncan-c-white/C/README b/challenge-179/duncan-c-white/C/README new file mode 100644 index 0000000000..12ccf8a6e3 --- /dev/null +++ b/challenge-179/duncan-c-white/C/README @@ -0,0 +1,8 @@ +Thought I'd also have a go at translating ch-1.pl into C.. + +ch-1.c produces identical (non-debugging) output to my Perl originals. + +It uses the command-line argument processing module args.[ch]. + +It was 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-179/duncan-c-white/C/args.c b/challenge-179/duncan-c-white/C/args.c new file mode 100644 index 0000000000..d4a2d38b9a --- /dev/null +++ b/challenge-179/duncan-c-white/C/args.c @@ -0,0 +1,207 @@ +#include +#include +#include +#include +#include +#include + + +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 +#include +#include +#include +#include +#include + +#include "args.h" + + +// rules to deal with "formwords".. + +char *oneto19[] = { + "one", "two", "three", "four", "five", "six", "seven", "eight", + "nine", "ten", "eleven", "twelve", "thirteen", "fourteen", + "fifteen", "sixteen", "seventeen", "eighteen", "nineteen" + }; + +char *tens[] = { + "twenty", "thirty", "forty", "fifty", "sixty", "seventy", + "eighty", "ninety" + }; + + +// char words[1000]; +// inner_formwords( n, words ); +// Generate the English form of n, writing it into words. +// eg if n=17, set words to "seventeen". +void inner_formwords( int n, char *words ) +{ + if( n < 20 ) + { + strcpy( words, oneto19[n-1] ); + return; + } + + if( n < 100 ) + { + char *t = tens[n/10-2]; + if( debug ) + { + printf( "debug: n=%d, tens=%s\n", n, t ); + } + strcpy( words, t ); + if( n % 10 != 0 ) + { + strcat( words, "-" ); + strcat( words, oneto19[n%10-1] ); + } + return; + } + + if( n < 1000 ) + { + int mod = n%100; + n /= 100; + strcpy( words, oneto19[n-1] ); + strcat( words, "-hundred" ); + if( mod != 0 ) + { + strcat( words, "-and-" ); + inner_formwords( mod, words+strlen(words) ); + } + return; + } + + if( n < 1000000 ) + { + int mod = n%1000; + n /= 1000; + inner_formwords( n, words ); + strcat( words, "-thousand" ); + if( mod != 0 ) + { + strcat( words, "-and-" ); + inner_formwords(mod, words+strlen(words) ); + } + return; + } +} + + +// remove_extra_ands( words ); +// If the string words contains more than one "-and-", +// remove excess "and-"s leaving only the last. +// +void remove_extra_ands( char *words ) +{ + for(;;) + { + // locate the first -and-, if any + char *fst = strstr( words, "-and-" ); + if( fst == NULL ) return; + + // locate the second -and-, if any + char *snd = strstr( fst+1, "-and-" ); + if( snd == NULL ) return; + + if( debug ) + { + printf( "found two -and-s in %s\n", words ); + } + + // remove the first + char *d = fst+1; + for( char *s = fst+5; *s; *d++ = *s++ ) + { + /*EMPTY*/ + } + *d = '\0'; + + if( debug ) + { + printf( "removed the first leaving %s\n", words ); + } + + } +} + + +// +// char words[1000]; +// formwords( n, words ); +// Generate the English form of n, writing it into words, +// checking the range of n, and removing surplus "-and-"s. +// eg if n=17, set words to "seventeen". +// +void formwords( int n, char *words ) +{ + if( n < 1 ) + { + fprintf( stderr, "formwords: n (%d) must be > 0\n", n ); + exit(1); + } + if( n >= 1000000 ) + { + fprintf( stderr, "formwords: n (%d) must be < 1000000\n", n ); + exit(1); + } + + inner_formwords( n, words ); + + remove_extra_ands( words ); +} + + +// rules to do with "nth" logic + +// a useful type.. +typedef struct { char *from; char *to; } pair; + + +// special cases, eg "one" -> "first".. +pair special[] = { + { "one", "first" }, + { "two", "second" }, + { "three", "third" }, + { "five", "fifth" }, + { "eight", "eighth" }, + { "twelve", "twelfth" }, +}; + + +#define NSPECIALS (sizeof(special)/sizeof(special[0])) + + +// +// char *spec = lookup_special( word ); +// Lookup word to see if it's special; +// return NULL if it's not, or the equivalent +// of the special word if it is. +// +char *lookup_special( char *word ) +{ + for( int i=0; i < NSPECIALS; i++ ) + { + if( strcmp( special[i].from, word ) == 0 ) + { + return special[i].to; + } + } + return NULL; +} + + +// +// char words[1000]; +// nth( n, words ); +// Generate the "nth" English form of n in words, +// eg if n=17, set words to "seventeenth". Do it using formwords() +// and then mucking about with the ending.. +// +void nth( int n, char *words ) +{ + formwords( n, words ); + + char *dash = strchr( words, '-' ); + char prefix[1000]; + char lastbit[1000]; + + if( dash != NULL ) + { + *dash = '\0'; + strcpy( prefix, words ); + strcpy( lastbit, dash+1 ); + } else + { + strcpy( prefix, "" ); + strcpy( lastbit, words ); + } + + char *spec = lookup_special( lastbit ); + if( spec != NULL ) + { + strcpy( lastbit, spec ); + } + else + { + char *last = lastbit+strlen(lastbit)-1; + if( *last == 'y' ) + { + strcpy( last, "ie" ); + } + strcat( lastbit, "th" ); + } + + if( *prefix != '\0' ) + { + sprintf( words, "%s-%s", prefix, lastbit ); + } else + { + strcpy( words, lastbit ); + } +} + + +int main( int argc, char **argv ) +{ + int argno = process_flag_n_args( "nth", argc, argv, + 1, "N (0..999999)" ); + char *arg = argv[argno]; // N + + int n; + if( !check_unsigned_int(arg,&n) ) + { + fprintf( stderr, "nth: arg %s must be +int\n", arg ); + exit(1); + } + char words[1000]; + //formwords( n, words ); + nth( n, words ); + //printf( "n=%d, word=%s\n", n, words ); + printf( "%s\n", words ); + return 0; +} diff --git a/challenge-179/duncan-c-white/README b/challenge-179/duncan-c-white/README index 500870a343..635f772de2 100644 --- a/challenge-179/duncan-c-white/README +++ b/challenge-179/duncan-c-white/README @@ -1,46 +1,29 @@ -Task 1: Permuted Multiples +Task 1: Ordinal Number Spelling -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. +You are given a positive number, $n. +Write a script to spell the ordinal number. -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 +11 => eleventh +62 => sixty-second +99 => ninety-ninth -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: ok, sounds pretty easy for n>0; let's arbitrarily deal with +n < 1000000 (although millions, billions etc would be pretty easy to add). +Let's do this via formwords(n) which converts (eg) n=17 to "seventeen" and +then use that to generate the final "seventeeth" (nth, ordinal, form) by +mucking about with the ending (and using some special case rules for English, +eg mapping one->first) 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 - -Write a script to find out all Reversible Numbers below 100. +Task 2: Unicode Sparkline -A number is said to be a reversible if sum of the number and its reverse -had only odd digits. +You are given a list of positive numbers, @n. -For example, +Write a script to print sparkline in Unicode for the given list of numbers. -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. - -Output - -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: Unusually, this seems even easier than task 1. - -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 NOTES: What on earth is "sparkline"? Not a fan of Unicode either, forget it. diff --git a/challenge-179/duncan-c-white/perl/ch-1.pl b/challenge-179/duncan-c-white/perl/ch-1.pl new file mode 100755 index 0000000000..4a343ffb73 --- /dev/null +++ b/challenge-179/duncan-c-white/perl/ch-1.pl @@ -0,0 +1,156 @@ +#!/usr/bin/perl +# +# Task 1: Ordinal Number Spelling +# +# You are given a positive number, $n. +# Write a script to spell the ordinal number. +# +# For example, +# +# 11 => eleventh +# 62 => sixty-second +# 99 => ninety-ninth +# +# MY NOTES: ok, sounds pretty easy for n>0; let's arbitrarily deal with +# n < 1000000 (although millions, billions etc would be pretty easy to add). +# Let's do this via formwords(n) which converts (eg) n=17 to "seventeen" and +# then use that to generate the final "seventeeth" by mucking about with the +# ending (and using some special case rules for English, eg one->first) +# +# 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: nth [--debug] N\n" + unless GetOptions( "debug"=>\$debug ) && @ARGV==1; + + +# rules to deal with "formwords".. + +my @oneto19 = qw(one two three four five six seven eight nine ten eleven twelve thirteen fourteen fifteen sixteen seventeen eighteen nineteen); +my @tens = qw(twenty thirty forty fifty sixty seventy eighty ninety); + + +# +# my $count = formwords( $n ); +# Generate and return the English form of $n, +# eg if n=17, return "seventeen". +# +fun formwords( $n ) +{ + die "formwords: n ($n) must be > 0\n" if $n<1; + + my $count = inner_formwords( $n ); + + # let's have no more than one "-and-".. + $count =~ s/and-// while $count =~ /-and-.*-and-/; + + return $count; +} + + +# +# my $count = inner_formwords( $n ); +# Generate and return the English form of $n, +# eg if n=17, return "seventeen". +# +fun inner_formwords( $n ) +{ + die "formwords: n ($n) must be > 0\n" if $n<1; + return $oneto19[$n-1] if $n<20; + if( $n < 100 ) + { + my $tens = $tens[int($n/10)-2]; + say "debug: n=$n, tens=$tens" if $debug; + if( $n % 10 == 0 ) + { + return $tens; + } + my $units = $oneto19[$n%10-1]; + return "$tens-$units"; + } + if( $n < 1000 ) + { + my $mod = $n%100; + $n = int($n/100); + my $hundreds = $oneto19[$n-1]; + return "$hundreds-hundred" if $mod==0; + my $fw = inner_formwords($mod); + return "$hundreds-hundred-and-$fw"; + } + if( $n < 1000000 ) + { + my $mod = $n%1000; + $n = int($n/1000); + my $thousands = inner_formwords($n); + my $result = "$thousands-thousand"; + return $result if $mod==0; + + $result .= "-and-". inner_formwords($mod); + return $result; + } + return "$n >= 1000000 to do"; +} + + +my %special = qw(one first two second three third five fifth + eight eighth twelve twelfth); + +# +# my $nth = nth( $n ); +# Generate and return the "nth" English form of $n, +# eg if n=17, return "seventeenth". Do it using formwords() +# and then mucking about with the ending.. +# +fun nth( $n ) +{ + my $fw = formwords( $n ); + + my( $prefix, $lastbit ); + if( $fw =~ /^(.+)-([^-]+)$/ ) + { + $prefix = $1; + $lastbit = $2; + } else + { + $prefix = ""; + $lastbit = $fw; + } + + if( defined $special{$lastbit} ) + { + $lastbit = $special{$lastbit}; + } + else + { + $lastbit =~ s/y$/ie/; + $lastbit .= "th"; + } + + if( $prefix ) + { + return "$prefix-$lastbit"; + } else + { + return $lastbit; + } +} + + + +my $n = shift; +die "nth: n ($n) must be > 0 and < 1,000,000\n" if $n<=0 || $n>1000000; + +#my $count = formwords( $n ); +#say $count; +my $nth = nth($n); +say $nth; -- cgit