This week’s section exercises focuses on three things: getting you setup on Qt Creator with the correct settings, exploring some C++ quirks and learning about some basic data structures. Have fun!
Each week, we will also be releasing a Qt Creator project containing starter code and testing infrastructure for that week's section problems. When a problem name is followed by the name of a .cpp file, that means you can practice writing the code for that problem in the named file of the Qt Creator project. Here is the zip of the section starter code:
1) Setting up your environment correctly
Topics: QT Creator
- Navigate to
Recommended Qt settingsand set all recommended settings suggested. You'll use Qt Creator for all assignments this quarter, so it's important that you use the best settings to make you more efficient. - Make a CS106B folder in your home directory. You can do so by:
- Opening your finder(if you use a Mac) or Windows Explorer (if you use Windows)
- Click on MacintoshHD for Mac or Primary Drive (C:) for windows
- Click on Users
- Click your name
- Right click and make new folder.
- Name the folder with ordinary characters, no spaces, special characters or emojis When you download new assignments and section materials, be sure to store them here. This will make it so that Qt has all the permissions it needs to run your programs
- If you use a Mac
- Right click on this section's .pro file
- Select Get info
- Check that Qt Creator has been set as the default program to open .pro files. If not, choose Qt Creator from the drop down, and click on Change All
- If you use Windows
- Open File Explorer (open any folder).
- Click the View tab.
- Select "File name extension" This will make it so you can see which files end with .pro
- Helpful Qt Creator hot keys (if you use windows, replace Command with Ctrl):
- Command + B to build your program
- Command + R to run your program
- Command + Y to run in debug mode
2) Program analysis: C++isms you should know
Topics: Types, References, range based loops, strings, stanford C++ library
In the following, we will analyze a simple program that filters last names whose end match a specific substring. Given an input string of format:
name1,name2, ...
and a string suffix, the program returns all the names in the input string that ends with the suffix.
#include "SimpleTest.h"
#include "vector.h"
#include "strlib.h"
using namespace std;
/*
@param input: input string whose last names will be filtered
@param suffix: the substring which we will filter last names by
Functionality: this function filters the input string and returns last names
that end with 'suffix'
*/
Vector<string> filter(string input, string suffix)
{
Vector<string> filteredNames;
Vector<string> names = stringSplit(input, ',');
for (string name: names) {
// convert to lowercase so we can easily compare the strings
if (endsWith(toLowerCase(name), toLowerCase(suffix))) {
filteredNames.add(name);
}
}
return filteredNames;
}
STUDENT_TEST("Filter names") {
Vector<string> results = filter("Zelenski,Szumlanski,Kwarteng", "Ski");
EXPECT_EQUAL(results, {"Zelenski","Szumlanski"});
results = filter("AmbaTi,Szumlanski,Tadimeti", "TI");
Vector<string> expected = {"AmbaTi", "Tadimeti"};
EXPECT(results == expected);
results = filter("Zelenski,Szumlanski,Kwarteng", "NnG");
EXPECT_EQUAL(results, {});
// what other tests could you add?
}
3) countNumbers (sum.cpp)
Topics: Vectors, strings, file reading, while true, conditional statements, Stanford C+++ library
The function countNumbers reads a text file and returns the number of times a user entered number appears in that text file. Here are some library functions that will be useful for this task:
readLines, to read all lines from a file stream into a VectorstringSplit, to divide a string into tokensgetLine, to read a line of text entered by the userstringIsInteger, to confirm a string of digits is valid integer
In particular you will be asked to write the following function
int countNumbers(string filename)
When given the following file, named numbers.txt, as input, your function should print 1 when a user enters 42. Similarly, when the user enters 9, your function should return 2.
42 is the Answer to the Ultimate Question of Life, the Universe, and Everything
This is a negative number: -9
Welcome to CS106B!
I want to own 9 cats and 9 dogs.
/*
* Function: countNumbers
* ----------------------
* Write a program to read through a given file and count the
* the number of times a user inputed number appears in that file. You
* can assume that numbers will be composed entirely of numerical digits,
* optionally preceded by a single negative sign.
*/
void countNumbers(string filepath) {
ifstream in;
if (!openFile(in, filepath)) {
return;
}
Vector<string> lines = readLines(in);
while (true) {
string number = getLine("Enter a number to check (enter to quit): ");
if (number == "") {
break;
}
if (!stringIsInteger(number)) {
cout << "Please enter a number" <<endl;
continue;
}
int count = 0;
for (string line : lines) {
Vector<string> tokens = stringSplit(line, " ");
for (string t : tokens) {
if (t == number) {
count ++;
}
}
}
cout << "Number " << number << " appeared " << count << " times in the file." << endl;
}
}
4) Debugging Deduplicating (deduplicate.cpp)
Topics: Vector, strings, debugging
Consider the following incorrect C++ function, which accepts as input a Vector<string> and tries to modify it by removing adjacent duplicate elements:
⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️
void deduplicate(Vector<string> vec) {
for (int i = 0; i < vec.size(); i++) {
if (vec[i] == vec[i + 1]) {
vec.remove(i);
}
}
}
⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️
The intent behind this function is that we could do something like this:
Vector<string> hiddenFigures = {
"Katherine Johnson",
"Katherine Johnson",
"Katherine Johnson",
"Mary Jackson",
"Dorothy Vaughan",
"Dorothy Vaughan"
};
deduplicate(hiddenFigures);
// hiddenFigures = ["Katherine Johnson", "Mary Jackson", "Dorothy Vaughan”]
The problem is that the above implementation of deduplicate does not work correctly. In particular, it contains three bugs. First, find these bugs by writing test cases that pinpoint potentially erroneous situations in which the provided code might fail, then explain what the problems are, and finally fix those errors in code.
There are three errors here:
- Calling
.remove()on theVectorwhile iterating over it doesn’t work particularly nicely. Specifically, if you remove the element at indexiand then incrementiin the for loop, you’ll skip over the element that shifted into the position you were previously in. - There’s an off-by-one error here: when
i = vec.size() - 1, the indexingvec[i + 1]reads off the end of theVector. - The
Vectoris passed in by value, not by reference, so none of the changes made to it will persist to the caller.
Here are corrected versions of the code:
// solution 1
void deduplicate(Vector<string>& vec) {
for (int i = 0; i < vec.size() - 1; ) {
if (vec[i] == vec[i + 1]) {
vec.remove(i);
} else {
i++;
}
}
}
// solution 2
void deduplicate(Vector<string>& vec) {
for (int i = vec.size() - 1; i > 0; i--) {
if (vec[i] == vec[i - 1]) {
vec.remove(i);
}
}
}
5) Pig-Latin (piglatin.cpp)
Topics: Strings, reference parameters, return types
Write two functions, pigLatinReturn and pigLatinReference, that accept a string and convert said string
into its pig-Latin form. To convert a string into pig-Latin, you must follow these steps:
- Split the input string into 2 strings: a string of characters BEFORE the first vowel, and a string of characters AFTER (and including) the first vowel.
- Append the first string (letters before the first vowel) to the second string.
- Append the string "ay" to the resulting string.
Here are a few examples…
nick -> icknay
chase -> asechay
chris -> ischray
You will need to write this routine in two ways: once as a function that returns the pig-Latin string to the
caller, and once as a function that modifies the supplied parameter string and uses it to store the resulting pig-Latin string. These will be done in pigLatinReturn and pigLatinReference, respectively. You may assume that your input is always a one-word, all lowercase string with at least one vowel.
Here's a code example of how these functions differ…
string name = "julie";
string str1 = pigLatinReturn(name);
cout << str1 << endl; // prints "uliejay"
pigLatinReference(name);
cout << name << endl; // prints "uliejay"
Once you've written these functions, discuss with your section the benefits and drawbacks of these two approaches. Which do you feel is easier to write? Which do you think is more convenient for the caller? Do you think one is better style than the other?
// Use const because VOWELS won't change -- no need to declare repeatedly
// in isVowel.
const string VOWELS = "aeiouy";
// Helper function, which I'd highly recommend writing!
bool isVowel(char ch) {
// A little kludgy, but the handout guarantees that
// ch will ALWAYS be lower case :)
// NOTE: For an assignment, you probably want a more robust isVowel.
return VOWELS.find(ch) != string::npos;
}
string pigLatinReturn(string input) {
int strOneIndex = 0;
for (int i = 0; i < input.length(); i++) {
if (isVowel(input[i])) {
strOneIndex = i;
break;
}
}
string strOne = input.substr(0, strOneIndex);
string strTwo = input.substr(strOneIndex);
return strTwo + strOne + "ay";
}
void pigLatinReference(string &input) {
int strOneIndex = 0;
for (int i = 0; i < input.length(); i++) {
if (isVowel(input[i])) {
strOneIndex = i;
break;
}
}
string strOne = input.substr(0, strOneIndex);
string strTwo = input.substr(strOneIndex);
input = strTwo + strOne + "ay";
}
Notice how similar these two approaches are – the only difference is how the result is handled at the very end. To address the discussion questions, although the pigLatinReference function is marginally more efficient because it doesn't need to make a copy of the input string, pigLatinReturn is probably more intuitive for both the caller and the writer: if the function's job is to somehow output some product, returning is the most explicit way to do so. In that way, a function that returns is also better style – it's makes the purpose of the function clearer to the reader.
If you wanted to combine the efficiency of pigLatinReference with the clarity of pigLatinReturn, I would recommend writing a function that takes in the input string by const reference, basically
string pigLatin(const string &input);
Although the const isn't explicitly necessary, it's nice to have because you never need to modify input. Moreover, you still get the efficiency gains from pass-by-reference while also writing very-understandable code.