In Memory of
Niklaus Wirth

Ryan R. Kelly

A few years ago, I bought a used copy of the programming text book, Algorithms + Data Structures = Programs by Niklaus Wirth, published in 1976. I read through it quickly, and then I decided to implement the examples and work through the exercises.

The programming language presented in the text is Pascal, and because I had not written anything in Pascal since the 1990s, I chose to take a step back and picked up the Pascal User Manual and Report, 4th Edition. Over the last few months of 2023, I casually studied the example programs in the manual and ran them using the Free Pascal compiler.

As the new year began, I turned my attention back to Algorithms + Data Structures = Programs. Although one could use any programming language to grasp the ideas in the book, I've been enjoying working through the exercises in Pascal in an attempt to dwell in the style of that era.

Wirth seems to have viewed a computer program as the expression of a purely abstract idea which should not be bound to the physical device that may be executing it. He designed Pascal to hide the details of the computer's low level architecture as much as possible. In his exposition of fundamental data types and algorithms, you'll find no mention of bytes or twos complement integer encodings, though some of the examples and exercises leave no doubt that he is fully aware of the importance of word sizes, floating point representations, and address alignment. Even as he prompts the reader to consider how one might implement an array or a record at the machine level, he gives the stronger impression that programs can and should be written at a higher level, independent of the machine details.

The design of the Pascal language also reveals Wirth's affinity for simplicity and efficiency. Although I find Pascal's syntax to be verbose and error-prone, I do appreciate that it is consistent and regular. It is a language purposely formulated to be easy to parse and compile.

Niklaus Wirth died on New Year's Day 2024, at the age of 89. Today would have been his 90th birthday. In his memory, here is my solution to the final exercise in the first chapter of the 1976 edition of his book.

This is not the most exciting exercise in the chapter. The prompts to implement versions of Pascal's file I/O routines using arrays were more fun, and the exercise on formatting floating point numbers was difficult. Some of the exercises involving character input, in particular the implementation of a prototype ed style text editor, expose the frustrating mismatch between a Linux console's line discipline and the text file model Pascal implements.

Exercise 14, however, is the most precisely specified programming task in the first chapter, and it also stands out for requiring its output to be sorted. Algorithms for sorting are the subject of the second chapter.

{ Algorithms + Data Structures = Programs
  Niklaus Wirth
  ISBN 0-13-022418-9
  Chapter 1. Page 54.
  Exercise 1.14.

Implemented by Ryan Kelly on February 4, 2024.

A company organizes a poll to determine the success of its products.  Its
products are records and tapes of hits, and the most popular hits are to be
broadcast in a hit parade.  The polled population is to be devided into four
categories according to sex and age (say, less or equal to 20, and older than
20).  Every person is asked to name five hits.  Hits are identified by the 
numbers 1 to N (say, N = 30).  The results of the poll are represented by a
file.

   type hit = 1..N;
        sex = (male,female);
        response =
           record name, firstname: alfa;
                  s: sex;
                  age: integer;
                  choice: array [1..5] of hit
           end ;
   var  poll: file of response

Hence, each file element represents a respondent and lists his name, first
name, sex, age, and his five preferred hits according to priority.  This file
is the input to a program which is supposed to compute the following results:

1. A list of hits in the order of their popularity.  Each entry consists of the
hit number and the number of times it was mentioned in the poll. Hits that
were never mentioned are omitted from the list.
2. Four seperate lists with the names and first names of all respondents who
had mentioned in first place one of the three hits most popular in their
category.

The five lists are to be preceded by suitable titles.
}
{$mode ISO}
program PollReports(Output, Poll);

   const
      N          = 30; {Number of hits tracked by the poll}
      MaxChoice  = 5;  {Number of hit choices per respondant}
      MaxAlpha   = 10; {Length of name strings, defined earlier in the text }
      AdultAge   = 21; {Least adult age, for categorization of respondants}
      OrderLimit = 3;  {Ranking limit for inclusion in Category reports}

   type
      Hit      = 1..N;
      Sex      = (Male, Female);
      Category = (AllListeners, AdultMen, AdultWomen, YoungMen, YoungWomen);
      Alpha    = array [1..MaxAlpha] of Char; { defined earlier in chapter 1 }
      Response = record
         Name     : Alpha;
         FirstName: Alpha;
         S        : Sex;
         Age      : Integer;
         Choice   : Array [1..MaxChoice] of Hit
      end;
      Score    = array [Category] of Integer;

   var
      Poll: file of Response;
      Scores: array [Hit] of Score;
      Order: array [1..N] of Hit;


   function ResponseCategory(var R: Response): Category;
   { Determine category of respondant by age and sex. }

      var result: Category;

   begin { ResponseCategory }
      if (R.Age >= AdultAge ) then
         if (R.S = Male) then
            result := AdultMen
         else
            result := AdultWomen
      else
         if (R.s = Male) then
            result := YoungMen
         else
            result := YoungWomen;
      ResponseCategory := result
   end   { ResponseCategory };


   procedure InitOrder();
   { Initialize ranking order }

      var
         I: Integer;

   begin { InitOrder }
      for I := 1 to N do
         Order[I] := I
   end { InitOrder };


   procedure ScoreHits();
   { Count mentions of each hit for all categories }

      var
         R: Response;
         H: Hit;
         I: Integer;
         C: Category;

   begin { ScoreHits }
      for I := 1 to N do
         for C := AllListeners to YoungWomen do
            Scores[I][C] := 0;
      Reset(Poll);
      while not Eof(Poll) do begin
         Read(Poll, R);
         for I := 1 to MaxChoice do begin
            H := R.Choice[I];
            Scores[H][AllListeners] := Scores[H][AllListeners] + 1;
            C := ResponseCategory(R);
            Scores[H][C] := Scores[H][C] + 1;
         end
      end
   end { ScoreHits };


   procedure SortOrder(C: Category);
   { Sort the ranking by descending number of mentions in category }

      function Compare(I, J: Hit; C: Category): Integer;
      { Compare score for category C of hit I and J. Used by Sort below}

         var
            Result: Integer;
            S1, S2: Integer;

      begin { Compare }
         S1 := Scores[I][C];  S2 := Scores[J][C];
         if S1 < S2 then
            Result := -1
         else if S1 = S2 then
            Result := 0
         else
            Result := 1;
         Compare := Result
      end { Compare };


      procedure Swap(I, J: Integer);
      { Swap hit in ranking order array. Used by Sort below}

         var T: Hit;

      begin { Swap }
         T := Order[I];
         Order[I] := Order[J];
         Order[J] := T
      end { Swap };


      procedure Sort(Left, Right: Integer; C: Category);
      { Recursive QuickSort - based on examples from
        The C Programming Language - Second Edition
        Brian W. Kernighan and Dennis M. Ritchie
        ISBN 0-13-110362-8
        ... because I haven't gotten to chapter two of Wirth's
        text. :) }

        var
          I, Last : Integer;

      begin { Sort }
         if Left < Right then begin
            Swap(Left, (Left + Right) div 2);
            Last := Left;
            for I := Left+1 to Right do
              if 0 < Compare(Order[I], Order[Left], C) then begin
                 Last := Last + 1;
                 Swap(Last, I)
              end;
            Swap(Left, Last);
            Sort(Left, Last, C);
            Sort(Left+1, Right, C);
         end
      end { Sort };

   begin { SortOrder }
     Sort(1, N, C)
   end { SortOrder };


   procedure ReportPopularity();
   { Write report of hits sorted by all mentions, excluding hits
     with no mentions }

      var
         I: Integer;
         H: Hit;

   begin { ReportPopularity }
      SortOrder(AllListeners);
      Writeln('--- Hit Popularity ---');
      Writeln('Hit':11,  'Mentions':11);
      for I := 1 to N do begin
         H := Order[I];
         if 0 < Scores[H][AllListeners] then
            Writeln(H:11, Scores[H][AllListeners]:11);
      end
   end { ReportPopularity };


   procedure WriteResponseName(var R: Response);
   { Write respondant R's name with new line. Used by ReportCategory below}

      procedure WriteAlpha(var A: Alpha);
      { Write an Alpha's contents, ending at the first non-alphabetical
        charater. }

         var
            I: Integer;
            Flag : Boolean;
            AlphaChars : set of Char;

      begin { WriteAlpha }
         I := 1;  Flag := True;
         AlphaChars := ['A'..'Z'] + ['a'..'z'];
         while (I <= MaxAlpha) and Flag do begin
            if A[I] in AlphaChars then
               Write(A[I])
            else
               Flag := False;
            I := I + 1
         end
      end { WriteAlpha };

   begin { WriteResponseName }
      WriteAlpha(R.FirstName);  Write(' ');  WriteAlpha(R.Name);
      Writeln()
   end { WriteResponseName };


   procedure ReportCategory(C: Category);
   { Report all respondants with first hit choice in the top choices
     in their category }

      var
         R: Response;
         Rc: Category;

      procedure ReportHeading();
      { Write the report heading base on category C}

      begin { Report Heading }
         Write('--- ');
         case C of
            AllListeners: Write('All Listeners');
            AdultMen    : Write('Adult Men');
            AdultWomen  : Write('Adult Women');
            YoungMen    : Write('Young Men');
            YoungWomen  : Write('Young Women');
         end;
         Writeln(' ---');
      end { Report Heading };


      function IsTopMention(H: Hit): Boolean;
      { Determine if hit H is a top choice in the current ranking order }

         var
            I: Integer;
            Flag: Boolean;

      begin { IsTopMention }
         Flag := False;
         for I := 1 to OrderLimit do
            if Order[I] = H then
               Flag := True;
         IsTopMention := Flag
      end { IsTopMention };


   begin { ReportCategory }
      SortOrder(C);
      Reset(Poll);
      ReportHeading();
      while not Eof(Poll) do begin
         Read(Poll, R);
         Rc := ResponseCategory(R);
         if (Rc = C) and IsTopMention(R.Choice[1]) then
            WriteResponseName(R)
      end
   end { ReportCategory };


begin { PollReports }
   InitOrder();
   ScoreHits();
   ReportPopularity();
   ReportCategory(AdultMen);
   ReportCategory(AdultWomen);
   ReportCategory(YoungMen);
   ReportCategory(YoungWomen);
end { PollReports } .