Journal Articles

CVu Journal Vol 12, #2 - Mar 2000 + Student Code Critiques from CVu journal.
Browse in : All > Journals > CVu > 122 (18)
All > Journal Columns > Code Critique (70)
Any of these categories - All of these categories

Note: when you create a new publication type, the articles module will automatically use the templates user-display-[publicationtype].xt and user-summary-[publicationtype].xt. If those templates do not exist when you try to preview or display a new article, you'll get this warning :-) Please place your own templates in themes/yourtheme/modules/articles . The templates will get the extension .xt there.

Title: Student Code Critique Competition

Author: Administrator

Date: 07 March 2000 13:15:36 +00:00 or Tue, 07 March 2000 13:15:36 +00:00

Summary: 

Body: 

Prizes for this competition are provided by Blackwells Bookshops in co-operation with Addison-Wesley. There was only a single entry for last issue's critique which made the job of selecting a winner easy. Why not make my task harder next time by looking at this issue's code at the end of this column?

Last Issue's Code:

void comp_stu () {
  FILE *grades_data;
  int i;
  char name[20];  /*students name */
  int test_1[3];  /*grade test 1*/
  int test_2[3];  /*grade test 2*/
  int test_3[3];  /*grade test 3*/
  int asign[3];     /*grade assignment */
  int final[3];     /*grade from final exam */
  int a;
  int val;
  i = 0;
  grades_data = fopen ("grades.data", "r"); 
/*file with names, grade for test1,2,and 3, and assignment, and final */
  val = fscanf (grades_data, "%s%d%d%d%d%d", &name[i], &test_1[i], &test_2[i], &test_3[i], &asign[i], &final[i]);
  while (val != EOF) {
/*read file until end of file */
    a = strcmp(&name[i],&name[i+1]);
/*compare first and second students' names */
    if (a > 0)
      printf ("%s%5s%d%4s%d%4s%d%4s%d%4s%d\n", &name[i], " ", &test_1[i], " ", &test_2[i], " ", &test_3[i], " ", &asign[i], " ",&final[i]);
    if (a ==0) 
/* code to intended print both students' data */
    if (a < 0) 
/* as above but in reverse order */
    i ++;
    val = fscanf (grades_data, "%s%d%d%d%d%d", &name[i], &test_1[i], &test_2[i], &test_3[i], &asign[i], &final[i]);
  }
  return;
}

The first and last name of each student is contained in one string. name[i] would contain the students first and last name. I have tried running the program using the above function, but its not working. I will get bits of the students' names and jumbled numbers.

Winning Critique

By Brett Fishburne

In an effort to understand the student code presented, I started with the name of the function. comp_stu. My guess is that this refers to "compare students." A quick look at the declarations suggests that 3 students are to be compared (all the grades are arrays that can hold 3 entries). This points out an immediate problem with the name array, which will only hold one name of up to 20 characters. The variable should be correctly declared as:

char name[3][20] /* 3 names, up to 20
             characters each */

The file "grades.data" is opened without confirming that the fopen command was successful. A check to see if the file is opened correctly should be incorporated as:

if ((grades_data = fopen("grades.data", "r"))
               == (FILE *)NULL) {
 fprintf(stderr, 
    "comp_stu: Unable to open grades_data\n");
 return; }

In this way, the programmer checks to be certain that the file has been opened successfully.

The function contains two calls to fscanf, that are both the same. By properly structuring the loop, the two calls can be combined into one. The loop as it exists, tests val, which is the return from fscanf. The return value of fscanf is the number of items scanned in from the input stream (EOF only if the input ends before the first conflict or conversion). Since the loop really is checking for the end of the stream, it would be better to use the feof function. As noted above, however, no more than 3 students can be compared, so this needs to be incorporated into the loop as well. It should be obvious that if more than 3 students are in the input file, it doesn't matter, as no more than three will be evaluated. Thus, the new loop condition should be:

while ((!feof(grades_data)) && (i < 3))

This checks for the end of file and checks the student count to be sure no more than three students are processed. In general I prefer a better variable naming convention than i but it is not terribly unreasonable as this variable is actually a counter.

Looking at the existing while loop, it is clear that the student wishes to compare students and print them out in order. It is necessary, however, to read in all the students, then perform the comparison. Thus, the comparison needs to be removed from the loop.

Since the loop condition above addresses the file, it is clear that the loop should read in the students. This suggests that the next statement should scan in the student information. The scan statement used in the existing program is basically fine for this purpose if it is fair to assume that the data is presented exactly in the form expected by the scan statement. In the last C Vu, Mr Stroustrup went to great lengths to examine the difficulty of reading in strings and explaining how difficult the C code for doing so is. For the sake of simplicity (and the premise of this being a true student project), I will assume that the teacher has guaranteed that the data is in the correct format or it should be considered a catastrophic error.

The first fscanf shown in the program needs a couple of simple modifications to perform more appropriately. The number of characters in the name needs to be constrained so that the array is not overrun. Thus the new call would be:

val = fscanf(grades_data, "%19s%d%d%d%d%d",
   &(name[i][0]), &(test_1[i]), &(test_2[i]),
   &(test_3[i]), &(assign[i]), &(final[i]));

It is important, however, to check the return value from fscanf to make sure that all the variables were read in! This requires the following additional code after the fscanf function:

if (val != 6) {
 fprintf(stderr, 
  "comp_stu: Error reading from grades data\n");
 (void)fclose(grades_data);
 return; }

The loop is almost complete, but it is necessary to increment the counting variable, i, and that will end the loop.

i++;

With the students read in, it is no longer necessary to keep the input file open. I like to close files right away so that there are no unused file descriptors hanging open inappropriately. fclose returns a value (0 on success) and there is rarely anything to be done if a file won't close, but just in case there is a problem, it is worthwhile to check the return value and handle it properly:

if (fclose(grades_data) == EOF) {
 fprintf(stderr, 
"comp_stu: Warning unable to close grades file\n");
 fprintf(stderr, 
  "comp_stu: Error number %d\n", errno); }

Now it is time to compare the students. Possibly, however, fewer than 3 students were read in, so it is important to check the number of students read in and use that to guide the comparison.

A different sorting function is performed depending on the number of students read in. This calls for a switch statement which works based on the number of students:

switch(i)

The first case is simple, if only 1 student was read in, then only 1 student needs to be printed. While the printf command in the student program has nothing inherently wrong with it, printing single spaces n times using the %s command is overkill. The spaces can be inserted directly. In addition the array index must be used consistently and putting sizing information for the width of the fields will keep columns consistent (The -19 left justifies the string in a 19 character field). Finally, the integer values should not be made into pointers:

case 1: { printf("%-19s   %6d  %6d  %6d  %6d  
%6d\n", &(name[0][0]), test_1[0], test_2[0], 
test_3[0], assign[0], final[0]);
     break; }

If two students were read in, then the larger name should appear first. This can be determined by looking at the "a < 0" condition in the original program. If the first string is smaller than the second string (strcmp returns a negative), then the original programmer prints the second string first (or at least starts to print the second string first). The two student case is also easily handled:

case 2: { 
if (strcmp(&(name[0][0]), &(name[1][0])) < 0) {
  printf("%-19s   %6d  %6d  %6d  %6d  %6d\n",
    &(name[1][0]), test_1[1], test_2[1], 
    test_3[1],assign[1], final[1]);
 printf("%-19s   %6d  %6d  %6d  %6d  %6d\n",
    &(name[0][0]), test_1[0], test_2[0],
    test_3[0],assign[0], final[0]);
} else {
/* code in reverse of the above order snipped */
break; }

At this point, it is almost frustrating to continue to try to use the style of code created by the student. On the other hand, it seems that the student will learn more from the review process by seeing the code back in his/her own style. Given that assumption, the case of three students must be handled. Below is a table that shows the possible permutations on three students:

<colgroup> <col> <col> <col> <col></colgroup> <thead> </thead> <tbody> </tbody>
Student name / comparison 1st > 2nd 2nd > 3rd 1st > 3rd
a, b, c False False False
a, c, b False True False
b, a, c True False False
b, c, a False True True
c, a, b True False True
c, b, a True True True

Given the sequences in the table, it will be possible to find the proper order of the students by doing up to three strcmp calls. These permutations hold true even if a and c, for example are the same (True, False, False and the associated rearrangement would work). At this point, it should be obvious that there will be six sets of printf statements. There is no question that this is inefficient code, but I think that using a sort call or something like that would simply obfuscate the errors made by the student programmer rather than elucidate them. Thus, the case statement for three students is (the "printf" calls are omitted for clarity):

case 3: { 
if (strcmp(& (name[0][0]),& (name[2][0])) > 0){
  if (strcmp(&(name[1][0]), &(name[2][0])) > 0) {
    if (strcmp(&(name[0][0]), &(name[1][0])) > 0) {
        /* print 0 then 1 then 2 */
    } 
    else {
        /* print 1 then 0 then 2 */
    }
  } 
  else {
        /* print 0 then 2 then 1 */
  }
} 
else {
  if (strcmp(&(name[1][0]), &(name[2][0])) > 0) {
       /* print 1 then 2 then 0 */
  } 
  else {
    if (strcmp(&(name[0][0]), &(name[1][0])) > 0) {
        /* print 2 then 0 then 1 */
 } 
  else {
        /* print 2 then 1 then 0 */
  }
 }
}
break; 
}

There remains the possibility that no students were read in from the grades file, so that should be handled by the default in the switch statement:

default: { 
  fprintf(stderr,
        "comp_stu: No students found\n");
  break; 
}

With this the case statement is complete and the function is ended with a return statement.

Sometime shortly Brett will be receiving a copy of Standard C++ IOStreams and Locales by Angelika Langer and Klaus Kreft (reviewed in the current issue of Overload).

This Issue's Student Code

I have to process a set of files, one per employee, that contains data about an employees working times. A typical file would be:

Kaiser
18.12.1999 6.0 K1001 Praktikum GIP
17.12.1999 4.5 K0002 Fachbereichssitzung
19.12.1999 2.0 K1001 Vorlesung GIP
19.12.1996 3.0 K0001 Weihnachtsfeier
01.01.2000 2.5 K3001 Klausur GIP erstellen

I am required to read the data into a structure and sort it by a bubblesort on the dates. I should also be able to generate a list for a specific time period (e.g. provide a list from 19.12.1999 till 30.01.2000)

Here is the C code. As I am German, the identifiers are in that language.

This is my structure in the header file:

/*** Datenstrukturen ***/
struct stunden {
 float std;
 char kostenstelle[6];  char datum[11];
 char name[21];  char arbeit[41];
};
/*** Globale Variablen ***/
extern struct stunden std[100];
extern int anz_daten;

Here I will read my data from a file into the structure:

int datei_einlesen(char datei[13]) {
FILE *pf;
  char name[21];
  pf = fopen(datei, "r");
  if(!pf)  return 0;
  fscanf(pf, "%s", name);
  while(!feof(pf)){
     fscanf(pf, "%s %f %s", std[anz_daten].datum,
               &std[anz_daten].std,
               std[anz_daten].kostenstelle);
    fgets(std[anz_daten].arbeit, 40, pf);
    if(strpbrk(std[anz_daten].arbeit, "\n"))
          std[anz_daten].arbeit[strlen(
      std[anz_daten].arbeit)- 1] = 0;
    strcpy(std[anz_daten].name, name);
    anz_daten++;
  }
  fclose(pf);
  return 1;
}

And here I want to list a period of time:

void datum_zerlegen(char datum[11], int *tag
            , int *monat, int *jahr) {
     char t[3], m[3], j[5];
     strncpy(t, datum, 2);
     t[2] = 0;
     strncpy(m , datum + 3, 2);
     m[2] = 0;
     strncpy(j, datum + 6, 4);
     j[4] = 0;
     *tag = atoi(t);
     *monat = atoi(m);
     *jahr = atoi(j);
}
void zeitraum_ausgeben(char von[11], char bis[11]) {
  int x, vtag, vmonat, vjahr;
  int btag, bmonat, bjah, xtag, xmonat, xjahr;
  datum_zerlegen(von, &vtag, &vmonat, &vjahr);
  datum_zerlegen(bis, &btag, &bmonat, &bjahr);
  for(x = 0; x < anz_daten; x++){
    datum_zerlegen(std[x].datum, &xtag, &xmonat, &xjahr);
     if( (xjahr  >= vjahr ) && (xjahr  <= bjahr ) &&
  (xmonat >= vmonat) && (xmonat <= bmonat) &&  (xtag
   >= vtag  ) && (xtag   <= btag  ))   {
      printf("%s %5.2f %s %-10s %s\n",
      std[x].datum, std[x].std,
 std[x].kostenstelle, std[x].name, std[x].arbeit);
    }
  }
  printf("\n");
}

Please critique the above code. Please do not comment on layout. You may judiciously comment on the quality of the problem set.

Notes: 

More fields may be available via dynamicdata ..