Re: Friday's the thirteenth. (And a little puzzle.)



pmj wrote:

... I've never learned the C Language...

Ok, I'll reproduce it with annotations. The texts that follows "//" are
C comments in the original; my additional comments now I'll prefix with
"--". My additional comments will occur before what I'm commenting on.

-- We're going to use two functions (that's C's name for them,
-- procedures or routines if you prefer) from the standard C library:
-- "printf" and "assert" and we need to tell the compiler and linker
-- about them so we include the text of two files "stdio.h" and
-- "assert.h" which are supplied with all C compilers. "stdio.h" has
-- the gen on printf (that's "print formatted", "print" in the sense
-- of send to the standard output device, usually the screen) and
-- "assert.h" has the gen on assert. The files are called "headers"
-- because they are usually included at the top of the program, and
-- thus ".h" is the usual extension. Actually, the C standard
-- doesn't required #include to _really_ include file text, it just
-- has to make known the required information, but the inclusion of
-- text (by something called the C pre-processor: part of the
-- compiler) is the usual method

#include <stdio.h>
#include <assert.h>

-- Certain "magic numbers" are going to feature such as 7 for the
-- number of days in the week. We use manifest constants in place
-- of numerals so that their meaning is clear. "#define xxx yyy"
-- means '"xxx" is really "yyy"'. (In C land things which begin with
-- "#" are called directives and are dealt with by the afore mentioned
-- preprocessor before the compiler proper gets to work. "mon" and
-- "sun" are going to be indexes into an array, since we're taking
-- "mon" to be the first day of the week, you might think that mon
-- should equal 1, but C arrays begin at 0. The other stuff that is
-- #defined is similar.

#define no_days_in_week 7
#define mon 0 // 'cuz C arrays start at 0
#define sun (no_days_in_week - 1)
#define no_months_in_year 12
#define jan 0 // Ditto.
#define feb (jan + 1)
#define dec (no_months_in_year - 1)

-- We're going to have an array, called "days_in_month" that will hold
-- the number of days in each month. All of the elements bar one we
-- can fill in when we declare the array, the exception is the number
-- of days in February which we need to calculate anew for each year.
-- A C declaration like "type1 function(type2 parameter)" tells the
-- compiler that function takes a parameter of a certain type and
-- returns a result of a certain type. Our "febdays" takes an integer
-- parameter (the year number) and gives us back another integer (the
-- number of days in February).

// For each year, calculate how many days February has.
int febdays(int year)
{

-- Mostly we'll return 28 so we'll have an integer "ret" initialized to
-- that which we'll return at the end. Between the initialization and
-- the return well deal with the exceptions.

int ret = 28;

-- xxx % yyy is the integer remainder after division of integer xxx by
-- integer yyy. "xxx == yyy" is true if xxx equals yyy and false
-- otherwise, so "if (year % 4) ..." does ... if year is divisible by
-- 4. The rule for leap years is:
-- if the year number is divisible by 4 it's a leap year, unless
-- the year number is divisible by 100 in which case it isn't, unless
-- the year number is divisible by 400 in which case it is.
-- The usual examples are:
-- the year 2000 is a candidate because 2000 is divisible by 4
-- it isn't ruled out by being divisible by 100 because it's
-- also divisible by 400.
-- The year 1900 is a candidate because 1900 is divisible by 4
-- it is ruled out by being divisible by 100 because it isn't
-- divisible by 400.

if (year % 4 == 0) // Divisible by 4, so
ret = 29; // a leap year unless...
if (year % 100 == 0) // divisible by 100
ret = 28; // not a leap year, unless...
if (year % 400 == 0) // divisible by 400.
ret = 29;

-- Having sorted that out, we return the result to whatever calls
-- febdays.
-- This is the point to remind you that Britain did not adopt the
-- Gregorian calendar until 1752: this program won't work for
-- earlier years.

return ret;
}

-- All C programs begin by executing a function called "main". Main
-- could return a value to the operating system but in this case we
-- don't need it to and it is declared "void". The parameter
-- declaration
-- "int argc, char *argv[]"
-- is unnecessary: the program would still compile and work if it was
-- omitted. If you want to know what it means, I'll tell you but not
-- now because it's irrelevant to this particular program.

void main(int argc, char *argv[])
{
// We're going to print "Mondays", "Tuesdays", etc, but "days"
// stays constant so we'll just changed the front part,
// which we'll call the prefix.
// Don't need no_days_in_week 'cuz compiler works it out,
// but do need sizeof "Wednes" (or similar, "Wednes" being chosen
// as the longest).

-- We're going to print each of "Monday", "Tuesday", "Wednesday",
-- "Thursday", "Friday", "Saturday" and "Sunday" and it would be
-- sensible to store those strings in an array indexed by "day" which
-- cycles from 0 to 6 ad nauseam, but I thought it would be fun (I
-- wasn't expecting my program to be read by anyone else) just to vary
-- the bit that needs to be varied and deal with the text "day"
-- separately, so my array will contain "Mon", "Tues", "Wednes",
-- "Thurs", "Fri", "Satur" and "Sun". To declare an array called
-- "array" of size "size" each element in it of type "type" we write
-- type array[size];
-- but in C strings are themselves arrays (of characters) so our
-- array is two-dimensional. It's called "prefix" because the the part
-- words "Mon", "Tues", etc are prefixes to the common suffix "day" so
-- we declare it
-- char prefix[size of "outer" array][size of "inner" array];
-- "char" being short for "character". The size of the "inner" array
-- is the length of the longest of the strings which is "Wednes". The
-- compiler works that out for us with "sizeof". "Wait a minute" I
-- hear you ask, "doesn't that mean that when a prefix that is shorter
-- than 'Wednes' is output, there'll be gaps like this:
-- 'Mon day', 'Tues day', 'Wednesday', 'Thurs day', etc?" No,
-- because C terminates it's strings with an invisible 0 and when
-- "printf" (see below) outputs it's text, it prints "Mon", sees the
-- terminating 0 and immediately outputs the "days" part. An easy
-- mistake to make would be to replace 'sizeof "Wednes"' with "6" it
-- should, because of that invisible 0, be "7": the best thing is to
-- let the compiler take care of it. Incidentally the size
-- "no_days_in_week" of the "outer" array is redundant; the compiler
-- can work it out for itself, but the size of the "inner" array must
-- be specified. (Perhaps the compiler says to itself "'ello ma'ey, I
-- need 4 chars for 'Mon' but 5 for 'Tues' (and so on), what am I to
-- do?")

char prefix[no_days_in_week][sizeof "Wednes"]
= {"Mon", "Tues", "Wednes", "Thurs", "Fri", "Satur", "Sun"};

-- We'll calculate the number of thirteenths on each day and store the
-- result in an array of integers "count"; the counts are initialized
-- to zero.

int count[no_days_in_week] = {0, 0, 0, 0, 0, 0, 0};

// February given 0 days arbitrarily because it'll sometimes be 28
// and sometimes 29.

-- Our calculation will need to know the number of days in each month.
-- We don't yet know what it is for February, but if the compiler is
-- to initialize the other parts of the array it needs _some_ number
-- in the second position. The use of "0" is arbitrary.

int days_in_month[no_months_in_year]
= {31, 0, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};

-- "day" is going to pick out one of the prefixes "Mon", "Tues", etc.
-- We'll start with the first.

int day = 0;

-- We'll need the month (as a number) and the year.

int month;
int year;

-- Each month has just one thirteenth so the total number of all the
-- thirteenths must equal the number of months in our chosen range of
-- years. We can use that fact to check our algorithm. The total is
-- accumulated in an integer "total".

int total; // For a final check.

/* Handy fact: in a 400 year period there are

400*365 + 100 - 4 + 1 = 146097

days, and 7|146097. I.e. there are a whole number (viz 20871)
of weeks in a 400 year period. No shorter period of
(a whole number of) years is divisible by 7. So we'll
consider any period of years so long as it's a multiple of 400
years long. Otherwise we'll end up with a fraction of a week
and some days-of-the-week will be considered once less
often than others.

Second handy fact: the first day of 2007 was a Monday.
*/

-- We're going to go round a loop dealing with each year in turn.
-- We'll start at 2007 and deal with 400 years (for the reasons
-- indicated in my first reply) i.e. we'll loop so long as
-- year < 2007 + 400
-- is true. "year++" means what some other, more prolix, languages
-- express by "year := year + 1" and that's what we'll do after each
-- time round the loop. This kind of brevity is one of the things I
-- like about C. (It is amusing that in
-- x := x + 1
-- the two "x"s have different meanings: the one on the left means
-- "the location called x", the one on the right means "the value of
-- the number stored in the location called x".)

for (year = 2007; year < 2007 + 400; year++)
{

-- For each year, calculate the number of days in February and put it
-- in the array days_in_month for later use.

days_in_month[feb] = febdays(year);

-- For each year, go round in a loop dealing with each month in turn.

for (month = jan; month <= dec; month++)
{

-- Only now do we get to the heart of the matter ("do the tricky stuff
-- as late as you can" is a good rule in programming--make a virtue of
-- prevarication). The day number in stored in the integer "day".
-- Once we've found the first thirteenth, the next is (what those
-- other languages call)
-- day := day + days_in_month[month];
-- C expresses that more briefly, as you'll see below. Since we're
-- starting with a year in which 1-st January was a Monday, and since
-- we initialized "day" to 0 when we declared it, the first thirteenth
-- is "day + 12". The days of the week number 0 to 6 unlike the days
-- in the year which number 0 to 365 or 366, so we reduces the day-in-
-- the-year number modulo no_of_days in week. I.e. we compute
-- (day + 12) % no_days_in_week
-- and store the result in an integer day_13.

int day_13;

// Count, for each day: mon, tue,..., the number of times
// that it is the 13-th.
// The first 13-th is day no. 12.
// Day-in-the-week is day no. mod no. of days in a week.
day_13 = (day + 12) % no_days_in_week;

-- We started with the thirteenth day (by adding 12) and since we're
-- going to add a month's worth of days to "day" each time round the
-- loop, day_13 will always be a thirteenth, so well add one on to the
-- day count each time we go round this inner loop. What other
-- languages express as
-- count[day_13] := count[day_13] + 1;
-- C expresses as..

count[day_13] += 1;
// The next 13-th is just one month later.

-- Having dealt with this months thirteenth, we'll step on to next
-- month. Other languages'
-- day := day + days_in_month[month];
-- in C is ...

day += days_in_month[month];

-- close off the two loops. I should already have remarked that
-- "{" and "}" are effectively "begin" and "end".

}
}

-- We're going to accumulate the total number of thirteenths in
-- "total" for checking purposes, so we better initialize it now. I
-- prefer to keep things as local as possible, so I initialize it near
-- were I use it. I could have initialized it in the declaration as I
-- did with "day", and I probably should have initialized "day" near
-- were I used it. It doesn't matter much in a small program, but if
-- it were a large one I'd slap myself on the wrist. My excuse is
-- that I just knocked this up in a hurry not realizing that anyone
-- else was going to read it.

total = 0;

-- We'll print the result in a loop through the days of the week.
-- Note that before "day" was a day in the year, now it's a day in the
-- week, no confusion I hope.

for (day = mon; day <= sun; day++)
{

-- The "printf" function is one of the Joys Of C and I can't go into
-- its details here. I'll just explain my particular use of it.
-- printf("... %d ... %s ...", x, y);
-- outputs (to the screen unless we've redirected the standard output
-- stream)
-- ... x ... y ...
-- "%" marks where the values of "x" and "y" are to be substituted,
-- "d" indicates that "x" had better be a decimal number (the
-- executable that C makes will happily print nonsense if the "%"
-- conversion specifications (as they are called) don't match with the
-- corresponding parameters). "\n" means newline: some combination
-- of linefeed (ascii 10) and carriage return (ascii 13) depending on
-- your computer. So,
-- "%d %sdays\n"
-- will be this:
-- a variable decimal number, followed by a space, followed
-- by a variable string, followed by the string "day",
-- followed by a newline
-- where the variable number is the number of thirteenths falling on
-- a given day and the variable string is the first part of the name
-- of the day in question (if m'lud pleases).

printf("%d %sdays\n", count[day], prefix[day]);
// A little check-ule is easily made: there are as many
// 13-ths in 400 years as there are months, i.e.
// 400*12 = 4800. So the sum of the numbers just output
// should be == 4800.

-- meanwhile we add up the number of thirteenths,

total += count[day];

-- and close the for-loop.

}

-- The function "assert" is one of C's best features. If, in
-- assert(p)
-- "p" is true, nothing happens. If "p" is false, the program stops
-- and some useful info is printed to the standard error stream (i.e.
-- the screen if it's not been redirected). That message specifies
-- the line number and file name where the assertion failed, it may
-- also specify the function name. It's a boon for debugging.

assert(total == 400*no_months_in_year);
}

If you want to use C, you'll find free compilers on the Internet,
including a Gnu one. There's also a Borland (is that what they call
themselves now?) one. For books

"The C Programming Language"
by Brian W Kernigan and Dennis M Ritchie

is well-nigh essential. It's published by Prentice Hall and the first
edition should be avoided since it refers to stuff that is long
out of date. Also very good is

"C a Reference Manual"
by Samuel P Harbison and Guy L Steele Jr.

It has a lot more to say about the standard libraries. No doubt there
are books addressed to dummies. If you don't mind being called a dummy,
that up to you, but I find these books as useless as their titles are
offensive. Also the "Learn blah in seven days"-type books are no good.
There is a sense in which one _could_ learn C in seven days; but learn
all its features _and_ how to use them well? I doubt it. That's not
because C is complex--it's a lot smaller than many other languages--I
don't think one could learn to use well any language in that time.

--
Remove "antispam" and ".invalid" for e-mail address.
We have lingered in the chambers of the sea
By sea-girls wreathed with seaweed red and brown
Till human voices wake us, and we drown.
.