From 3f5cc2a20c7fa9ccad0b000461953a9a19fc67c5 Mon Sep 17 00:00:00 2001 From: Jeff Parsons Date: Thu, 21 Sep 2017 17:27:55 -0700 Subject: [PATCH] New blog post: Puzzled Programmers --- _posts/2017-09-21-puzzled-programmers.md | 332 +++++++++++++++++++++++ 1 file changed, 332 insertions(+) create mode 100644 _posts/2017-09-21-puzzled-programmers.md diff --git a/_posts/2017-09-21-puzzled-programmers.md b/_posts/2017-09-21-puzzled-programmers.md new file mode 100644 index 000000000..c4b51e804 --- /dev/null +++ b/_posts/2017-09-21-puzzled-programmers.md @@ -0,0 +1,332 @@ +--- +layout: post +title: Puzzled Programmers +date: 2017-09-21 10:00:00 +permalink: /blog/2017/09/21/ +machines: + - id: ibm5160-msdos320 + type: pcx86 + resume: 1 + config: /devices/pcx86/machine/5160/ega/640kb/machine.xml + drives: '[{name:"10Mb Hard Disk",type:3,path:"/disks/pcx86/fixed/10mb/MSDOS320-C400.json"}]' + autoMount: + A: + name: None + B: + name: None +--- + +One of books I used to have a copy of (and perhaps still do, in the bowels of my storage unit) was "Puzzled Programmers" +by Michael Wiesenberg. It was published by Microsoft Press in 1987, and I recently rediscovered an online copy in the +[Internet Archive's](https://archive.org) [Open Library](https://openlibrary.org): + +> [Puzzled Programmers: 15 mind-boggling story puzzles to test your programming prowess, solutions in BASIC, Pascal, and C](https://openlibrary.org/books/OL2379315M/Puzzled_programmers) + +Someone else was currently "borrowing" it, so I added myself to the wait-list, and a few days ago, the Internet Archive +notified me that it was available for reading. I started skimming it, and noticed that for each of the puzzles, it +included complete solutions in all the aforementioned languages: BASIC, Pascal, and C. I also noticed this portion of +the Introduction: + +> **Timing the Programs on Your Computer** + +> Run times are specified for each puzzle, but you should look at these times only as guidelines or, in the vernacular, +as ballpark figures. The figures given are for a Hewlett-Packard Vectra (IBM PC AT compatible) running at a clock speed +of 8 MHz. Obviously, the times will differ with other interpreters and on other computers. The following table gives you +some idea of how long a program ought to take on your particular system. This table shows timings on various systems +of the programs presented in the solution to Puzzle 5. + + Computer BASIC Turbo Pascal Microsoft C + ------------------------------------------------------------------------------- + Tandy 1000 (4.77 MHz) 2 minutes 11 seconds 1.3 seconds 2.4 seconds + IBM PC XT clone (4.77 MHz) 1 minute 58.5 seconds 1.3 seconds 2.3 seconds + IBM PC A T (6 MHz) 50 seconds 0.5 seconds 0.9 seconds + HP Vectra (8 MHz) 35 seconds 0.5 seconds 0.4 seconds + Macintosh Plus 8 seconds < 1 second < 1 second + Apple II C 2 minutes 20 seconds 23.6 seconds* n/a + ------------------------------------------------------------------------------- + *Apple Pascal + +This almost seemed like an invitation to try my hand at Puzzle #5, and to see how a PCjs "machine" fared. + +### A Pleasant Fourthsum + +I skipped ahead to page 27, "Puzzle 5: A Pleasant Fourthsum", and started reading. I have to admit that the story +completely failed to interest me. There were lots of irrelevant details about a delicious breakfast that this imaginary +bunch of successful twenty-somethings were enjoying, when suddenly one of them says: + + "Before we get into the future of languages at I-Q, I've got a great computer puzzle for everyone." + +which he then states in one sentence: + + "Find a four-digit number that is the sum of the fourth powers of its digits." + +and then the conversation returns to other random topics, like whether anyone should be worried that FORTRAN might +become a weapons guidance language, and whether they need to make lunch reservations -- when they've barely finished +breakfast! + +Skipping ahead to the "Solutions" section, it turned out that the challenge was to actually find *all* four-digit +numbers, not just *a* number. Also, only numbers 1000 through 9999 are considered (no numbers with leading zeros). + +Just for fun, I created [my own solution](/tests/node/puzzled/puzzle5.js) in JavaScript first: + +```javascript +let p = new Array(10); + +/** + * test(n) + * + * Returns true IFF the number matches the criteria. + * + * @param {number} n + * @returns {boolean} + */ +function test(n) { + let total = 0; + let value = n; + while (n) { + total += p[n % 10]; + n = (n / 10)|0; + } + return total == value; +} + +function run() { + let n = 1000; + for (let d = 0; d < 10; d++) { + p[d] = Math.pow(d, 4); + } + while (true) { + if (test(n)) console.log(n); + if (++n > 9999) break; + } +} + +run(); +``` + +then I looked at the solutions in "Puzzled Programmers". Here's the BASIC solution: + +```basic +5 REM BASIC Solution to Puzzle #5 in "Puzzled Programmers" (c) 1987 +10 DIM PWR(9) +20 FOR I = 0 TO 9 +30 PWR(I) = I^4 +40 NEXT I +50 H000 = 1000 +60 FOR H = 1 TO 9 +70 I00 = 0 +80 FOR I = 0 TO 9 +90 J0 = 0 +100 FOR J = 0 TO 9 +110 PARTSUM = H000 + I00 + J0 +120 PART4THS = PWR(H) + PWR(I) + PWR(J) +130 FOR K = 0 TO 9 +140 SUM = PARTSUM + K +150 IF PART4THS + PWR(K) <> SUM THEN 190 +160 PRINT SUM; "= "; +170 PRINT USING "#^4 + "; H, I, J; +180 PRINT USING "#^4"; K +190 NEXT K +200 J0 = J0 + 10 +210 NEXT J +220 I00 = I00 + 100 +230 NEXT I +240 H000 = H000 + 1000 +250 NEXT H +``` + +and the C solution (which I modified to loop 100 times, to make timing with a stopwatch easier): + +```c +/* C solution to puzzle #5 in "Puzzled Programmers" (c) 1987 */ +main() +{ + int pwr[10], /* the fourth power of each digit */ + h, /* the digit in the thousands position */ + h000, /* h * 1000, that is, the value of the number + in the thousands position */ + i, /* the digit in the hundreds position */ + i00, /* i * 100, that is, the value of the number + in the hundreds position */ + j, /* the digit in the tens position */ + j0, /* i * 10, that is, the value of the number + in the tens position */ + part4ths, /* h^4 + i^4 + j^4 */ + partsum, /* h000 + i00 + j0 */ + k, /* the digit in the ones position */ + sum, l; + + for (i = 0; i < 10; i++) pwr[i] = i * i * i * i; + for (l = 100; l-- > 0;) { + for (h000 = 1000, h = 1; h <= 9; h++, h000 += 1000) { + /* thousands digit */ + for (i00 = i = 0; i <= 9; i++, i00 += 100) { + /* hundreds digit */ + for ( j0 = j = 0; j <= 9; j++, j0 += 10) { + /* tens digit */ + partsum = h000 + i00 + j0; + part4ths = pwr[h] + pwr[i] + pwr[j]; + for ( k = 0; k <= 9; k++) { /* ones digit */ + sum = partsum + k; /* this produces the four-digit + number */ + if (part4ths + pwr[k] == sum) { + if (!l) printf("%d = %d^4 + %d^4 + %d^4 + %d^4\n", + sum, h, i, j, k); + } + } + } + } + } + } +} +``` + +Here's my own C solution, which is slightly smaller and faster than the book's solution: + +```c +/* @jeffpar's optimized C solution to puzzle #5 */ +main() +{ + int p[10]; + int d, l, n, s; + for (d = 0; d < 10; d++) p[d] = d * d * d * d; + for (l = 100; l-- > 0;) { /* loop 100 times for timing purposes */ + for (n = 100; n < 1000; n++) { + /* I originally used a "do...while (t /= 10)" loop here + to create the power summation (where t was a copy of n), + but since we know n is a 3-digit number, we can inline + all three power references and minimize the number of + divisions; we also bias the sum by -(n*10) so that we + don't have to add (n*10) to d for the final comparison. */ + s = p[n/100] + p[(n/10)%10] + p[n%10] - (n*10); + for (d = 0; d < 10; d++) { + if (s + p[d] == d) { + if (!l) printf("%d = %d^4 + %d^4 + %d^4 + %d^4\n", + n * 10 + d, n/100, (n/10)%10, n%10, d); + } + } + } + } +} +``` + +And finally, here's the book's Pascal solution: + +```pascal +program sum4ths(input, output); +var + pwr: { the fourth power of each number, 0 to 9 } + array[0..9] of integer; + h, { the digit in the thousands position } + h000, { h * 1000, that is, the value of the number + in the thousands position } + i, { the digit in the hundreds position } + i00, { i * 100, that is, the value of the number in + the hundreds position } + j, { the digits in the tens position } + j0, { j * 10, that is, the value of the number in + the tens position } + part4ths, { h^4 + i^4 + j^4 } + partsum, { h000 + i00 + j0 } + k, { the digit in the ones position } + sum: integer; + +begin + for i := 0 to 9 do pwr[i] := i * i * i * i; + h000 := 1000; + for h := 1 to 9 do begin { thousands digit } + i00 := 0; + for i := 0 to 9 do begin { hundreds digit } + j0 := 0; + for j := 0 to 9 do begin { tens digits } + partsum := h000 + i00 + j0; + part4ths := pwr[h] + pwr[i] + pwr[j]; + for k := 0 to 9 do begin { ones digit } + sum := partsum + k; { this produces the four-digit number } + if part4ths + pwr[k] = sum then + writeln(sum, ' = ', h, '^4 + ', i, '^4 + ', j, '^4 + ', k, '^4') + end; + j0 := j0 + 10; + end; + i00 := i00 + 100; + end; + h000 := h000 + 1000; + end +end. +``` + +You can play with all these solutions in the 4.77Mhz IBM PC XT machine below. Use `CD \PUZZLED\PUZZLE5` to switch +to the directory for Puzzle #5. + +To run the BASIC version, use `GWBASIC`, since the machine boots MS-DOS 3.20: + + C:\PUZZLED\PUZZLE5>GWBASIC PUZZLE5.BAS + +To compile and run the C version, you can use the pre-installed copy of Microsoft C 4.00: + + C:\PUZZLED\PUZZLE5>cl puzzle5.c + Microsoft (R) C Compiler Version 4.00 + Copyright (C) Microsoft Corp 1984, 1985, 1986. All rights reserved. + + puzzle5.c + Microsoft (R) Overlay Linker Version 3.51 + Copyright (C) Microsoft Corp 1983, 1984, 1985, 1986. All rights reserved. + + Object Modules [.OBJ]: PUZZLE5.OBJ + Run File [PUZZLE5.EXE]: PUZZLE5.EXE/NOI + List File [NUL.MAP]: NUL + Libraries [.LIB]: ; + + C:\PUZZLED\PUZZLE5>puzzle5 + 1634 = 1^4 + 6^4 + 3^4 + 4^4 + 8208 = 8^4 + 2^4 + 0^4 + 8^4 + 9474 = 9^4 + 4^4 + 7^4 + 4^4 + +To compile and run the Pascal version, load diskette "IBM Pascal 1.00 (Combined)" in drive A, and then type the +commands shown below: + + C:\PUZZLED\PUZZLE5>a:pas1 + IBM Personal Computer Pascal Compiler + Version 1.00 (C)Copyright IBM Corp 1981 + Source filename [.PAS]: puzzle5p + Object filename [PUZZLE5P.OBJ]: + Source listing [NUL.LST]: + Object listing [NUL.COD]: puzzle5p + Pass One No Errors Detected. + + C:\PUZZLED\PUZZLE5>a:pas2 + + Code Area Size = #0296 (662) + Cons Area Size = #004C (76) + Data Area Size = #002A (42) + + Pass Two No Errors Detected. + + C:\PUZZLED\PUZZLE5>a:link + IBM Personal Computer Linker + Version 1.00 (C) Copyright IBM Corp 1981 + Object Modules: puzzle5p + Run File: puzzle5p + List File [PUZZLE5P.MAP] : + Libraries [ ] : + Publics [No]: + Line Numbers [No]: + Stack size [Object file stack]: + Load Low [Yes]: + DSAllocation [No]: + + C:\PUZZLED\PUZZLE5>puzzle5p + 1634 = 1^4 + 6^4 + 3^4 + 4^4 + 8208 = 8^4 + 2^4 + 0^4 + 8^4 + 9474 = 9^4 + 4^4 + 7^4 + 4^4 + +It appears that PCjs is somewhat faster than a real machine. For example, the author's BASIC solution ran +for almost two minutes on a PC XT clone, but it finishes in about one and a half minutes on the 4.77Mhz PC XT +configuration below. + +Clearly, I still have some work to do if I want PCjs to faithfully simulate how *slow* these systems used to be. + +{% include machine.html id="ibm5160-msdos320" %} + +*[@jeffpar](http://twitter.com/jeffpar)* +*Sep 21, 2017*