9.4. Lab: Collections
Notes on Assignments:
Notes on GAI: Note that the course policy is that you should not use generative AI (GAI) without authorization. GAI’s are great tools and you should learn how to use it, but they are tools and should be used to facilitate but not replace your learning. If you are suspected to have used GAI tools to generate answers to the assignment questions instead of using it as a learning tool, you may be called up to explain/reproduce your work. If you fail to demonstrate your competence, all your related assignments throughout the semester will be regraded as 0. For example, if you fail to produce good code in
while
loops in midterm exam, your lab06 while loop homework and lab will be re-evaluated.
Create a dotnet console app project (see Create a C# Project if you need to) in your
introcscs
directory (C:\Users\*USERNAME*\workspace\introcscs
for Windows orCOMPUTER:introcscs USERNAME$
for macOS) ; call it Ch09CollectionsLab.Inside the folder, issue the command
dotnet new console
to create the project in the folder.Still inside the project directory, type
code .
to start VS Code with the folder as the default folder.Prepare your code in VS Code using the file
Program.cs
to code unless otherwise specified.The namespace of this project is IntroCSCS.
The class name of this project is Ch09CollectionsLab.
When executing code, you will start with the Main() method in a designated class/file.
You will prepare methods in the same class or project to be called from the Main() method.
Use a Word document to prepare your assignment.
Number the questions and annotate your answers (using // in code) to show your understanding.
For coding questions, screenshot and paste 1) your code in VS Code and 2) the results of the code’s execution (command prompt and username are part of the execution).
9.4.1. Overview
9.4.1.1. Goals for this lab:
Read a text file.
Work with loops.
Work with a Dictionary and a List.
Retrieve a random entry.
This project has a program called We-Give-Answer!
as in fake_help_verbose/fake_help_verbose.cs
that has some fake AI/virtual assistant capacity. The program is working but all the response texts are
hard-coded in the program and that is not ideal for maintenance. Your team want to use external text
files so that you can manage response texts with ease. For that your colleagues have prepared a file
called fake_help.cs
. Your job is to complete this new program.
Your team has designed this new program to be structured a little differently from the verbose version.
A helper class file_util.cs
(file_util.cs) is added to
contain the functionalities of handling files. You need to work on the file to provide such functionalities.
You will need to complete short versions of methods GetParagraphs
and GetDictionary
in file_util.cs
.
Testing: When you complete this function, the program should behave just like the earlier verbose version with the hard-coded data: Using a dictionary value if it finds the right key, or choosing a random response if there is no key match.
This should also be an extremely short amount of coding! Think of following through the data file, and get the corresponding sequence of instructions to handle the data in the exact same sequence.
9.4.2. Steps
9.4.2.1. Download files
Download the following files by going to the github repositories and use the download raw files to download individual files and move them to the project folder:
fake_help_verbose (fake_help_verbose),
fake_help.cs (dict_lab_stub) along with the following files:
file_util.cs
help_not_defaults.txt
help_not_responses.txt, and
the UI class (
ui.cs
at ui) so you don’t have to write the user input code.
Note that some of the files are data files (*.txt).
Note
For now, you may keep the namespace IntroCS
and change them later for organization purpose.
9.4.2.2. The FakeHelpVerbose Class
Open the working program, the downloaded take_help_verbose.cs (fake_help_verbose/fake_help_verbose.cs) and
look at how the methods Main
, Response
, GetParagraphs()
and GetDictionary()
work together
to provide the Fake Help functionality.
Note
To run the program (take_help_verbose.cs), you need to change the Main
method in fake_help.cs to something
else such as main
as you are allowed to have only one Main in a program and both fake_help.cs and
fake_help_verbose.cs have a Main
method. You have to rename or comment out one of them to run the other.
All the strings for the responses are pre-coded for you there, but if
you were writing your own methods, it would be a pain. There is all the
repetitious code to make multiline strings and then to add to the
List
and Dictionary
. That’s why you will later provide simple versatile methods to
fill a List<string>
or a Dictionary<string, string>
so that you only need
you to write the string data itself into a text file, with the only
overhead being a few extra newlines.
For now, run the program to see how this fake AI/virtual look like:
PS C:\Users\[username]\workspace\introcscs\Ch09CollectionLab> dotnet run
Welcome to We-Give-Answers! // this is the prompt for user action
What do you have to say?
Enter 'bye' to end our session. // input "bye" to escape the while loop and exit
>
The Main method: Look back into the code, in the Main
method, you see that:
public static void Main(string[] args)
{
List<string> guessList = GetParagraphs(); // create a filled string list guessList from GetParagraph
Dictionary<string, string> responses = GetDictionary(); // create a filled dictionary response from GetDictionary
string prompt = "\n> "; // create a string variable and initialize it as prompt
Console.WriteLine(@"Welcome to We-Give-Answers! // prompt user input;
What do you have to say?"); // the @ means the string is a verbatim string literal; escape sequence is ignored
Console.Write("\nEnter 'bye' to end our session."); // \n means print a new line
string fromUser; // create another string variable
do // use do-while loop to loop at least once and keep looping
{ // until the condition is met
fromUser = UI.PromptLine(prompt).ToLower().Trim(); // user UI's PromptLine to read user input and save it to fromUser
if (fromUser != "bye") // condition: if user does not enter 'bye'...
{
string answer = Response(fromUser, guessList, responses); // call Response(); throw the args to Response()
Console.WriteLine('\n' + answer); // print returned string from Response in a new line
}
} while (fromUser != "bye"); // condition: when user input "bye", terminate the while loop
Console.WriteLine(@" // Good bye with verbatim string literal
We-Give-Answers
thanks you for your patronage.
Call again if we can help you
with any other problem!");
}
Both the GetParagraphs()
and GetDictionary()
in this class actually does one
thing: To add texts to the List/Dictionary variables:
The GetParagraph method: The GetParagraph()
method, taking no arguments, returns a string list of paragraphs:
public static List<string> GetParagraphs() // when called, return a string list
{
List<string> all = new List<string>(); // create the new "all" string list variable
all.Add(@"No other customer has ever complained // add string to the variable using "Add()"
about this before. What is your system
configuration?");
all.Add(@"That sounds odd. Could you describe // add string to the variable using "Add()"
that problem in more detail?");
// ...
all.Add("Could you elaborate on that?"); // add string to the variable using "Add()"
return all; // return the string list variable all
The GetDictionary method: The GetDictionary()
method, taking no arguments,
creates and returns a dictionary of keyword:paragraph (key:value) pairs:
public static Dictionary<string, string> GetDictionary() // the method returns a Dictionary<string, string> type
{
Dictionary<string, string> d = new Dictionary<string, string>(); // create Dictionary variable d
d["crash"] = @"Well, it never crashes on our system. // add key:value pair to d
It must have something to do with your system.
Tell me more about your configuration.";
d["slow"] = @"I think this has to do with your hardware. // add key:value pair to d
// ... // keep adding...
d["linux"] = @"We take Linux support very seriously.
But there are some problems.
Most have to do with incompatible glibc versions.
Can you be a bit more precise?";
return d; // return dictionary d to caller
The Response method: Response()
takes three arguments and returns:
a paragraph value from the responses dictionary (created by
GetDictionary()
) if the user input matches the paragraph’s key; ora random paragraph from the string list guessList (created by
GetParagraphs()
).
public static string Response(string fromUser, List<string> guessList,
Dictionary<string, string> responses)
{
char[] sep = "\t !@#$%^&*()_+{}|[]\\:\";<>?,./".ToCharArray(); // define separators
string[] words = fromUser.ToLower().Split(sep); // make fromUser lower case and split the words
foreach (string word in words) { // loop through the words
if (responses.ContainsKey(word)) { // if a word (keyword) matches a "responses" dictionary key
return responses[word]; } } // return the value (msg) by that key
return guessList[rand.Next(guessList.Count)]; // if the word is not a key in "response", return a random "guess"
}
9.4.2.3. The FakeHelp Class
help_not_defaults.txt: First, look into your lab project for the first data file: help_not_defaults.txt, and the beginning is shown below:
Welcome to We-Give-Answers!
What do you have to say?
We-Give-Answers
thanks you for your patronage.
Call again if we can help you
with any other problem!
No other customer has ever complained
about this before. What is your system
configuration?
That sounds odd. Could you describe
that problem in more detail?
You can see that it includes the data for the welcome and goodbye strings, as seen in
FakeHelpVerbose (fake_help_verbose.cs)
, followed by all the data to go in
the list variable guessList
of random answers.
One complication is that many of these strings take up several lines, in what we call a paragraph. We follow a standard convention for putting paragraphs into plain text: Put a blank line after a paragraph to mark its end. As you can see, that is how help_not_defaults.txt is set up.
Now look in your copy of the class FakeHelp (fake_help.cs
), you will see that it is
very similar to class FakeHelpVerbose in fake_help_verbose.cs
:
11public static void main()
12{
13 StreamReader reader = new StreamReader("help_not_defaults.txt");
14 // special data is in the first two paragraphs
15 string welcome = FileUtil.ReadParagraph(reader);
16 string goodbye = FileUtil.ReadParagraph(reader);
17 List<string> guessList = // rest of the file gives a
18 FileUtil.GetParagraphs(reader); // list of random responses
19 reader.Close();
20 reader = new StreamReader("help_not_responses.txt");
21 Dictionary<string, string> responses =
22 FileUtil.GetDictionary(reader);
23 reader.Close();
24 Console.Write(welcome);
25 string prompt = "\n> ";
26 Console.WriteLine("Enter 'bye' to end our session.");
27 string fromUser;
28 do
29 {
30 fromUser = UI.PromptLine(prompt).ToLower().Trim();
31 if (fromUser != "bye")
32 {
33 string answer = Response(fromUser, guessList, responses);
34 Console.Write("\n" + answer);
35 }
36 } while (fromUser != "bye");
37 Console.Write("\n" + goodbye);
38}
The
StreamReader
is set up to read from the right file.The the
FileUtil
methodsReadParagraph
,GetParagraphs
, andGetDictionary
are used to provide the text data needed.
All of the additions you need to make are in the bodies of method
definitions in the class FileUtil
. Look back to Main
in fake_help.cs
to
see how the methods from FileUtil
are actually used:
.. definitions in the class FileUtil
. Look back to Main
in FakeAdvise
to
It creates the
List
guessList
and theDictionary
responses
using more general functions that you need to fill in.The stubs are put in the class
FileUtil
for easy reuse.The
Main
calls these methods and chooses the files to read.The results will look the same as the original program to the user, but the second version will be easier for a programmer to read and generalize: It will be easier in other situations where you want lots of canned data in your program (like in a game you might write).
The stub should run as is (mostly saying things are not implemented).
Test out your work at every stage!
9.4.2.4. ReadParagraph
The first method to complete in
file_util.cs
is useful by itself and later for use in the
GetParagraphs
and GetDictionary
that you will complete. See the stub:
/// Return a string consisting of a sequence of nonempty lines read
/// from reader. All the newlines at the ends of these lines are included.
/// The function ends after reading (but not including) an empty line.
public static string ReadParagraph(StreamReader reader)
The first call to ReadParagraph
, using the file illustrated above, should
return the following (showing the escape codes for the newlines):
"Welcome to We-Give-Answers!\nWhat do you have to say?\n"
and then the reader should be set to read the goodbye paragraph
(the next time ReadParagraph
is called).
To code, you can read lines one at a time, and append them to the part of the
paragraph read so far. There is one thing to watch out for: The
ReadLine
function throws away the following newline ("\n"
) in the input. You
need to preserve it, so be sure to explicitly add a newline, back onto
your paragraph string after each nonempty line is added. The returned
paragraph should end with a single newline.
Throw away the empty line in the input after the paragraph. Make sure you stop after reading the empty line. It is very important that you advance the reader to the right place, to be ready to read the next paragraph.
Be careful of a pitfall with files: You can only read a given chunk once:
If you read again, with the exact same syntax,
you get the next line of the file. The ReadLine
method
has the side effect of advancing the reading position in the file.
Testing: This first short ReadParagraph
function should actually be most of
the code that you write for the lab! The program is set up so you can immediately
run the program and test ReadParagraph
: It is called to read in the welcome string
and the goodbye string for the program, so if those come correctly to the screen, you
can advance to the next two parts.
9.4.2.5. GetParagraphs
Since you have ReadParagraph
at your disposal, you now only need to
insert a few remaining lines of code to complete the next method
GetParagraphs
, that reads to the end of the file, and likely
processes more than one paragraph.
/// Read the remaining empty-line terminated paragraphs
/// from reader into a new list of paragraph strings,
/// and return the list.
/// The function reads all the way to the end of
/// the file attached to reader.
/// The file must end with two newlines in sequence: one at the
/// end of the last nonempty line followed by one for the empty line.
public static List<string> GetParagraphs(StreamReader reader)
{
List<string> all = new List<string>();
// REPLACE the next line with your lines of code to fill all
all.Add("You have not coded GetParagraphs yet!\n");
return all;
}
Look again at help_not_defaults.txt, to see how the data is set up.
This lab requires very few lines of code. Be sure to read the examples and instructions carefully (several times). A lot of ideas get packed into the few lines!
Testing: After writing GetParagraphs
, the random
responses in the lab project program should work as the user enters lines in the program.
9.4.2.6. GetDictionary
The last stub to complete in file_util.cs
is GetDictionary
. Its
stub also takes a StreamReader
as parameter. In
Main
this method is called to read from
help_not_responses.txt.
Here are the first few lines:
crash
Well, it never crashes on our system.
It must have something to do with your system.
Tell me more about your configuration.
slow
I think this has to do with your hardware.
Upgrading your processor should solve all
performance problems.
Have you got a problem with our software?
performance
Performance was quite adequate in all our tests.
Are you running any other processes in the background?
Here is the stub of the function to complete, reading such data:
/// Return a new Dictionary, taking data for it from reader.
/// Reader contains key-value pairs, where each single-line key is
/// followed by a possibly multi-line paragraph value that is terminated
/// by an empty line. The file must end with two newlines in sequence:
/// one at the end of the last nonempty line followed by one for the
/// empty line.
public static Dictionary<string, string> GetDictionary(StreamReader reader)
{
Dictionary<string, string> d = new Dictionary<string, string>();
// add your lines of code to fill d here!
return d;
}