- You are expected to work individually.
- Due: Monday July 14th at 11pm ET (Baltimore time).
- This assignment is worth 60 points.
Learning Objectives
To practice with:
- Input and output using iostreams (
cin
andcout
) - File input and output using
ifstream
andofstream
- C++
string
- Data representation using STL collections such as
vector
andmap
- Access and manipulation of STL collections using iterators
Overview
The goal for this assignment is to become familiar with basic I/O in C++, and to use built-in classes and algorithms from the STL to solve a problem that would be difficult to code from scratch.
Phone Database
Your will write a program to keep track of an in-memory database of contact names and phone numbers. The database should be structured as follows:
- Each contact consists of a last name and a first name
- Each contact can have up to 5 phone numbers, with types “HOME”, “CELL”, “WORK”, “FAX”, and “VOIP”; only one phone number of each type is allowed for a single contact
Last names and first names are nonempty sequences of non-whitespace characters.
Phone numbers are sequences of digit (0–9) and hyphen (“-“) characters, and must start and end with a digit.
Input and Output
The program will read data from cin
and write data to cout
.
There are three kinds of output from the program.
Each kind of output must be printed as a complete line of output to cout
, terminated by a newline/endl
, in exactly the forms specified below.
Info messages are messages that are helpful for the user, but which aren’t part of the “official” results of the program. They have the form
Info: message text
where message text
can be any text. You can use info messages for prompts
and other information for the user.
Result messages indicate the result of a command, and have the form
Result: message text
where message text
is the exact text required for a particular command result
(as described in the Commands section below.)
Error messages are messages indicating that an error has occurred. They have the form
Error: message text
where message text
is the exact text required for the error message (again,
as described in the Commands section.) Note that error messages
should be printed to cout
, not cerr
.
Commands
The program should support the following commands. The user will enter each command as a single line of text. There can be arbitrary horizontal whitespace (e.g., space and tab characters) preceding or following each token.
Here is a summary of the commands and their forms:
Command | Purpose | Form |
---|---|---|
C |
Create a contact | C lastname firstname |
D |
Delete a contact | D lastname firstname |
L |
List contact names | L |
P |
List phone numbers for a contact | P lastname firstname |
N |
Add or replace phone number for a contact | N lastname firstname type phone_number |
X |
Delete phone number for a contact | X lastname firstname type |
S |
Save the database to a file | S filename |
R |
Restore the database from a file | R filename |
Q |
Quit the program | Q |
For all commands with lastname
and firstname
values specifying
a contact name, searches are case-insensitive. As an example, if a contact
with last name Granger
and first name Hermione
exists in the database,
then a P
command specifying granger
and HERMIONE
as the last name
and first name should match the existing contact, and print the phone
numbers for that contact.
Note however that whenever a contact’s last and first names are printed
by the L
command, the exact capitalization used in the original C
command
when creating the contact should be used. One way to think about this is
that the contacts are stored in the database using an exact representation
of upper and lower case for each letter in the last and first names,
but comparisons between contact names should be done in a case-insensitive
manner.
C
command
The C
command creates a contact.
If successful, a result message with the text Contact created
should be printed.
If unsuccessful because a contact with the same last name and first name
already exists, an error message with the text Contact already exists
should be printed.
D
command
The D
command deletes a contact.
If successful, a result messasge with the text Contact deleted
should be printed.
If unsuccessful because no contact with the given last name and first name exists,
an error message with the text Contact not found
should be printed.
L
command
The L
command lists the last and first names of each contact. The contacts
should printed in lexicographical order by last name, with comparison of
first names breaking ties in the case where two contacts have the same last
name. All comparisons are case-insensitive. Each contact name should be
printed as a separate result message, with message text of the form lastname,firstname
.
P
command
The P
command prints the phone numbers for a specific contact. The phone
numbers are printed in alphabetical order by type, so (for example) the
CELL
phone number should come before the HOME
phone number.
Each existing phone number should be printed as a separate result message, with
message text of the form type,phone_number
. (If no phone number is
specified for a specific type, that type should not be printed.)
If the contact is not found, an error message with the message text
Contact not found
should be printed.
Note that it is not an error if the contact exists, but there are no phone numbers for the contact. In this case there are no required result messages to output, but you could print an info message.
N
command
The N
command adds or modifies a phone number for a contact.
If the command successfully adds a new phone number (i.e., there was
no prior phone number of the specified type for the contact), a result
message with the text Phone number added
should be printed.
If the command successfully replaces a phone number (i.e., a phone
number of the specified type already existed for the contact),
a result message with the text Phone number replaced
should
be printed.
If the contact is not found, an error message with the message text
Contact not found
should be printed.
If the phone number type is not valid, meaning that it is not one of
CELL
, HOME
, WORK
, FAX
, or VOIP
, an error message with the
message text Invalid phone number type
should be printed.
If the phone number is not valid, an error message with the message
text Not a valid phone number
should be printed.
(The possible error conditions should be checked and handled in the order specified above. Only one error message should be printed.)
X
command
The X
command deletes a phone number for a contact.
If successful, a result message with the text Phone number deleted
should be printed.
If the contact is not found, an error message with the message text
Contact not found
should be printed.
If the contact has no phone number of the specified type, an error message
with the message text No phone number of that type
should be printed.
Note that the X
command does not need to validate that the phone
number type is valid, as the N
command does. So, if the phone number
type is invalid, the correct response is an error message with the
text No phone number of that type
.
(The possible error conditions should be checked and handled in the order specified above. Only one error message should be printed.)
S
command
The S
command saves all of the data currently in the phone database to
a named file.
If the output file can’t be opened, an error message with
the message text Could not open output file
should be printed.
Note that there is no specification for how data should be written.
It is up to you to determine a format that will allow the R
command
to successfully read back the saved data.
R
command
The R
command loads data from a file written using a previous S
command
into the database, replacing whatever data is currently present.
If the input file can’t be opened, an error message with
the message text Could not open input file
should be printed.
If the input file can be opened, but the data in it is not valid,
an error message with the message text Invalid database file
should be printed.
Note that if the input data is corrupted, the database contents in memory
are allowed to be indeterminate. (In other words, it is acceptable for
the R
command to partially load data into memory.)
Q
command
The Q
command causes the program to terminate.
Example Session
Here is an example session of the program:
Info: Welcome to the Phone Database!
Info: Please enter a command
C Weasley Ron
Result: Contact created
Info: Please enter a command
C Granger Hermione
Result: Contact created
Info: Please enter a command
C Malfoy Draco
Result: Contact created
Info: Please enter a command
L
Result: Granger,Hermione
Result: Malfoy,Draco
Result: Weasley,Ron
Info: There are 3 contact(s)
Info: Please enter a command
N Granger Hermione CELL 401-555-1234
Result: Phone number added
Info: Please enter a command
N Weasley Ron WORK 61-491-570-156
Result: Phone number added
Info: Please enter a command
N Granger Hermione VOIP 301-555-8765
Result: Phone number added
Info: Please enter a command
P Granger Hermione
Result: CELL,401-555-1234
Result: VOIP,301-555-8765
Info: Found 2 phone number(s) for this contact
Info: Please enter a command
P Weasley Ron
Result: WORK,61-491-570-156
Info: Found 1 phone number(s) for this contact
Info: Please enter a command
P Malfoy Draco
Info: There are no phone numbers for this contact
Info: Please enter a command
D Malfoy Draco
Result: Contact deleted
Info: Please enter a command
L
Result: Granger,Hermione
Result: Weasley,Ron
Info: There are 2 contact(s)
Info: Please enter a command
X Granger Hermione VOIP
Result: Phone number deleted
Info: Please enter a command
P Granger Hermione
Result: CELL,401-555-1234
Info: Found 1 phone number(s) for this contact
Info: Please enter a command
P Longbottom Neville
Error: Contact not found
Info: Please enter a command
Q
Info: Thank you for using the Phone Database!
Hints
The first thing you will need to think about is how to use STL containers
to represent the data in the phone database. The map
container type
should be very useful. For example, if Name
represents a contact
name (last name and first name), and PhoneNumberCollection
represents a
collection of phone numbers for a contact, then the variable declaration
map<Name, PhoneNumberCollection> phone_db;
could be a good representation for the entire phone database.
Note that in order to allow searches to be case-insensitive, you will
need to have a way of ordering the keys in this map that ignores case
distinctions. I.e., we want the contacts Granger,Hermione
and granger,HERMIONE
to be considered the same when searching, and we want granger,HERMIONE
to be considered “less than” Longbottom,Neville
, even though the
character code for g
is greater than the character code for L
.
To allow this, we will need the map to use an appropriate ordering function
for the map keys (in this case, the keys belong to the Name
data type.)
The default ordering function for keys in map
s is
std::less
,
which in turn is based on ordering values according to the <
(less than) operator.
So, one way to ensure that contact names are ordered correctly is to override the <
operator. This would look something like this:
bool operator<(const Name &left, const Name &right) {
// return true if left<right, false otherwise,
// ignoring case
}
As long as this function exists, is defined correctly, and its declaration
preceeds any use of the Name
type as the key type in a map
instance,
then the contact names should be ordered correctly.
Note that the Name
and PhoneNumberCollection
data types don’t necessarily
need to be struct
or class
types that you define. They could also
be typedef
s for STL data types. For example:
// Note: probably not a great way to represent phone numbers for
// a contact
typedef vector<string> PhoneNumberCollection;
This typedef
would make PhoneNumberCollection
an alias for vector<string>
.
Even though vector<string>
is not a great way to represent the collection of
phone numbers for a contact, it’s likely that you can use STL data types
to achieve a better representation.
When writing the database to a file in the S
command, you will need to think
about how to write the data so that it can be easily read back in by the R
command. One useful technique to use when writing a collection of data
is to first write the number of items in the collection, and then write each
individual item. That way, the code that reads the data will know exactly
how many items should be read back in.
Testing
A good way to test the program is to prepare an input file with commands to send to the program, and an expected output file containing the result and error messages expected to be produced. (The expected output file shouldn’t contain the info messages, since producing them is optional, and their content isn’t specified.)
Example input and expected output files called input1.txt
and
expected_output1.txt
are provided with the starter files. Here is how
you can use them to test your program:
make phone_db
(./phone_db < input1.txt) | egrep -v "^Info:" > actual1.txt
diff expected_output1.txt actual1.txt
echo $?
If the diff
command produces no output, and the echo $?
command results in
the output 0
, then your program’s output matched the expected output.
If the files that diff
is comparing are different, then it will display the differences in the lines of each file, using <
to indicate a line from the first file argument and a >
to indicate a line from the second file argument. It will also give you indications of line numbers for the differences, such as 5c5,6
to mean line number 5 between columns 5 and 6. Here is a brief example:
5c5,6
< // PART 7 TO DO: Make sure to include any additional necessary header files
---
> // PART ? TO DO: Make sure to include any additional necessary header files
Shared Test Repository
To allow you to share your tests with the entire class, and to benefit from tests written by other students, we have set up a shared test repository: https://github.com/jhu-ip/cs220-summer24-hw5-tests
The basic idea is that you can clone this repository and then
- use
git add
andgit push
to contribute your own tests - use
git pull
to gain access to tests written by others
See the README.md
for details.
Files, Submitting
Provided files
Start with the template Makefile, source files, and header file in the public repo:
cs220-summer22-public/homework/hw5/
.
-
The Makefile is very minimal, building an executable called
phone_db
from a source file calledphone_db.cpp
. This might be sufficient, but if you would like to use additional source and/or header files, you will need to modify the Makefile accordingly -
The initial
phone_db.cpp
is very minimal, consisting of just amain
function
Gitlog
You must include with your submission a copy of the output of git log
showing at least five commits to the repository. Save the git log
output
into a file called gitlog.txt
(e.g. by doing git log > gitlog.txt)
.
README
Please submit a file called README
(not README.txt
or README.md
,
etc – just README
) including information about what design choices you
made and anything the graders should know about your submission. In your
README
you should:
- Write your name and JHED ID at the top of the file.
- Briefly justify the structure of your program; why you defined the functions you did, etc.
- If applicable: Highlight anything you did that was particularly clever.
- If applicable: Tell the graders if you couldn’t do everything. Where did you stop? What did you get stuck on? What are the parts you already know do not work according to the requirements?
Compiling
Your code should compile with no errors or warnings with the typical
command: g++ <source> -Wall -Wextra -std=c++-11 -pedantic
.
(These are the options included in the provided Makefile
.)
Submission
Create a .zip
file named hw5.zip
containing:
- All
.cpp
files - All
.h
files (if any) - README
- Makefile
- gitlog.txt
Copy hw5.zip
file to your local machine (using scp
or pscp
),
and submit it as Homework 5 on Gradescope. When you submit, gradescope
conducts a series of automatic tests. These do basic checks, e.g. to
check that you submitted the right files. If you see error messages (in
red), address them and resubmit. You may re-submit any number of times
prior to the deadline; only your latest submission will be graded. Review
the course syllabus for late submission policies (grace period and late
days), and remember that if your final submitted code does not compile,
you will likely earn a zero score for the assignment.
Remember that if your final submitted code does not compile, you will earn a zero score for the assignment.
Two notes regarding automatic checks for programming assignments:
- Passing an automatic check is not itself worth points. (There might be a nominal, low point value like 0.01 associated with a check, but that won’t count in the end.) The checks exist to help you and the graders find obvious errors.
- The automatic checks cover some of the requirements set out in the assignment, but not all. It is up to you to test your own work and ensure your programs satisfy all stated requirements. Passing all the automatic checks does not mean you have earned all the points.