COMP1511 17s1 Introduction to Programming

Objectives

In this Lab, you will practice:

Preparation

Before the lab you should re-read the relevant lecture slides and their accompanying examples. You should also have read the lab assessment guidelines.

Getting Started

One member of your programming pair should login and run the following commands inside a Linux terminal

Create a new directory for this lab called lab11 by typing:

mkdir lab11
Change to this directory by typing:
cd lab11

Exercise: Converting COMP1511 Lab Grade Into a Mark

Each week you recieve one of 5 grades for a COMP1511 lab exercise. The following table lists the grades.

The table also has an (indicative) mapping for each grade to an individual mark.

Grade Criteria Mark
A+ Complete, correct, and clear solution - if there are challenge exercises a good attempt on these must be made 1.25
A Competent solution to standard lab exercises 1.1
B Most of standard lab exercises completed, to all completed but with one or more major bugs 0.8
C Partial solution only, most of lab not completed 0.5
. Not attempted .

Write a C program grades2labmark.c which takes a single command line arguments, a string of lab grades. It should print the corresponding total lab mark to one decimal place. Remember your lab mark can not exceed 10. For example:

dcc grades2labmark.c -o grades2labmark
./grades2labmark CAA.A+A+ABCBA
9.5
./grades2labmark ABABAA+BBABA+
10.0
./grades2labmark CA+..B..BBCA+
5.9
Don't assume any particular number of lab grades in the string (there are actually 10 assessable labs this session).

Hint: place your code to calculate the lab mark in a function with this prototype:

double grades2labmark(char grades[]);
And create a simple main function which calls grades2labmark.

You'll want to reuse grades2labmark in the next exercise.

As usual autotest is available to help you test your program.

 ~cs1511/bin/autotest lab11 grades2labmark.c
Sample solution for grades2labmark.c
#include <stdio.h>

// written by andrewt@unsw.ed.au as a COMP1511 lab solution

double grades2labmark(char grades[]);

int
main(int argc, char *argv[]) {
    if (argc == 2) {
        printf("%.1lf\n", grades2labmark(argv[1]));
        return 0;
    } else {
        fprintf(stderr, "Usage: %s <lab-grade>\n", argv[0]);
        return 1;
    }
}

// give a string of COMP1511 grades:  "BCAA..BAA+A+."
// calculate a lab mark on this basis
// A+ -> 1.25
// A -> 1.1
// B -> 0.8
// C -> 0.5
// . -> 0.0
// total mark is capped at

double grades2labmark(char grades[]) {
    double lab_mark = 0;

    int i = 0;
    while (grades[i] != '\0') {
        if (grades[i] == 'A') {
            if (grades[i + 1] == '+') {
                lab_mark += 1.25; // A+
                i++;
            } else {
                lab_mark += 1.1; // A
            }
        } else if (grades[i] == 'B') {
            lab_mark += 0.8;
        } else if (grades[i] == 'C') {
            lab_mark += 0.5;
        } else if (grades[i] == '.') {
        } else {
            fprintf(stderr, "Warning: unexpected character in grade string: '%c'\n", grades[i]);
        }
        i++;
    }

    if (lab_mark > 10) {
        lab_mark = 10;
    }
    return lab_mark;
}

Exercise: Printing Lab Marks

The file labmarks.txt contains the lab marks for COMP1511 students.

The file print_labmarks.c contains a C code to read this information into a linked list of structs. It is given one command-line argument, the name of the marks file.

Modify print_labmarks.c so that it prints the lab marks for all students in a specified class. The name of the lab class will be specified as a second command-line argument.

Start by copying the two files:

cp /import/adams/A/cs1511/public_html/17s1/tlb/11/print_labmarks.c .
cp /import/adams/A/cs1511/public_html/17s1/tlb/11/labmarks.txt .
head labmarks.txt
5082094 Jonathan Liang wed13-sitar .ABC..B.ACB
5025147 Yishuang Feuer thu09-sitar CA+CBACC.A+CB
5048284 Chao Caamwang mon17-kora A+CB..AA.BCA
5003054 Michael Ganguly fri12-oboe BBCA+A+A+.ABBB
5006842 Darshan Gupta tue18-flute A+A+.BAA+BABCB
5066309 Alfred Tao tue12-sitar B.A+A+AAACA+A+.
5078835 Phillip Cai tue15-oboe CBA+A.A+BA+.CC
5062191 Emily Koh mon17-kora A+BABA+BA.A.A
5006921 Jurun Ping tue12-oboe .C.CABABCAA
5070065 Aayushma Cohen wed13-oboe CBCCB.BAA+A+A

Here is how print_labmarks.c should behave after your changes:

dcc print_labmarks.c -o print_labmarks
./print_labmarks labmarks.txt tue09-kora
5063177 Morgan Huynh                   tue09-kora   A+.B.AA..AC.            5.8
5097719 Kah Li                         tue09-kora   BCABA.CBA+A+A+          9.4
5024788 Raymond Lim                    tue09-kora   A+CA+AA+A+A+BA+A+C     10.0
5039227 Haosu Kwong                    tue09-kora   B.CCBACCA.A+            7.1
5087364 Timotius Yu                    tue09-kora   A+.A+BC.ABC.C           6.7
5072336 Liyee Henoch                   tue09-kora   AAA.AB.A+A+AB           9.6
5057497 Zhengyu Wang                   tue09-kora   BAA+BCACCCA.            8.2
5071465 Xiaohan Cao                    tue09-kora   BCC.ABCBAA+A            8.4
5016202 Yijia Davis                    tue09-kora   CA+..CCCA+CBB           6.6
5002493 Song Li                        tue09-kora   AA+ABB.BABAA            9.9
5044859 Guanting Davidson              tue09-kora   A+AA+A+.CAA+BA+A       10.0
5068224 David Huang                    tue09-kora   C.AABA...A+.            5.8
5039893 Bhawna Sun                     tue09-kora   CBBAC.BA+.AC            7.3
5079069 James Tran                     tue09-kora   A+A+AA+..B.B..          6.4
5011466 Kevin Wang                     tue09-kora   A+.BABA+..C.C           6.2
5060552 Abdullah Devathi               tue09-kora   CB..A+A+A.BA.           6.8
5054357 Emmanuel Ghatak                tue09-kora   .CACBAA+AAA+A           9.8
5047380 Nancy Zhong                    tue09-kora   B..ACBC.A+C.            5.5
5002062 Christopher Wu                 tue09-kora   A+CA+A+CBCB..C          7.3
5033947 Carey Charlton                 tue09-kora   CA+CACCBA+...           6.4
5094267 Brendan Jillani                tue09-kora   AB.BCAAACA.             8.1
5026044 William Tejero                 tue09-kora   .BBA+.A+CA+B.A          7.8
5009976 Renjie Xue                     tue09-kora   AA+AAB..BCA+B           8.7
5040965 Vincent She                    tue09-kora   BAA+A.BA+.BB.           7.9
5052488 Hong Agapitos                  tue09-kora   BB...AB.AAA+            6.9
5023273 Caleb Phuenghansaporn          tue09-kora   A+BAAAAC.BA+A          10.0
5028543 Daksh Gao                      tue09-kora   AA+CB.CCA+CA+C          8.2
5042895 Da Dong                        tue09-kora   BA+A+.A+BBAABA+        10.0
5077748 Guhan Kim                      tue09-kora   BBBABA+CB.AA            9.1
5099218 Rahil Eager                    tue09-kora   AA.AA+AA+.A+A+B        10.0
5069640 Buyang Chin                    tue09-kora   B...A+A+A+BCBA+         7.9
You must also add code to print_labmarks.c which calls free to release all the memory allocated for student structs with malloc.

Do not read any input. Do not call fscanf, fgets etc. Do not create any arrays or other linked lists. Just use the linked list returned by the read_students_file function in the code you are given in print_labmarks.c

Hint: the code you are given in print_labmarks.c just takes one command-line argument (the name of the marks file). Change it to take 2.

Hint: this printf format might be useful: "%d %-30s %-12s %-22s %4.1lf\n".

Hint: copy the grade2mark function into print_labmarks.c (autotest is expecting only this file)

As usual autotest is available to help you test your program.

 ~cs1511/bin/autotest lab11 print_labmarks.c
Sample solution for print_labmarks.c
// modified by andrewt@unsw.ed.au as a COMP1511 lab solution
// from work by Mark Sonnenschein & Alex Linker

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

#define MAX_STUDENT_NAME_LENGTH 128
#define MAX_GRADE_STRING_LENGTH 22
#define MAX_LAB_NAME_LENGTH 32
#define MAX_LINE_LENGTH 4096

struct student {
    int              zid;
    char             name[MAX_STUDENT_NAME_LENGTH + 1];
    char             lab_name[MAX_LAB_NAME_LENGTH + 1];
    char             lab_grades[MAX_GRADE_STRING_LENGTH + 1];
    struct student   *next;
};

void print_students_from_lab(struct student *first, char *lab_name);
struct student *read_students_file(char filename[]);
struct student *read_student(FILE *stream);
double grades2labmark(char grades[]);
void free_students(struct student *head);

int main(int argc, char *argv[]) {

    if (argc != 3) {
        fprintf(stderr, "Usage: %s <marks-file> <lab-name>\n", argv[0]);
        return 1;
    }

    struct student *student_list = read_students_file(argv[1]);
    print_students_from_lab(student_list, argv[2]);

    free_students(student_list);
    return EXIT_SUCCESS;
}

void print_students_from_lab(struct student *first, char *lab_name) {
    for (struct student *s = first; s != NULL; s = s->next) {
        if (strcmp(s->lab_name,lab_name) == 0) {
            printf("%d %-30s %-12s %-22s %4.1lf\n", s->zid, s->name, s->lab_name, s->lab_grades, grades2labmark(s->lab_grades) );
        }
    }
}

// read_students_file reads a file where line contains information for 1 student
// it creates a linked of student structs containing the information
// it returns a pointer to the head of the list

struct student *read_students_file(char filename[]) {
    FILE *fp = fopen(filename, "r");
    if (fp == NULL) {
        fprintf(stderr,"warning file %s could not  be opened for reading\n", filename);
        return NULL;
    }

    struct student *first_student = NULL;
    struct student *last_student = NULL;
    struct student *s;
    while ((s = read_student(fp)) != NULL) {
        if (last_student == NULL) {
            first_student = s;
            last_student = s;
        } else {
            last_student->next = s;
            last_student = s;
        }
    }

    fclose(fp);
    return first_student;
}


// read_student mallocs a student struct
// and reads a line in this format:
//
// 5099703 Tsun Bordignon                 thu13-sitar  A+A+CABAB..A.           7.8
//
// stores the values in the struct field
// and returns a pointer to the struct

struct student *read_student(FILE *stream) {
    char line[MAX_LINE_LENGTH];

    struct student *s = malloc(sizeof (struct student));
    assert(s);

    if (fgets(line, MAX_LINE_LENGTH, stream) == NULL) {
        free(s);
        return NULL;
    }

    // truncate string at '\r' or '\n'
    line[strcspn(line, "\r\n")] = '\0';

    char *space_ptr = strrchr(line, ' ');
    assert(space_ptr);
    strncpy(s->lab_grades, space_ptr + 1, MAX_GRADE_STRING_LENGTH);
    s->lab_grades[MAX_GRADE_STRING_LENGTH] = '\0';
    *space_ptr = '\0';

    space_ptr = strrchr(line, ' ');
    assert(space_ptr);
    strncpy(s->lab_name, space_ptr + 1, MAX_LAB_NAME_LENGTH);
    s->lab_name[MAX_LAB_NAME_LENGTH] = '\0';
    *space_ptr = '\0';

    space_ptr = strchr(line, ' ');
    assert(space_ptr);
    strncpy(s->name, space_ptr + 1, MAX_STUDENT_NAME_LENGTH);
    s->name[MAX_STUDENT_NAME_LENGTH] = '\0';
    *space_ptr = '\0';

    s->zid = atoi(line);
    s->next = NULL;
    return s;
}

// give a string of COMP1511 grades:  "BCAA..BAA+A+."
// calculate a lab mark on this basis
// A+ -> 1.2
// A -> 1
// B -> 0.8
// C -> 0.5
// . -> 0.0
// total mark is capped at

double grades2labmark(char grades[]) {
    double lab_mark = 0;

    int i = 0;
    while (grades[i] != '\0') {
        if (grades[i] == 'A') {
            if (grades[i + 1] == '+') {
                lab_mark += 1.25; // A+
                i++;
            } else {
                lab_mark += 1.1; // A
            }
        } else if (grades[i] == 'B') {
            lab_mark += 0.8;
        } else if (grades[i] == 'C') {
            lab_mark += 0.5;
        } else if (grades[i] == '.') {
        } else {
            fprintf(stderr, "Warning: unexpected character in grade string: '%c'\n", grades[i]);
        }
        i++;
    }

    if (lab_mark > 10) {
        lab_mark = 10;
    }
    return lab_mark;
}

// free memory allocated with malloc for all student
// structs in list

void free_students(struct student *head) {
    struct student *s = head;
    while (s != NULL)  {
        struct student *t = s->next;  // save a copy of s->next
        free(s);
        s = t;
    }
}

Challenge Exercise: Sorting Students

Create a C program sort_students.c which, given a lab marks file as a command-line argument, prints the students in sorted order.

You must use the code you are given in print_labmarks.c to read the file.

You must rearrange the linked list returned by read_students_file into sorted order and then print it.

You can not use arrays in your code.

You can not use malloc in your code (except for the call to malloc in read_student).

You must reorder the linked list returned by read_students_file and then print the list.

Students should be ordered firstly on last name. If they have the same last name they should be ordered on first name. If this is also the same they should be ordered on zid.

You can assume students only have two names.

Make you program match the following example exactly:

dcc sort_students.c -o sort_students
./sort_students labmarks.txt
5080641 Joshua Abayasingam             wed13-kora   BBCA+CBBBBBB            8.7
5075388 Edbert Adhikara                mon13-sitar  ACA..A+A+A+BAC          8.8
5096572 Saeed Adjei-Yeboah             tue09-sitar  AA+C.CCA+.CA+A          7.9
5093918 Haocheng Agapiou               tue15-sitar  CA+.CA+A+BBAA.          8.5
5051260 Yiwei Agapiou                  thu17-kora   A+C.ABA+A+A.AA+         9.6
5052488 Hong Agapitos                  tue09-kora   BB...AB.AAA+            6.9
5071624 Thomas Agapitos                tue15-sitar  .A+CCC.BCA+CB           6.6
5026321 Amanda Aggarwal                thu09-sitar  AA+.AAABA+.A+A         10.0
5092895 Joshua Agius                   fri12-sitar  BA+A+BAA+CCC.A+         9.2
5018595 Abdullah Agrawal               fri09-oboe   A+A+A+A+A+CAA+AAA+     10.0
...
5063193 Edbert Zhou                    mon13-oboe   CCCAACACBAA             8.8
5046373 Franklin Zhou                  fri09-kora   A+ABBA.CA+CBC           8.6
5069868 Jacob Zhou                     tue18-flute  A+B.BA...CBA            6.3
5022608 Maxwell Zhou                   tue12-kora   .ACA.A+.CCCA            6.6
5044229 William Zhou                   tue15-oboe   A+BCA+CA.A+A+CB         9.2
5037555 Yinuo Zhou                     thu09-sitar  A+CA+B..AA+..C          6.7
5047970 Alyne Zhu                      tue09-oboe   A+BABBA+.A+A+BA        10.0
5009758 Sang-Hoon Zhu                  fri12-oboe   .CACCA..A+AB            6.9
5083232 Jessica Zong                   mon17-sitar  .B.AABC.ACC             6.4
5082060 Nicholas Zong                  thu13-kora   AA+ABB.CCA+AA+          9.7
As usual autotest is available to help you test your program.
 ~cs1511/bin/autotest lab11 sort_students.c
Sample solution for sort_students.c
// modified by andrewt@unsw.ed.au as a COMP1511 lab solution
// from work by Mark Sonnenschein & Alex Linker

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

#define MAX_STUDENT_NAME_LENGTH 128
#define MAX_GRADE_STRING_LENGTH 22
#define MAX_LAB_NAME_LENGTH 32
#define MAX_LINE_LENGTH 4096

struct student {
    int              zid;
    char             name[MAX_STUDENT_NAME_LENGTH + 1];
    char             lab_name[MAX_LAB_NAME_LENGTH + 1];
    char             lab_grades[MAX_GRADE_STRING_LENGTH + 1];
    struct student   *next;
};

struct student * quicksort_students(struct student *head);
void partition(struct student *pivot, struct student *head, struct student **smaller, struct student **bigger);
int compare_student(struct student *s1, struct student *s2);
struct student *last(struct student *student);
void print_students(struct student *first);
struct student *read_students_file(char filename[]);
struct student *read_student(FILE *stream);
double grades2labmark(char grades[]);
void free_students(struct student *head);

int main(int argc, char *argv[]) {

    if (argc != 2) {
        fprintf(stderr, "Usage: %s <marks-file>\n", argv[0]);
        return 1;
    }

    struct student *student_list = read_students_file(argv[1]);
    student_list = quicksort_students(student_list);
    print_students(student_list);
    free_students(student_list);

    return EXIT_SUCCESS;
}

// return student list rearranged into sorted order
struct student *quicksort_students(struct student *head) {
    struct student *pivot = head;
    struct student *smaller;
    struct student *bigger;

    if (head == NULL) {
        return NULL;
    }

    partition(pivot, head->next, &smaller, &bigger);
    smaller = quicksort_students(smaller);
    bigger = quicksort_students(bigger);

    pivot->next = bigger;
    if (smaller == NULL) {
        return pivot;
    } else {
        last(smaller)->next = pivot;
        return smaller;
    }
}

// partition list of students point to by head,into 2 lists based on pivot
// *smaller is set to the list of student which should be ordered before the pivot student
// *larger is set to the list of student which should be ordered after the pivot student

void partition(struct student *pivot, struct student *head, struct student **smaller, struct student **bigger) {
    if (head == NULL) {
        *smaller = NULL;
        *bigger = NULL;
    } else {
        if (compare_student(pivot, head)) {
            *smaller = head;
            partition(pivot, head->next, &(head->next), bigger);
        } else {
            *bigger = head;
            partition(pivot, head->next, smaller, &(head->next));
        }
    }
}

// return 1 iff student s1 should be sorted before student s2, 0 otherwise
// students are sorted on last name, then first name, then zid

int compare_student(struct student *s1, struct student *s2) {
    char *space1 = strchr(s1->name, ' ');
    char *space2 = strchr(s2->name, ' ');
    assert(space1);
    assert(space2);
    int last_name_order = strcmp(space1 + 1, space2 + 1);
    if (last_name_order > 0) {
        return 1;
    } else if (last_name_order < 0) {
        return 0;
    }
    int first_name_order = strcmp(s1->name, s2->name);
    if (first_name_order > 0) {
        return 1;
    } else if (first_name_order < 0) {
        return 0;
    }
    return s1->zid > s2->zid;
}

// return last in list

struct student *last(struct student *head) {
    if (head == NULL || head->next == NULL) {
        return head;
    }
    return last(head->next);
}

// print all students with their lab_mark

void print_students(struct student *first) {
    for (struct student *s = first; s != NULL; s = s->next) {
        printf("%d %-30s %-12s %-22s %4.1lf\n", s->zid, s->name, s->lab_name, s->lab_grades, grades2labmark(s->lab_grades) );
    }
}

// read_students_file reads a file where line contains information for 1 student
// it creates a linked of student structs containing the information
// it returns a pointer to the head of the list

struct student *read_students_file(char filename[]) {
    FILE *fp = fopen(filename, "r");
    if (fp == NULL) {
        fprintf(stderr,"warning file %s could not  be opened for reading\n", filename);
        return NULL;
    }

    struct student *first_student = NULL;
    struct student *last_student = NULL;
    struct student *s;
    while ((s = read_student(fp)) != NULL) {
        if (last_student == NULL) {
            first_student = s;
            last_student = s;
        } else {
            last_student->next = s;
            last_student = s;
        }
    }

    fclose(fp);
    return first_student;
}


// read_student mallocs a student struct
// and reads a line in this format:
//
// 5099703 Tsun Bordignon                 thu13-sitar  A+A+CABAB..A.           7.8
//
// stores the values in the struct field
// and returns a pointer to the struct

struct student *read_student(FILE *stream) {
    char line[MAX_LINE_LENGTH];

    struct student *s = malloc(sizeof (struct student));
    assert(s);

    if (fgets(line, MAX_LINE_LENGTH, stream) == NULL) {
        free(s);
        return NULL;
    }

    // truncate string at '\r' or '\n'
    line[strcspn(line, "\r\n")] = '\0';

    char *space_ptr = strrchr(line, ' ');
    assert(space_ptr);
    strncpy(s->lab_grades, space_ptr + 1, MAX_GRADE_STRING_LENGTH);
    s->lab_grades[MAX_GRADE_STRING_LENGTH] = '\0';
    *space_ptr = '\0';

    space_ptr = strrchr(line, ' ');
    assert(space_ptr);
    strncpy(s->lab_name, space_ptr + 1, MAX_LAB_NAME_LENGTH);
    s->lab_name[MAX_LAB_NAME_LENGTH] = '\0';
    *space_ptr = '\0';

    space_ptr = strchr(line, ' ');
    assert(space_ptr);
    strncpy(s->name, space_ptr + 1, MAX_STUDENT_NAME_LENGTH);
    s->name[MAX_STUDENT_NAME_LENGTH] = '\0';
    *space_ptr = '\0';

    s->zid = atoi(line);
    s->next = NULL;
    return s;
}

// give a string of COMP1511 grades:  "BCAA..BAA+A+."
// calculate a lab mark on this basis
// A+ -> 1.2
// A -> 1
// B -> 0.8
// C -> 0.5
// . -> 0.0
// total mark is capped at

double grades2labmark(char grades[]) {
    double lab_mark = 0;

    int i = 0;
    while (grades[i] != '\0') {
        if (grades[i] == 'A') {
            if (grades[i + 1] == '+') {
                lab_mark += 1.25; // A+
                i++;
            } else {
                lab_mark += 1.1; // A
            }
        } else if (grades[i] == 'B') {
            lab_mark += 0.8;
        } else if (grades[i] == 'C') {
            lab_mark += 0.5;
        } else if (grades[i] == '.') {
        } else {
            fprintf(stderr, "Warning: unexpected character in grade string: '%c'\n", grades[i]);
        }
        i++;
    }

    if (lab_mark > 10) {
        lab_mark = 10;
    }
    return lab_mark;
}

// free memory allocated with malloc for all student
// structs in list

void free_students(struct student *head) {
    struct student *s = head;
    while (s != NULL)  {
        struct student *t = s->next;  // save a copy of s->next
        free(s);
        s = t;
    }
}

A second sample solution for sort_students.c
// Andrew Bennett <andrew.bennett@unsw.edu.au>
// COMP1511 Week 11, 17s1
// Prints the students from a given lab marks file in sorted order, using
// merge sort as a sorting algorithm.

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

#define MAX_STUDENT_NAME_LENGTH 128
#define MAX_GRADE_STRING_LENGTH 22
#define MAX_LAB_NAME_LENGTH 32
#define MAX_LINE_LENGTH 4096

struct student {
    int              zid;
    char             name[MAX_STUDENT_NAME_LENGTH + 1];
    char             lab_name[MAX_LAB_NAME_LENGTH + 1];
    char             lab_grades[MAX_GRADE_STRING_LENGTH + 1];
    struct student   *next;
};

struct student *read_students_file(char filename[]);
struct student *read_student(FILE *stream);
void print_students(struct student *student_list);
double get_lab_marks(char *grades);

// sorting functions -- see verbose comments below
struct student *sort_students(struct student* student_list);
struct student *mergesort(struct student *student_list);
struct student *split_middle(struct student *student_list);
struct student *merge(struct student *first, struct student *second);

// comparison and helper functions for dealing with student names
char *get_last_name(struct student *s);
int compare(struct student *s1, struct student *s2);

int main(int argc, char *argv[]) {
    if (argc != 2) {
        fprintf(stderr, "Usage: %s <marks-file> labname\n", argv[0]);
        return 1;
    }

    struct student *student_list = read_students_file(argv[1]);
    if (student_list == NULL) {
        fprintf(stderr, "wat\n");
        return 1;
    }
    student_list = sort_students(student_list);
    print_students(student_list);

    return 0;
}

void print_students(struct student *student_list) {
    struct student *curr = student_list;
    while (curr != NULL) {
        printf("%d %-30s %-12s %-22s %4.1lf\n", curr->zid, curr->name, curr->lab_name, curr->lab_grades, get_lab_marks(curr->lab_grades));
        curr = curr->next;
    }
}

// A pretty generic "wrapper" function; earlier I was testing out a
// bubble sort, so having this meant it was very easy to swap between
// the two.
struct student *sort_students(struct student *student_list) {
    return mergesort(student_list);
}

// Merge sort the student list:
// Split the list in half, literally, by chopping the link between the two halves.
//
// Then, call mergesort on those two lists (thus splitting them in half, and their halves in half, ...),
// and merge the two sorted lists together.
struct student *mergesort(struct student *student_list) {
    struct student *first_half = student_list;
    struct student *second_half = split_middle(student_list);

    // If we only have a single element (the first half is the same as the second half),
    // then it's already sorted, so we don't need to do anything.
    if (first_half == second_half) {
        return first_half;
    }

    // otherwise, mergesort the first half, and the second half,
    // and then merge them together.
    first_half = mergesort(first_half);
    second_half = mergesort(second_half);

    return merge(first_half, second_half);
}

// Split the list into two lists, severing the link at the halfway point.
//
// This works by walking along the linked list using two pointers, a
// "fast" pointer and a "slow" pointer. The "fast" pointer walks along
// the list every loop iteration, and the "slow" pointer only moves along
// every second iteration.
//
// Once the "fast" pointer hits the end, the "slow" pointer will be
// halfway along. We can then cut the link between the middle and middle+1st
// nodes, and return the second half of the list as our split list.
//
// This works out fine, because we merge the lists back together later.

struct student *split_middle(struct student *student_list) {
    struct student *single_step = student_list;
    struct student *double_step = student_list;
    // a variable that alternates between 0 and 1, and determines
    // whether the "slow" pointer should progress forward.
    int toggle = 0;
    while (double_step->next != NULL) {
        if (toggle) {
            single_step = single_step->next;
        }
        // if toggle is 0, !0 = 1.
        // if toggle is 1, !1 = 0.
        // so, this causes the variable to "toggle".
        toggle = !toggle;
        double_step = double_step->next;
    }

    // Now that we've finished the loop, single_step is at the
    // halfway point.

    // If single_step is pointing to the same place as double_step,
    // then we only had a single node to begin with, so return
    // single_step (our "slow" pointer)
    if (single_step == double_step) {
        return single_step;
    }

    // Otherwise, our second half of the list starts at the element
    // after our "slow" pointer, so keep a reference to that, and
    // cut the link between them.
    struct student *next = single_step->next;
    single_step->next = NULL;
    return next;
}

// Merges two sorted lists into a single sorted list.
// Looks at the first element of each list, and moves the smaller element to
// a new list; then moves that list along to the next element, and compares again.
// eg: if the lists are
//
//   1 -> 3 -> 4 -> 10 -> X
//   2 -> 5 -> X
//
// It will start out looking at the 1 and the 2:
//   v (first)
//   1 -> 3 -> 4 -> 10 -> X
//   2 -> 5 -> X
//   ^ (second)
//
// since 1 is smaller than 2, 1 will be added to our new sorted list, and
// "first" will move along to the next node":
//        v (first)
//        3 -> 4 -> 10 -> X
//   2 -> 5 -> X
//   ^ (second)
// sorted list:
//   1 -> X
//
// Once one of the lists is NULL (or rather, once one of the lists has been completely
// merged into the new overall list), it appends the remaining list onto the end, as we
// know that there's nothing else left in the now-exhausted list, and we know that the
// remaining list is in sorted order.
struct student *merge(struct student *first, struct student *second) {
    // the head of our new merged list
    struct student *head = NULL;
    // the node we're currently up to (at the end of) our new merged list
    struct student *upto = NULL;

    // while there are still nodes in *both* lists:
    while (first != NULL && second != NULL) {
        struct student *smallest;
        // avoid code duplication: work out which is smallest, then deal with edge cases
        if (compare(first, second) <= 0) {
            smallest = first;
            first = first->next;
        } else {
            smallest = second;
            second = second->next;
        }
        // deal with the edge cases:
        // if head is NULL, then this is the very first node, so set head to point to it.
        if (head == NULL) {
            head = smallest;
            upto = head;
        } else {
            upto->next = smallest;
            upto = upto->next;
        }
    }

    // at this point, either first is null, second is null, or both are null.
    if (first != NULL) {
        // insert everything from first onwards
        upto->next = first;
    } else if (second != NULL) {
        // insert verything from second onwards
        upto->next = second;
    }
    return head;
}

// Returns a pointer to the student's surname.
// Calls strchr to get a pointer to the space between the names,
// then returns a pointer that points one past that (i.e. to the
// start of the surname)
char *get_last_name(struct student *s) {
    return strchr(s->name, ' ') + 1;
}


// Compares two students, and returns whether the first student should be placed
// before (negative value), or after (positive value) the second student.
// If the students are the same, the function will return 0.
//
// Students are compared based on last name, then first name, then zid.
int compare(struct student *s1, struct student *s2) {
    char *s1_last_name = get_last_name(s1);
    char *s2_last_name = get_last_name(s2);

    // 0 means they're the same, -1 means n1 < n2, +1 means n1 > n2
    int surname_compare = strcmp(s1_last_name, s2_last_name);

    // If the surnames are the same:
    if (surname_compare == 0) {
        // Compare by first name:
        int firstname_compare = strcmp(s1->name, s2->name);
        // If *those* are the same, compare based on zid.
        if (firstname_compare == 0) {
            return s1->zid - s2->zid;
        }
        return firstname_compare;
    }
    return surname_compare;
}

// return the smaller of two given values
double min(double a, double b) {
    if (a < b) {
        return a;
    }
    return b;
}

// calculate lab marks, according to the given scheme.
double get_lab_marks (char *grades) {
    int upto = 0;
    double mark = 0.0;
    while (grades[upto] != '\0') {
        switch (grades[upto]) {
            case 'A':
                mark += 1.1;
                break;
            case 'B':
                mark += 0.8;
                break;
            case 'C':
                mark += 0.5;
                break;
            case '+':
                mark += 0.15;
                break;
        }
        upto += 1;
    }
    // return the smallest of their actual mark, or 10.0 (i.e. cap their lab mark at 10.0).
    return min(mark, 10.0);
}

// DO NOT CHANGE THE CODE BELOW HERE - DO NOT CHANGE read_students_file

// read_students_file reads a file where line contains information for 1 student
// it creates a linked of student structs containing the information
// it returns a pointer to the head of the list

struct student *read_students_file(char filename[]) {
    FILE *fp = fopen(filename, "r");
    if (fp == NULL) {
        fprintf(stderr,"warning file %s could not  be opened for reading\n", filename);
        return NULL;
    }

    struct student *first_student = NULL;
    struct student *last_student = NULL;
    struct student *s;
    while ((s = read_student(fp)) != NULL) {
        if (last_student == NULL) {
            first_student = s;
            last_student = s;
        } else {
            last_student->next = s;
            last_student = s;
        }
    }
    return first_student;
}

// DO NOT CHANGE read_student

// read_student mallocs a student struct
// and reads a line in this format:
//
// 5099703 Tsun Bordignon thu13-sitar A+A+CABAB..A.
//
// stores the values in the struct field
// and returns a pointer to the struct

struct student *read_student(FILE *stream) {
    char line[MAX_LINE_LENGTH];

    struct student *s = malloc(sizeof (struct student));
    assert(s);

    if (fgets(line, MAX_LINE_LENGTH, stream) == NULL) {
        free(s);
        return NULL;
    }

    char *newline_ptr = strchr(line, '\n');
    assert(newline_ptr);
    *newline_ptr = '\0';

    char *space_ptr = strrchr(line, ' ');
    assert(space_ptr);
    strncpy(s->lab_grades, space_ptr + 1, MAX_GRADE_STRING_LENGTH);
    s->lab_grades[MAX_GRADE_STRING_LENGTH] = '\0';
    *space_ptr = '\0';

    space_ptr = strrchr(line, ' ');
    assert(space_ptr);
    strncpy(s->lab_name, space_ptr + 1, MAX_LAB_NAME_LENGTH);
    s->lab_name[MAX_LAB_NAME_LENGTH] = '\0';
    *space_ptr = '\0';

    space_ptr = strchr(line, ' ');
    assert(space_ptr);
    strncpy(s->name, space_ptr + 1, MAX_STUDENT_NAME_LENGTH);
    s->name[MAX_STUDENT_NAME_LENGTH] = '\0';
    *space_ptr = '\0';

    s->zid = atoi(line);
    s->next = NULL;
    return s;
}

Submission/Assessment

When you are satisfied with your work, ask your tutor to assess it. You also need to submit your work electronically by typing (run this command in the lab11 directory):
give cs1511 lab11 grades2labmark.c print_labmarks.c sort_students.c
Submit the challenge exercises only if you attempt them.

If you are working at home, you may find it more convenient to upload your work via give's web interface.

Remember the lab assessment guidelines - if you don't finish the exercises you can finish them in your own time, submit them by Monday 11:00am using give and ask your tutor to assess them at the start of the following lab.

Either or both members of a programming pair can submit the work (make sure each program lists both of you as authors in the header comment).