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.

  1. 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 or COMPUTER:introcscs USERNAME$ for macOS) ; call it Ch09CollectionsLab.

  2. Inside the folder, issue the command dotnet new console to create the project in the folder.

  3. Still inside the project directory, type code . to start VS Code with the folder as the default folder.

  4. Prepare your code in VS Code using the file Program.cs to code unless otherwise specified.

  5. The namespace of this project is IntroCSCS.

  6. The class name of this project is Ch09CollectionsLab.

  7. When executing code, you will start with the Main() method in a designated class/file.

  8. You will prepare methods in the same class or project to be called from the Main() method.

  9. Use a Word document to prepare your assignment.

  10. Number the questions and annotate your answers (using // in code) to show your understanding.

  11. 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:

  1. fake_help_verbose (fake_help_verbose),

  2. fake_help.cs (dict_lab_stub) along with the following files:

  3. file_util.cs

  4. help_not_defaults.txt

  5. help_not_responses.txt, and

  6. 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:

  1. a paragraph value from the responses dictionary (created by GetDictionary()) if the user input matches the paragraph’s key; or

  2. a 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 methods ReadParagraph, GetParagraphs, and GetDictionary 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 the Dictionary 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;
}