CSC161 2011S Imperative Problem Solving

Notes on Exam 1: Linux and C Basics

These are my rough notes on examination 1, prepared before and while grading the examination.

General Issues

I realize that I did not give the explanation of the academic honesty statements. The primary reason I ask for the statements is to remind you that I place high import on academic honesty. The second statement is there to help address the moral dilemma of I saw my classmates cheat. What do I do now? You will be unable to sign the second statement, but you need not give me the details.

I had more There's more to life examinations than expected (and many more than I would have liked). I do my best to make myself available, so please come see me when you're having difficulty, rather than working to the time limites. I expect everyone who invoked this clause to see me soon, and then visit me again when the next exam is assigned.

In general, I'm happy to see you (or to answer email questions) when you're having trouble on the exam. At my discretion, I often provide the assistance in

I primarily work from the printed copies of your examinations. Hence, it's important that you present readable printed copies. In some cases, they were almost completely unreadable because of missing text or line wraps.

While the computer is one audience of your code, human beings are another. I expect you to choose intelligible variable names and to comment on the purposes of those variables. It is also useful to comment sections of code.

Code that is not formatted consistently is very difficult to read. Indentation shows block structure (what code is part of what loop). I very much care that you follow appropriate formatting standards to make it easier not only for me, but for others, to read your code.

In many cases, I was able to identify errors with your code by doing some straightforward testing. I expect you to test your code, and one test is usually insufficient.

Problem 01: Selecting Students

Topics: Linux commands, Pipes

The Problem

Suppose Professor L. stores information for his classes in the following form:

FirstName:LastName:Assignment:Grade:Notes

Write a Linux command (a pipe between various programs) that prints out the records for the five highest grades on HW1, sorted alphabetically by name (e.g., John Doe comes before Jane Smith but after Jen Doe).

You can assume that no two students have the same grade on the assignment.

A Solution

First, we need to extract all the lines for HW1. We use grep HW1 file to get those lines.

Next, we need them sorted from highest to lowest. The sort command sorts.

Our Linux instruction so far is

$ grep HW1 file | sort -t: -k4 -n -r

We want only the five highest grades. Those are the first five lines of the output. The head command works well for grabbing the first five lines.

$ grep HW1 file | sort -t: -k4 -n -r | head -5

If we just wanted to sort by last name, we'd add the command to sort by last name.

$ grep HW1 file | sort -t: -k4 -n -r | head -5 | sort -t: -k2,2

Many of you stopped your answer there. But that shouldn't be enough, since it does not specify sorting by first name. The traditional ways to sort by two fields are either (a) add another key to the sort or (b) sort by the secondary key first.

So, either

$ grep HW1 file | sort -t: -k4 -n -r | head -5 | sort -t: -k2,2 -k1,1

or

$ grep HW1 file | sort -t: -k4 -n -r | head -5 | sort -t: -k1,1 | \
  sort -t: -k2,2 -s

Notes

Times reported: 15, 20, 20, 30, 30, 35, 45, 60, 60, 90, 90, 120, 120, 150

The most prominent issue that I saw in this problem was a failure to test. Many of you saw that your instructions worked correctly for the sample input, and didn't go any further. (Because computers are sentient and malicious, the seemingly incomplete instructions seemed to work on all the inputs I gave them, so I ended up changing a lot of grades.)

Problem 02: Counting Numbers

Topics: Input processing, Numbers, Histograms, Loops

The Problem

Write a C program, numbers, that extracts the numbers from input and prints them out. For example, if the input was

The 411 on 3.1415 is
that 11x11 is 121.

then the output should be

411
3.1415
11
11
121

Note: You should use getchar to read values, and not scanf.

A Solution



Notes

Times reported: 60, 60, 60, 70, 90, 120, 120, 120, 180, 180, 210, 225, 270, 450

Problem 03: Simple Coding

Topics: Strings, Characters, Output, Arithmetic Operations

The Problem

In our initial explorations of output, we built a simple coding program by adding 1 to each character. Let's develop a more complex version of that program. Instead of using a single number, we'll use a code word. To encode an input, you add characters from the code word. E.g.,

   This is the text to be encoded
 + WordWordWordWordWordWordWordWo
 --------------------------------

So, the first letter of output will be the result of adding 'T' and 'W'. The second will be the result of adding 'h' and 'o'. The third will be the result of adding 'i' and 'r'. And so on and so forth.

Of course, this strategy is a bit harder in C, since we need to deal with the limitations of ASCII representation. In particular, only characters with values between 32 (space) and 126 (tilde) are printable. So, one has to play a bit with the computation using modulo and other operations.

a. Write a C program, encode, that encodes its input using a hard-coded code word. (You can use the code word of your choice.)

b. Write a C program, decode, that decodes its input using the same hard-coded code word.

A Solution


/**
 * encode codeword - Encode standard input using the given codeword.
 */

// +---------+--------------------------------------------------------
// | Headers |
// +---------+

#include <stdio.h>	// For getchar and printf
#include <string.h>	// For strlen


// +-----------+------------------------------------------------------
// | Constants |
// +-----------+

/**
 * The minimum and maximum printable characters.
 */
#define MIN_CHAR 32
#define MAX_CHAR 127


/**
 * The default codeword (in case one does not get specified on the
 * command line.
 */
#define DEFAULT_CODEWORD "CSC161-01"


// +------+-----------------------------------------------------------
// | Main |
// +------+

int
main (int argc, char *argv[])
{
  char *codeword;	// The codeword we're using
  int ch;		// The input character
  int k;		// The character in the codeword
  int e;                // The encoded character
  int i=0;		// The index in the codeword
  int len;		// The length of the codeword
  int printable;	// The number of printable characters

  // Set up information on the codeword
  if (argc < 2)
    codeword = DEFAULT_CODEWORD;
  else
    codeword = argv[1];
  len = strlen (codeword);

  // Determine the number of printable characters
  printable = 1 + MAX_CHAR - MIN_CHAR;

  // Process the input
  while ((ch = getchar ()) != EOF)
    {
      // Echo non-printable characters verbatim
      if ((ch < MIN_CHAR) || (ch > MAX_CHAR))
        printf ("%c", ch);
      else
        {
          // Get the next character in the code word.
          k = codeword[i];
          i = (i + 1) % len;
     // printf ("ch=%d(%c), k=%d(%c), ", ch, ch, k, k);	// TESTING
          // Convert each to a reasonable range
          ch -= MIN_CHAR;
          k -= MIN_CHAR;
          // Add them together
          e = (ch + k) % printable;
          //  Return to a reasonable range
          e += MIN_CHAR;
          // And print it out
          // printf ("e=%d(%c)\n", e, e);				// TESTING
          printf ("%c", e);
        }
    } // while

  // And we're done
  return 0;
} // main



/**
 * decode codeword - Decode standard input using the given codeword.
 */

// +---------+--------------------------------------------------------
// | Headers |
// +---------+

#include <stdio.h>	// For getchar and printf
#include <string.h>	// For strlen


// +-----------+------------------------------------------------------
// | Constants |
// +-----------+

/**
 * The minimum and maximum printable characters.
 */
#define MIN_CHAR 32
#define MAX_CHAR 127


/**
 * The default codeword (in case one does not get specified on the
 * command line.
 */
#define DEFAULT_CODEWORD "CSC161-01"


// +------+-----------------------------------------------------------
// | Main |
// +------+

int
main (int argc, char *argv[])
{
  char *codeword;	// The codeword we're using
  int e;                // The encoded character
  int k;		// The character in the codeword
  int ch;		// The output character
  int i=0;		// The index in the codeword
  int len;		// The length of the codeword
  int printable;	// The number of printable characters

  // Set up information on the codeword
  if (argc < 2)
    codeword = DEFAULT_CODEWORD;
  else
    codeword = argv[1];
  len = strlen (codeword);

  // Determine the number of printable characters
  printable = 1 + MAX_CHAR - MIN_CHAR;

  // Process the input
  while ((e = getchar ()) != EOF)
    {
      // Echo non-printable characters verbatim
      if ((e < MIN_CHAR) || (e > MAX_CHAR))
        printf ("%c", e);
      else
        {
          // Get the next character in the code word.
          k = codeword[i];
          i = (i + 1) % len;
     // printf ("e=%d(%c), k=%d(%c), ", e, e, k, k);	// TESTING
          // Convert each to a reasonable range
          e -= MIN_CHAR;
          k -= MIN_CHAR;
          // Undo the addition from encoding
          ch = e - k;
          if (ch < 0)
            ch += printable;
          // Convert back to a reasonable range
          ch += MIN_CHAR;
          // And print it out
          // printf ("ch=%d(%c)\n", ch, ch);			// TESTING
          printf ("%c", ch);
        }
    } // while

  // And we're done
  return 0;
} // main


Notes

Times reported: 0, 90, 105, 120, 150, 180, 180, 240, 240, 240, 300, 300, 480, 480

This problem ended up being the hardest one on the exam. It appears a lot of small errors easily creep in to solutions, and are hard to debug. It's also clear that working out the math of the problem was hard for many of you.

One big off by 1 problem happened with the modulus. Many of you used a modulus of 94. However, that modulus ensures that ' ' (32) and '~' (126) map to the same value, making decoding impossible.

There were a number of constants in the program: The smallest valid character, the largest valid character, the number of valid characters, the length of your string, etc. Rather than writing them throughout the code, it's much clearer if you declare mnemonic constants.

Some of you decided to shove the input into an array. That's a bad idea, since it limits the amount of text you can process. It is much better to just do a character at a time. (More clever encryption algorithms might do a block of characters at a time, but still allow arbitrarily long input.)

Many of you needed to explore a larger set of input characters. One could generate those input characters with a program, or one could simply type the full set of printable characters, as in


`1234567890-=
qwertyuiop[]\
asdfghjkl;'
zxcvbnm,./
~!@#$%^&*()_+
QWERTYUIOP{a|}
ASDFGHJKL:"
ZXCVBNM<>?
[ ]
>

Problem 04: Drawing Diamonds

Topics: Output, Loops, Error Checking

The Problem

Write a concise C program, diamond, that reads in an integer and prints out a diamond made out of asterisks, such as the following.

   *
  ***
 *****
*******
 *****
  ***
   *

The input integer represents the size of the edges. For example, the diamond given above has sides of size 4.

Your program should generate an appropriate error message if the user does not enter a valid side length.

A Solution

We start by analyzing the problem to figure out how many spaces and stars are printed in the rows on the top half of the diamond.

We'll do a diamond with side length 5.

ROW  SPACES  STARS |
 0      4      1   |    *    
 1      3      3   |   ***
 2      2      5   |  *****
 3      1      7   | *******
 4      0      9   |*********

Is there a pattern? We see the number of spaces starting at side-1, decreasing by 1 each time, and ending at 0. We see the number of stars (asterisks) starting at 1 and increasing by 2 each time. We can use that strategy. We can also work out a formula (something I'm more inclined to do).

For the bottom half, we just count down.

Putting it all together, we get


/**
 * diamond - Make and print a simple diamond from asterisks.
 */

// +---------+--------------------------------------------------------
// | Headers |
// +---------+

#include <stdio.h>
#include <stdlib.h>


// +------+-----------------------------------------------------------
// | Main |
// +------+

int
main (int argc, char *argv[])
{
  int side;     // The length of the side
  int row;      // A count of the rows we're printing
  int i;        // A counter variable

  // Verify that there are enough arguments
  if (argc != 2)
    {
      printf ("Usage: diamond SIDE\n");
      return 1;
    }

  // Determine the side length
  side = atoi (argv[1]);

  // Validate the side legth
  if (side <= 0)
    {
      printf ("Invalid side length: %s\n", argv[1]);
      return 2;
    }

  // Print the top half
  for (row = 0; row < side; row++)
    {
      // Print the spaces in the row
      for (i = 0; i < side-1-row; i++)
        putchar (' ');
      // Print the asterisks
      for (i = 0; i < 2*row+1; i++)
        putchar ('*');
      // That's it for the line
      printf ("\n");
    } // for lines in top half

  // Print the bottom half
  for (row = side-2; row >= 0; row--)
    {
      // Print the spaces in the row
      for (i = 0; i < side-1-row; i++)
        putchar (' ');
      // Print the asterisks
      for (i = 0; i < 2*row+1; i++)
        putchar ('*');
      // That's it for the line
      printf ("\n");
    } // for lines in bottom half

  // And we're done
  return 0;
} // main


But that's not an ideal solution. In particular, the code is very repetitious. We should first factor out the bodies of the for loops. Then we should factor out the print N copies of a character code. There may even be more to do.

Notes

Times reported: 30, 30, 60, 60, 60, 90, 120, 120, 120, 150, 150, 180, 180, 180

I saw some very creative solutions that involved drawing an appropriate-sized grid and figuring out for each space on the grid whether it was a space or an asterisk.

 

History

Wednesday, 9 February 2011 [Samuel A. Rebelsky]

  • Started working on my own answers when exam was released.

Saturday, 19 February 2011 [Samuel A. Rebelsky]

  • Finished all of my solutions.
  • Started putting explanations in this file.

Sunday, 20 February 2011 [Samuel A. Rebelsky]

  • More explanations.
  • Added times per problem.

Monday, 21 February 2011 [Samuel A. Rebelsky]

 

Disclaimer: I usually create these pages on the fly, which means that I rarely proofread them and they may contain bad grammar and incorrect details. It also means that I tend to update them regularly (see the history for more details). Feel free to contact me with any suggestions for changes.

This document was generated by Siteweaver on Mon Feb 21 10:52:33 2011.
The source to the document was last modified on Mon Feb 21 10:52:30 2011.
This document may be found at http://www.cs.grinnell.edu/~rebelsky/Courses/CSC161/2011S/Exams/notes.01.html.

Samuel A. Rebelsky, rebelsky@grinnell.edu