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 :ref:`create-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**. #. 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). Overview ----------- 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 :repsrc:`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`` (:repsrc:`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. Steps ----------------- 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 (:repsrc:`fake_help_verbose`), #. fake_help.cs (:repsrc:`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 :repsrc:`ui`) so you don't have to write the user input code. .. #. help_not_responses2.txt, and 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. The FakeHelpVerbose Class ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Open the working program, the downloaded take_help_verbose.cs (:repsrc:`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`` or a ``Dictionary`` 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: .. code-block:: bash 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: .. code-block:: csharp public static void Main(string[] args) { List guessList = GetParagraphs(); // create a filled string list guessList from GetParagraph Dictionary 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: .. code-block:: public static List GetParagraphs() // when called, return a string list { List all = new List(); // 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: .. code-block:: public static Dictionary GetDictionary() // the method returns a Dictionary type { Dictionary d = new Dictionary(); // 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; or #. a **random** paragraph from the string list **guessList** (created by ``GetParagraphs()``). .. code-block:: none public static string Response(string fromUser, List guessList, Dictionary 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" } The FakeHelp Class ~~~~~~~~~~~~~~~~~~~~ **help_not_defaults.txt**: First, look into your lab project for the first data file: :repsrc:`help_not_defaults.txt `, and the beginning is shown below: .. literalinclude:: ../../examples/introcs/dict_lab_stub/help_not_defaults.txt :language: none :lines: 1-15 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 :repsrc:`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``: .. code-block:: :lineno-start: 11 public static void main() { StreamReader reader = new StreamReader("help_not_defaults.txt"); // special data is in the first two paragraphs string welcome = FileUtil.ReadParagraph(reader); string goodbye = FileUtil.ReadParagraph(reader); List guessList = // rest of the file gives a FileUtil.GetParagraphs(reader); // list of random responses reader.Close(); reader = new StreamReader("help_not_responses.txt"); Dictionary responses = FileUtil.GetDictionary(reader); reader.Close(); Console.Write(welcome); string prompt = "\n> "; Console.WriteLine("Enter 'bye' to end our session."); string fromUser; do { fromUser = UI.PromptLine(prompt).ToLower().Trim(); if (fromUser != "bye") { string answer = Response(fromUser, guessList, responses); Console.Write("\n" + answer); } } while (fromUser != "bye"); Console.Write("\n" + goodbye); } - 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! ReadParagraph ~~~~~~~~~~~~~~ The first method to complete in :repsrc:`file_util.cs ` is useful by itself and later for use in the ``GetParagraphs`` and ``GetDictionary`` that you will complete. See the stub: .. literalinclude:: ../../examples/introcs/dict_lab_stub/file_util.cs :start-after: ReadParagraph chunk :end-before: chunk :dedent: 6 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. 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. .. literalinclude:: ../../examples/introcs/dict_lab_stub/file_util.cs :start-after: GetParagraphs chunk :end-before: chunk Look again at :repsrc:`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. GetDictionary ~~~~~~~~~~~~~~~ The last stub to complete in :repsrc:`file_util.cs ` is ``GetDictionary``. Its stub also takes a ``StreamReader`` as parameter. In ``Main`` this method is called to read from :repsrc:`help_not_responses.txt `. Here are the first few lines: .. literalinclude:: ../../examples/introcs/dict_lab_stub/help_not_responses.txt :language: none :lines: 1-15 Here is the stub of the function to complete, reading such data: .. literalinclude:: ../../examples/introcs/dict_lab_stub/file_util.cs :start-after: GetDictionary chunk :end-before: chunk :dedent: 6 .. Be careful to distinguish the data file .. :repsrc:`help_not_responses.txt ` .. from .. :repsrc:`help_not_responses2.txt `, .. used in the extra credit option. .. Show the program output to a TA (after the extra credit if you like). .. Extra credit .. ~~~~~~~~~~~~~~~~~~~~~~ .. #. (20%) Modify ReadParagragh so it *also* works if the paragraph ends .. at the end of the file, with no blank line after it, or if the line after .. the paragraph only has whitespace characters. Both changes are good to .. bullet-proof the code, since .. the added or removed whitespace is hard to see in print. .. #. (20%) The crude word classification scheme would recognize "crash", but not .. "crashed" or "crashes". You could make whole file entries .. for each key variation, repeating the value paragraph. .. A concise approach is to use a data file .. like :repsrc:`help_not_responses2.txt `. .. Here are the first few lines: .. .. literalinclude:: ../../examples/introcs/dict_lab_stub/help_not_responses2.txt .. :language: none .. :lines: 1-15 .. The line that used to have one key now may have several blank-separated keys. .. Here is how the documentation for ``GetDictionary`` should be changed: .. .. literalinclude:: ../../examples/introcs/dict_lab_stub/file_util.cs .. :start-after: Extra credit documentation .. :end-before: } .. :dedent: 6 .. Modify the lab project to use this file effectively: Find .. "help_not_responses.txt" on line 22 in ``Main``. Change it to .. "help_not_responses2.txt" (inserting '2'), so ``Main`` reads it. .. In your test of the program, be sure to use several of the keys that apply to the .. same response, and show to your TA.