diff --git a/CSCI251-A1- Spring 2019.pdf b/CSCI251-A1- Spring 2019.pdf new file mode 100644 index 0000000..07105a4 Binary files /dev/null and b/CSCI251-A1- Spring 2019.pdf differ diff --git a/Readme.txt b/Readme.txt new file mode 100644 index 0000000..6cd8a1a --- /dev/null +++ b/Readme.txt @@ -0,0 +1,24 @@ +// License: AGPLv3 or later. https://www.gnu.org/licenses/licenses.html + +Compile on UOW Banshee server using either of the following command in terminal/shell: +$ make +OR +$ CC -std=c++11 -o ABC driver.cpp abc.cpp general_functions.cpp + +As per test on my machine using folowwing code, there is no memory leak: +$ valgrind --tool=memcheck ./ABC Students.txt Subjects.txt Teachers.txt Results.txt + +However, there is leak when running bcheck on banshee. This could be because of bug in compiler CC. + +Documentation: + +Commenting explaning functions use and rational are in header files. Comments explaning implementation of functions are in cpp files. + +Exit codes error list: +(Read standard error output to find the exact reason of errors.) +1 - Error related to arguments recived +2 - Error while reading file +3 - Error while processing subjects file lines string into subject structs +4 - Error while processing teachers file lines string into teacher structs +5 - Subject code in student's subject list or teacher's subject list is out of range (more than number of subjects) +6 - Error while writting results to output-file diff --git a/abc.cpp b/abc.cpp new file mode 100644 index 0000000..9ae287a --- /dev/null +++ b/abc.cpp @@ -0,0 +1,541 @@ +// License: AGPLv3 or later. https://www.gnu.org/licenses/licenses.html + +#include +#include +#include +# include "general_functions.h" +# include "abc.h" + +using namespace std; + +const int MAX_ATTEMPTS_ALLOWED = 3; + +input_files_name perform_checks_on_args( + int argc, char *argv[]) +{ + if (argc != 5) + { + cerr << "Expected 5 arguments, received " << argc + << " argument(s). Please provide exactly 5 arguments and " + "in correct order i.e.:\n" + "1. Program name/path (received automatically)\n" + "2. Students File such as Students.txt\n" + "3. Subjects File such as Subjects.txt\n" + "4. Teachers File such as Teachers.txt\n" + "5. Output-file such as Results.txt\n" + "\n" + "Run program similar to this:\n" + "$ ./ABC Students.txt Subjects.txt Teachers.txt " + "Results.txt\n"; + exit(1); + } + else + { + input_files_name args; + args.students = argv[1]; + args.subjects = argv[2]; + args.teachers = argv[3]; + args.results = argv[4]; + return args; + } +} + +input_files_textlines read_input_files( + input_files_name file_names) +{ + input_files_textlines files_textlines; + files_textlines.students = get_textfile_lines( + file_names.students); + files_textlines.subjects = get_textfile_lines( + file_names.subjects); + files_textlines.teachers = get_textfile_lines( + file_names.teachers); + + print_three_newlines(); + + return files_textlines; +} + +vector generate_students_struct( + vector students_lines) +{ + cout << "Processing students file text:" << endl; + + vector students; + int line_number = 0; + for (auto student_line:students_lines) + { + line_number++; + + // Ignoring empty line. Giving no warning/error for it either. + if (student_line.size() == 0) + continue; + + try + { + student a_student; + string ability_string; + string consistency_string; + string subject_sring; + + istringstream line(student_line); + getline(line, a_student.name, ','); + getline(line, a_student.code, ','); + getline(line, ability_string, ','); + a_student.ability = stoi(ability_string); + getline(line, consistency_string, ','); + a_student.consistency = stoi(consistency_string); + getline(line, a_student.program, ':'); + while(getline(line, subject_sring, ',')) + { + int subject_code = stoi(subject_sring); + if (subject_code > 0) + a_student.subjects.push_back(subject_code); + else + throw "Anything that you can"; + } + + if (a_student.name.size() == 0 + or a_student.code.size() != 6 + or is_string_some_positive_number( + a_student.code) == false + or a_student.ability < 0 + or a_student.ability > 100 + or a_student.consistency < 0 + or a_student.consistency > 15 + or a_student.program.size() == 0 + or a_student.subjects.size() == 0) + { + throw "All the things that you can"; + } + + students.push_back(a_student); + + cout << "Student Name: " << a_student.name << '\n' + << "Code: " << a_student.code << '\n' + << "Ability: " << a_student.ability << '\n' + << "Consistency: " << a_student.consistency << '\n' + << "Program Name: " << a_student.program << '\n' + << "Subjects code: "; + for (auto subject_code: a_student.subjects) + { + cout << subject_code << " "; + } + cout << '\n' << endl; + + } + catch(...) + { + cerr << "Non-Fatal Error Occured while processing student" + << " file lines into student structs. However as per " + << "specifiction, skipping proccesing anymore " + << "students.\n" + << "Line number: " << line_number << " is not in " + << "proper format.\n" + << "Actual Line:\n" + << student_line << "\n" + << "Expected line format:\n" + << "Name,Student code,Ability,Consistency,Program " + << "name:Subject list\n" + << "Where,\n" + << "1. Name is an non-empty string.\n" + << "2. Student code is 6 *digit* string.\n" + << "3. Ability is an integer in range 0 to 100.\n" + << "4. Consistency is an integer in range 0 to 15.\n" + << "5. Program name is an non-empty string.\n" + << "6. Subject list is the list of comma-seprated " + << "positive integers representing subject codes.\n"; + break; + } + } + + print_three_newlines(); + + return students; +} + +vector generate_subjects_struct( + vector subjects_lines) +{ + cout << "Processing subjects file text:" << endl; + + vector subjects; + int line_number = 0; + for (auto subject_line: subjects_lines) + { + line_number++; + + // Ignoring empty line. Giving no warning/error for it either. + if (subject_line.size() == 0) + continue; + + try + { + subject a_subject; + string difficulty_string; + string variability_string; + + istringstream line(subject_line); + getline(line, a_subject.name, ','); + getline(line, difficulty_string, ','); + a_subject.difficulty = stoi(difficulty_string); + getline(line, variability_string); + a_subject.variability = stoi(variability_string); + + if (a_subject.name.size() == 0 + or a_subject.difficulty < -15 + or a_subject.difficulty > 15 + or a_subject.variability < -3 + or a_subject.variability > 3) + { + throw "Buzzwords"; + } + + subjects.push_back(a_subject); + + cout << "Subject Name: " << a_subject.name << '\n' + << "Difficulty: " << a_subject.difficulty << '\n' + << "Variability: " << a_subject.variability << '\n' + << endl; + + } + catch (...) + { + cerr << "Fatal error occured while processing subjects " + << "file lines into subject structs. Line: " + << line_number << " is not in proper format.\n" + << "Actual line:\n" + << subject_line << "\n" + << "Expected line format: \n" + << "Name,Difficulty,Variability\n" + << "Where,\n" + << "1. Name is a non-empty string.\n" + << "2. Diffuculty is interger in range -15 to 15.\n" + << "3. Variability is integer in range -3 to 3.\n"; + exit(3); + } + } + + print_three_newlines(); + + return subjects; +} + +vector generate_teachers_struct( + vector teachers_lines) +{ + cout << "Processing teachers file text:" << endl; + + vector teachers; + int line_number = 0; + for (auto teacher_line: teachers_lines) + { + line_number++; + + // Ignoring empty line. Giving no warning/error for it either. + if (teacher_line.size() == 0) + continue; + + try + { + teacher a_teacher; + istringstream line(teacher_line); + string toughness_string; + string variability_string; + string subject_string; + + getline(line, a_teacher.name, ','); + getline(line, toughness_string, ','); + a_teacher.toughness = stoi(toughness_string); + getline(line, variability_string, ':'); + a_teacher.variability = stoi(variability_string); + while(getline(line, subject_string, ',')) + { + int subject_code = stoi(subject_string); + if (subject_code > 0) + a_teacher.subjects.push_back(subject_code); + else + throw "Cheesy praises at teacher"; + } + + if (a_teacher.name.size() == 0 + or a_teacher.toughness < -15 + or a_teacher.toughness > 15 + or a_teacher.variability < -3 + or a_teacher.variability > 3 + or a_teacher.subjects.size() == 0) + { + throw "some swag"; + } + + cout << "Teacher Name: " << a_teacher.name << '\n' + << "Toughness: " << a_teacher.toughness << '\n' + << "Variability: " << a_teacher.variability << '\n' + << endl; + + teachers.push_back(a_teacher); + } + catch (...) + { + cerr << "Fatal error occured while processing teachers " + << "file lines into teacher structs. Line: " + << line_number << " is not in proper format.\n" + << "Actual line:\n" + << teacher_line << "\n" + << "Expected line format: \n" + << "Name,Toughness,Variability:Subjects List\n" + << "Where,\n" + << "1. Name is a non-empty string.\n" + << "2. Toughness is interger in range -15 to 15.\n" + << "3. Variability is integer in range -3 to 3.\n" + << "4. Subects list is comma-seprated list of positive" + << "integers representing subjects codes.\n"; + exit(4); + } + } + + print_three_newlines(); + + return teachers; +} + +void add_teachers_vector_index_number_to_subjects_struct( + vector &teachers, + vector &subjects) +{ + for (int i=0; i < teachers.size(); i++) + { + for (auto subject_code: teachers[i].subjects) + { + if (subject_code <= subjects.size()) + { + /* -1 and +1 since indexes starts from 0. + */ + subjects[subject_code-1].teachers.push_back(i+1); + } + else + { + cerr << "Fatal error. Subject code " << subject_code + << " of teacher: " << i+1 << " i.e. " + << teachers[i].name << " is more than total " + << "number of subjects i.e. " << subjects.size() + << '\n'; + exit(5); + } + } + } +} + +void process_students_and_generate_results( + vector &students, + vector &subjects, + vector &teachers) +{ + cout << "Processing students results:" << endl; + + int student_number = 0; + for (auto &a_student: students) + { + student_number++; + int attempts = 0; + + cout << "Processing student " << student_number << " i.e. " + << a_student.name << ":" << endl; + + for (int i=0; i < a_student.subjects.size(); i++) + { + attempts++; + result a_result; + + a_result.subject_code = a_student.subjects[i]; + if (a_result.subject_code > subjects.size()) + { + cerr << "Fatal error. Subject code " + << a_result.subject_code << " of student: " + << student_number << " i.e. " << a_student.name + << " is more than total number of subjects i.e. " + << subjects.size() << '\n'; + exit(5); + } + // -1 since index starts at 0 + auto &a_subject = subjects[a_result.subject_code-1]; + a_result.subject_name = a_subject.name; + + a_result.teacher_code = pick_teacher_randomly( + a_subject.teachers); + // -1 since index starts at 0 + auto &a_teacher = teachers[a_result.teacher_code-1]; + a_result.teacher_name = a_teacher.name; + + int attempts_based_marks_modifier = (attempts-1)*5; + a_result.mean = ( + a_student.ability + - a_subject.difficulty + - a_teacher.toughness + + attempts_based_marks_modifier); + + a_result.standard_deviation = ( + a_student.consistency + - a_subject.variability + - a_teacher.variability); + a_result.standard_deviation = absolute_number( + a_result.standard_deviation); + + int minimum_possible_marks = ( + a_result.mean - a_result.standard_deviation); + minimum_possible_marks = bigger_number( + minimum_possible_marks, 0); + minimum_possible_marks = smaller_number( + minimum_possible_marks, 100); + + int maximum_possible_marks = ( + a_result.mean + a_result.standard_deviation); + maximum_possible_marks = smaller_number( + maximum_possible_marks, 100); + maximum_possible_marks = bigger_number( + maximum_possible_marks, 0); + + int marks = random_int( + minimum_possible_marks, + maximum_possible_marks); + a_result.marks = marks; + a_result.grade = return_grade_for_float((float)marks); + a_student.results.push_back(a_result); + + cout << a_result.subject_name << " (" + << a_result.subject_code << ") by " + << a_result.teacher_name << " (" + << a_result.teacher_code << ") Attempt: " + << attempts << " Mean: " + << a_result.mean << " Standard Deviation: " + << a_result.standard_deviation << " Maximum: " + << maximum_possible_marks << " Minimum: " + << minimum_possible_marks << " Marks: " + << a_result.marks << " Grade: " << a_result.grade + << endl; + + // Supplementary provided. + if (marks >= 45 and marks < 50) + { + int supplementary_marks_modifier = 5; + a_result.mean += supplementary_marks_modifier; + marks = random_int( + minimum_possible_marks, + maximum_possible_marks) + + supplementary_marks_modifier; + a_result.marks = marks; + + if (marks >= 50) + { + a_result.grade = "SP"; + } + else + a_result.grade = "F"; + + a_student.results.push_back(a_result); + + cout << "Supplementary Marks: " << a_result.marks + << " Grade: " << a_result.grade << endl; + } + + // Fail. + if (a_result.grade == "F") + { + /* Max attempts allowed reached. Excluded from college. + * No more subjects are taught. + */ + if (attempts >= MAX_ATTEMPTS_ALLOWED) + { + a_student.overall_marks += marks; + cout << endl; //cosmetic hotfix + a_student.overall_grade_or_status = "F"; + a_student.subjects_studied += 1; + break; + } + // Repeat same subject + else + { + i--; + } + } + // Study next subject with attempt reset to 0. + else + { + a_student.overall_marks += marks; + attempts = 0; + a_student.subjects_studied += 1; + } + + // line between every subject attempt + cout << endl; + } + + a_student.percentage = ((float) + ((a_student.overall_marks*100)/\ + a_student.subjects_studied)/100.0); + + if (a_student.overall_grade_or_status == "?") + { + a_student.overall_grade_or_status =\ + return_grade_for_float(a_student.percentage); + } + + cout << "Overall Percentage: " << a_student.percentage + << " Overall Grade: " << a_student.overall_grade_or_status + << '\n' << endl; + } +} + +void write_results_to_output_file( + vector &students, std::string results_file_name) +{ + ofstream results_file(results_file_name); + + if (!results_file.good()) + { + cerr << "Fatal Error: Could not write results to output-file:" + << results_file_name << "\n" + << "Possibly do not have write acces to directory\n"; + exit(6); + } + + for (auto a_student: students) + { + results_file << a_student.code << "-" << a_student.name << ","\ + << a_student.program << "-" << a_student.percentage << "-"\ + << a_student.overall_grade_or_status << ":"; + for (auto a_result:a_student.results) + { + results_file << a_result.subject_code << "-"\ + << a_result.subject_name << ","\ + << a_result.teacher_code << "-"\ + << a_result.teacher_name << ","\ + << a_result.mean << "+-"\ + << a_result.standard_deviation << "=>"\ + << a_result.marks << "-" << a_result.grade << ";"; + } + results_file << "\n"; + } + results_file.close(); +} + +int pick_teacher_randomly(vector teachers_codes) +{ + int selected_teacher_index = random_int( + 0, (teachers_codes.size()-1)); + return teachers_codes[selected_teacher_index]; +} + +string return_grade_for_float(float percentage) +{ + if (percentage >= 85.0) + return "HD"; + else if (percentage >= 75.0) + return "D"; + else if (percentage >= 65.0) + return "C"; + else if (percentage >= 50.0) + return "P"; + else + return "F"; +} diff --git a/abc.h b/abc.h new file mode 100644 index 0000000..1dbff25 --- /dev/null +++ b/abc.h @@ -0,0 +1,120 @@ +// License: AGPLv3 or later. https://www.gnu.org/licenses/licenses.html + +/* Functions which are written specifically for A Big College (ABC) + */ + +#ifndef _ABC_H_ +#define _ABC_H_ + +#include + +struct input_files_name +{ + std::string students; + std::string subjects; + std::string teachers; + std::string results; // output-file +}; + +struct input_files_textlines +{ + std::vector students; + std::vector subjects; + std::vector teachers; +}; + +struct teacher +{ +/* teacher's index no.+1 is treated as teacher code which is used + * internally in subject's struct as teachers who can teach those + * subjects + */ +// int code; + std::string name; + int toughness; + int variability; + std::vector subjects; +}; + +struct result +{ + int subject_code; + /* Even though redundant, it makes function statements a lot more + * readable. + */ + std::string subject_name; + int teacher_code; + // Same as previous comment + std::string teacher_name; + int mean; + int standard_deviation; + int marks; + std::string grade; +}; + +struct student +{ + std::string name; + std::string code; + int ability; + int consistency; + std::string program; + // Only the last attempt of every subject is added to overall_marks + int overall_marks = 0; + /* Following calculation is used to obtain percentage upto two decimal + * points precision only: + * ((float)((overall_marks*100)/subjects_studied)/100.0) + */ + float percentage; + /* If fail, F will be added at exclusion time. Else, will be + * calculated based on percentage. + */ + std::string overall_grade_or_status = "?"; + std::vector subjects; + // used for calculating overall percentage + int subjects_studied = 0; + std::vector results; +}; + +struct subject +{ +// subject's index no.+1 is treated as subject code +// int code; + std::string name; + int difficulty; + int variability; + std::vector teachers; +}; + +input_files_name perform_checks_on_args( + int argc, char *argv[]); + +input_files_textlines read_input_files( + input_files_name file_names); + +std::vector generate_students_struct( + std::vector students); + +std::vector generate_subjects_struct( + std::vector subjects); + +std::vector generate_teachers_struct( + std::vector teachers); + +void add_teachers_vector_index_number_to_subjects_struct( + std::vector &teachers, + std::vector &subjects); + +void process_students_and_generate_results( + std::vector &students, + std::vector &subjects, + std::vector &teachers); + +void write_results_to_output_file( + std::vector &students, std::string results_file_name); + +int pick_teacher_randomly(std::vector teachers_codes); + +std::string return_grade_for_float(float percentage); + +#endif diff --git a/driver.cpp b/driver.cpp new file mode 100644 index 0000000..816c33d --- /dev/null +++ b/driver.cpp @@ -0,0 +1,33 @@ +// License: AGPLv3 or later. https://www.gnu.org/licenses/licenses.html + +#include +#include +#include "abc.h" + +using namespace std; + +int main(int argc, char *argv[]) +{ + input_files_name args = perform_checks_on_args(argc, argv); + + input_files_textlines input_files_by_line = read_input_files( + args); + + vector students = generate_students_struct( + input_files_by_line.students); + + vector subjects = generate_subjects_struct( + input_files_by_line.subjects); + + vector teachers = generate_teachers_struct( + input_files_by_line.teachers); + + add_teachers_vector_index_number_to_subjects_struct(teachers, subjects); + + process_students_and_generate_results( + students, subjects, teachers); + + write_results_to_output_file(students, args.results); + + return 0; +} diff --git a/general_functions.cpp b/general_functions.cpp new file mode 100644 index 0000000..c6f49dd --- /dev/null +++ b/general_functions.cpp @@ -0,0 +1,65 @@ +// License: AGPLv3 or later. https://www.gnu.org/licenses/licenses.html + +#include +#include +#include +# include "general_functions.h" + +using namespace std; + +vector get_textfile_lines(std::string filename) +{ + ifstream file(filename); + vector file_lines; + + if (! file.good()) + { + cerr << "Failed to open/read file: " << filename\ + << ". Non-exhaustive list of possible reasons:\n"\ + << "1. File does not exist.\n"\ + << "2. Unable to read file due to:\n"\ + << "2.1 Don't have read access to file\n"\ + << "2.2 File is opened elsewhere and some program have a "\ + << "lock on file.\n"; + exit(2); + } + + cout << "Reading file: " << filename << endl; + + while (file.good()) + { + string line; + getline(file, line); + file_lines.push_back(line); + } + + file.close(); + + return file_lines; +} + +int random_int(int lower_bound, int upper_bound) +{ + static std::random_device seed; + static std::mt19937 random_number_generator(seed()); + std::uniform_int_distribution range( + lower_bound, upper_bound); + return range(random_number_generator); +} + +bool is_string_some_positive_number(std::string a_string) +{ + for (int i=0; i < a_string.size(); i++) + { + string character(1, a_string[i]); + try + { + stoi(character); + } + catch(...) + { + return false; + } + } + return true; +} diff --git a/general_functions.h b/general_functions.h new file mode 100644 index 0000000..7584f65 --- /dev/null +++ b/general_functions.h @@ -0,0 +1,64 @@ +// License: AGPLv3 or later. https://www.gnu.org/licenses/licenses.html + +/* General functions which can be used in any program + */ + +#ifndef _GENERAL_FUNCTIONS_H_ +#define _GENERAL_FUNCTIONS_H_ + +#include + +std::vector get_textfile_lines(std::string filename); + +int random_int(int lower_bound, int upper_bound); + +bool is_string_some_positive_number(std::string a_string); + +/* Its a good practice to prototype all functions including which are + * defined in header file itself such as inline functions before defining + * any function. This way, even if we use one function in another + * function, we do not have to worry about function undefined error + * possibilities. + */ + +inline int absolute_number(int number); + +inline int smaller_number(int a, int b); + +inline int bigger_number(int a, int b); + +/* this function is used between different parts of program's standard + * output for better sepration of dissimilar outputs + */ +inline void print_three_newlines(); + +inline int absolute_number(int number) +{ + if (number < 0) + return -(number); + else + return number; +} + +inline int smaller_number(int a, int b) +{ + if (a > b) + return b; + else + return a; +} + +inline int bigger_number(int a, int b) +{ + if (a < b) + return b; + else + return a; +} + +inline void print_three_newlines() +{ + std::cout << "\n" << std::endl; +} + +#endif diff --git a/makefile b/makefile new file mode 100644 index 0000000..9c3cb73 --- /dev/null +++ b/makefile @@ -0,0 +1,11 @@ +ABC: driver.o abc.o general_functions.o + CC -std=c++11 -o ABC driver.o abc.o general_functions.o && rm *.o + +driver.o: driver.cpp abc.h + CC -std=c++11 -c driver.cpp + +abc.o: abc.cpp general_functions.h + CC -std=c++11 -c abc.cpp + +general_functions.o: general_functions.cpp + CC -std=c++11 -c general_functions.cpp