Haircut Appointment Scheduler

Objective

The goal of the assignment is to assess your ability to write clear readable code demonstrating object oriented programming concepts.

Assignment

Create a command-line program in an object-oriented language of your choice (C++, Java, Python) that interacts with the user using stdin and stdout. You may use any standard libraries in your solution. The user of the program is a hairdresser who needs to keep track of his or her haircut appointments. The program will support the scheduling of two types of appointments:

  • Haircut: 30 minutes

  • Shampoo & Haircut: 1 hour

Upon running of the program, the program should prompt for user input. It should accept 3 command types: 1. List: listing of all future appointments 2. Schedule: schedules either type of appointment (note that this command may take additional parameters so that the hairdresser can distinguish one appointment from another). 3. Exit: the hairdresser is done with the program; upon exit, all existing state will be destroyed (i.e. the program does not persist the appointments in between program runs)

For simplicity sake, the program may assume that appointments are always scheduled at 15-minute offsets from the hour (e.g. 12:15pm, 4:00pm, 5:45pm, 6:30pm).

Steps

  1. Start by creating an object-oriented design for the scheduler interface (i.e. public methods) and

    the model classes for the schedule and the appointments.

  2. Implement the method that lists existing appointments.

  3. Implement the method that schedules a single appointment.

  4. Wire the program together so that it can be run from the command line.

Submission

Write a short README that lists the source files and a short description of each source file (e.g. Javadocs may be used in place of the description). In the README, also specify how to run the program and a log of one or more test runs of the program. If applicable, include any design considerations that came up during the design of the program. Respond to this email with a zip file of the README and the source files for your solution. Please do not post your solution to GitHub or another public repository.

// Scheduler.h

#pragma once
#include <iostream>
#include <string>
#include <unordered_map>

#ifdef DBUILD

#include <limits>
inline void pause() { cin.ignore(numeric_limits<streamsize>::max(), '\n'); }

template<typename T>
inline void print2d(vector<vector<T>> &stuffs) {
    cout << endl;
    int count = 0;
    for (auto i : stuffs) {
        cout << count++ << ":";
        for (auto j : i)
            cout << setw(2) << j << " ";
        cout << endl;
    }
    cout << endl;
}

template<typename T>
inline void print(vector<T> &stuffs) {
    cout << endl;
    for (auto i : stuffs) cout << i << " "; cout << endl; pause();
    cout << endl;
}
#endif


using namespace std;


struct Time {
    Time(short h, short m, bool morn) {
        hr = h; min = m;
        morning = morn;
    }

    Time(short h, short m) {
        hr = h; min = m;
    }

    Time() { hr = min = 0; }

    Time(const Time &rhs)
    {
        hr = rhs.hr;
        min = rhs.min;
        morning = rhs.morning;
    }

    friend Time operator+(const Time &one, const Time &two);
    void operator +=(const Time& rhs);
    bool operator==(const Time &other) const;
    void operator=(const Time &rhs);

    short hr, min;
    bool morning;
};

Time operator+(const Time &one, const Time &two) {
    // add both minutes
    short min_total = one.min + two.min;
    short hr_carry = min_total / 60;
    short min_remaining = min_total - (hr_carry * 60);
    short total_hours = hr_carry + one.hr + two.hr;

    short number_flips = total_hours / 12;
    bool morn = one.morning;
    // odd indicates opposite daylight hrs
    if (number_flips % 2 != 0 && total_hours >= 12)
        morn = !one.morning;

    return Time((total_hours > 12) ? total_hours % 12 : total_hours, min_remaining, morn);
}

void Time::operator+=(const Time & rhs)
{
    // add both minutes
    short min_total = rhs.min + min;
    short hr_carry = min_total / 60;
    short min_remaining = min_total - (hr_carry * 60);
    short total_hours = hr_carry + rhs.hr + hr;

    short number_flips = total_hours / 12;

    hr = total_hours % 12;
    min = min_remaining;

    // odd indicates opposite daylight hrs
    if (number_flips % 2 != 0 && total_hours > 12)
        morning = !morning;
}

inline bool Time::operator==(const Time & other) const
{
    return (hr == other.hr && min == other.min
        && morning == other.morning);
}

inline void Time::operator=(const Time & rhs)
{
    hr = rhs.hr;
    min = rhs.min;
    morning = rhs.morning;
}

/* hash function */
namespace std {
    template <>
    struct hash<Time>
    {
        std::size_t operator()(const Time& k) const
        {
            return ((hash<short>()(k.hr)
                ^ (hash<short>()(k.min) << 1)) >> 1)
                ^ (hash<bool>()(k.morning) << 1);
        }
    };
}


struct Activity {
    Activity(string activity) {
        this->activity = activity;
    }

    bool active = false;
    string activity;
};


class Scheduler
{
public:
    Scheduler() 
    {
        Time start(12,0,1);             // 12:00am
        Time incrementor;               // 00:00
        Time static_incrementor(0, 15); // 00:15

        // creates an empty schedule
        // there are 96, 15 min intervals from 12:00am to 11:45pm
        for (int i = 0; i < 48; i++) {
            Time newTime(start + incrementor);
            newTime.morning = true;
            //cout << newTime.hr << ":" << newTime.min << newTime.morning << endl;
            schedule.insert(make_pair(Time(newTime.hr, newTime.min, newTime.morning), 
                            Activity("")));
            incrementor += static_incrementor;
        }

        Time start2(12, 0, 0); // 12:00pm
        Time incrementor2;     // 00:00
        for (int i = 0; i < 48; i++) {
            Time newTime(start2 + incrementor2);
            newTime.morning = false;
            //cout << newTime.hr << ":" << newTime.min << newTime.morning << endl;

            schedule.insert(make_pair(Time(newTime.hr, newTime.min, newTime.morning),
                            Activity("")));
            schedule.insert(make_pair(newTime, Activity("")));
            incrementor2 += static_incrementor;
        }
    };

    vector<string> appointmentTimes()
    {
        return activeAppoint;
    }

    bool appointHaircut(Time &time, const string activity) 
    {
        // check confliction
        if (schedule.find(time)->second.active) return true;

        // 30 min - add to schedule
        Time incrementor(0, 0);
        const Time incrementor_static(0, 15);
        for (int i = 0; i < 2; i++) {
            Time hashTime(time + incrementor);
            schedule.find(hashTime)->second.active = true;
            schedule.find(hashTime)->second.activity = activity;
            incrementor += incrementor_static;
        }
        // add to active schedule
        addAppointment(time, activity);
        return false;
    }

    bool appointShampooAndHairCut(Time &time, const string activity)
    {
        // check confliction
        if (schedule.find(time)->second.active) return true;

        // 1 hr
        Time incrementor(0, 0);
        const Time incrementor_static(0, 15);
        for (int i = 0; i < 4; i++) {
            Time hashTime(time + incrementor);
            schedule.find(hashTime)->second.active = 1;
            schedule.find(hashTime)->second.activity = activity;
            incrementor += incrementor_static;
        }
        // add to active schedule
        addAppointment(time, activity);
        return false;
    }

    bool isEmpty() 
    {
        return schedule.empty();
    }

private:
    unordered_map<Time, Activity> schedule;
    vector<string> activeAppoint;

    void addAppointment(Time &time, const string &activity)
    {
        string appointment;
        // formating to 2 digits: e.g. 9:[00]am
        if (time.min <= 9) {
            appointment = "  " + to_string(time.hr) + ":" +
                + "0" + to_string(time.min) + std::string((time.morning) ? ("am") : ("pm")) +
                std::string(" - ") +
                std::string((activity == "shampoo") ? ("shampoo & haircut") : (activity));;
        }
        else {
            appointment = "  " + to_string(time.hr) + ":" +
                to_string(time.min) + std::string((time.morning) ? ("am") : ("pm")) +
                std::string(" - ") + 
                std::string((activity == "shampoo") ? ("shampoo & haircut") : (activity));
        }

        activeAppoint.push_back(appointment);
    }
};

// Source.cpp

#include <iostream>
#include <string>
#include <vector>
#include <cctype>       //tolower()
#include <sstream>        //stringstream
#include "Scheduler.h"


using namespace std;

string toLower(string);
bool isIntegral(string &);
vector<string> split(const string&, char);
bool isValid(const vector<string> &);
Time getTime(const string &);


int main()
{
    string input;
    bool invalid, exit = false;
    Scheduler schedule;
    //input = "schedule shampoo & haircut 7:30pm";

    while (!exit) {
        cout << "\n> ";
        getline(cin, input);
        input = toLower(input);
        vector<string> commands = split(input, ' ');

        if (!isValid(commands)) {
            cout << "  Input command is invalid. Please try again" << endl;
            continue;
        }

        if (commands[0] == "exit") {
            exit = true;
            continue;
        }
        else if (commands[0] == "list") {
            if (schedule.isEmpty()) {
                cout << "No appointments at this time." << endl;
            }
            else {
                vector<string> appointments = schedule.appointmentTimes();
                for (string & s : appointments)
                    cout << s << endl;
                continue;
            }
        }
        // is a scheduling command
        else {
            // ensure time is properly formated
            Time appointment = (getTime(commands[commands.size() - 1]));
            //cout << getTime(commands[commands.size() - 1]).morning << endl;
            bool conflict;
            if (appointment.hr == 0 && appointment.min == 0) {
                cout << "  Appointment time has invalid format. Please try again" << endl;
                continue;
            }
            else if (commands[1] == "haircut") {
                conflict = schedule.appointHaircut(appointment, "haircut");
                if (conflict) cout << "  There is an conflict with appointment time." << endl;
            }
            else if (commands[1] == "shampoo") {
                conflict = schedule.appointShampooAndHairCut(appointment, "shampoo");
                if (conflict) cout << "  There is an conflict with appointment time." << endl;
            }
        }
    }
}



string toLower(string str)
{
    for (char& c : str)
        c = tolower(c);

    return str;
}

bool isIntegral(string &num) 
{
    for (char& c : num) {
        if (!isdigit(c)) return false;
    }
    return true;
}

vector<string> split(const string &s, char delim)
{
    vector<string> elems;
    stringstream ss;
    ss.str(s);
    string item;
    while (getline(ss, item, delim)) {
        elems.push_back(item);
    }
    return elems;
}

bool isValid(const vector<string> &commands)
{
    // no command or too many
    if (commands.empty() || commands.size() > 5) return false;
    // list or exit
    else if (commands.size() == 1) {
        return ((commands[0]) == "list" || 
                (commands[0]) == "exit");
    }
    // schedule
    else {
        // haircut
        if (commands.size() == 3)
            return ((commands[0]) == "schedule" && 
                    (commands[1]) == "haircut");
        else {
            // haircut and shampoo
            return ((commands[0]) == "schedule" &&
                    (commands[1]) == "shampoo" &&
                    (commands[2]) == "&" &&
                    (commands[3]) == "haircut");    
        }
    }
    return false;
}

Time getTime(const string &str)
{
    // XX:XXam or X:XXpm
    if (str.length() < 6 || str.length() > 7) return Time();
    int colon_index = 0;
    for (int i = 0; i < str.size(); i++) {
        if (str[i] == ':') {
            colon_index = i;
            break;
        }
    }

    string daylight = str.substr(str.size() - 2, str.size());
    if (!(daylight == "am" || daylight == "pm")) return Time();
    if (colon_index == 1) {
        string one = str.substr(0, 1);
        if (isIntegral(one) && isIntegral(str.substr(2, 2))) {
            int hr = atoi(str.substr(0, 1).c_str());
            int min = atoi(str.substr(2, 2).c_str());

            // valid minutes and hour and 15 min interval
            bool test = (daylight == "am") ? (true) : (false);
            if ((hr >= 1 && hr <= 12 ) && (min >= 0 && min <= 59) && min % 15 == 0)
                return Time(hr, min, test);
        }
    }
    else if (colon_index == 2) {
        if (isIntegral(str.substr(0, 2)) && isIntegral(str.substr(3, 2))) {
            int hr = atoi(str.substr(0, 2).c_str());
            int min = atoi(str.substr(3, 2).c_str());

            if ((hr >= 1 && hr <= 12) && (min >= 0 && min <= 59) && min % 15 == 0)
                return Time(hr, min, (daylight == "am") ? (true) : (false));
        }
    }

    return Time(); // 00:00
}

Last updated