结构体指针:
指针为NULL:不指向任何有效内存
哈希表:
std::map
使用红黑树实现的,查找删除插入操作平均复杂度为O(logN)
不是所有 STL 容器都返回引用。不同的容器函数在返回值上有不同的行为。以下是一些常见的 STL 容器函数返回类型的情况:
返回引用的情况:
std::vector
、std::deque
和 std::list
的 back()
、front()
函数,它们返回的是引用。std::map
和 std::unordered_map
的 operator[]
返回键对应的值的引用。返回值的情况:
std::vector
、std::deque
和 std::list
的 pop_back()
、pop_front()
函数,它们删除元素并返回被删除元素的拷贝。std::map
和 std::unordered_map
的 operator[]
在键不存在时会插入默认值,并返回这个默认值的引用。返回迭代器的情况:
std::set
和 std::unordered_set
的 insert()
、erase()
函数,它们返回的是迭代器,指向插入或删除的元素位置。下面返回元素的方法都是返回的引用, 存在引用到值的隐式转换, 转为副本,
1 | int& ele = list.back(); // 修改list内元素 |
vector向量是一种动态数组(可变长数组),在运行时能根据需要自动改变数组大小。
1 |
|
STL的list是数据结构中的双向链表,内存空间不连续,通过指针来访问
特点:可高效率在任意地方删除与插入,但不支持随机访问(访问较慢)
不支持list.find(), 只能遍历查找
1 |
|
1 |
|
1 |
|
1 |
|
1 | map<string, int> mp; // 定义一个将string映射成int的map |
pair
迭代器:
迭代器是一个类,容器返回的是迭代器对象。
但是迭代器类对很多操作符做了重载。
*
): it 指向的是 值。1 | std::vector<int> myVector = {1, 2, 3, 4, 5}; |
->
): it 指向的是对象时候。1 | // map元素就是pair对象 |
++
): 将迭代器移动到容器的下一个位置。1 | std::list<int> myList = {1, 2, 3, 4, 5}; |
==
, !=
, <
, <=
, >
, >=
): 比较两个迭代器的相对位置。 是否指向的是同一个元素1 | std::vector<int> myVector = {1, 2, 3, 4, 5}; |
日期 | 1 | |||
---|---|---|---|---|
使用blankProject配置
// TODO
Stanford CPP Lib:
在QT中,有一个工具qmake可以生成一个makefile文件,它是由.pro文件生成而来的.
TEMPLATE
app
: 用于构建应用程序。这是一个典型的 GUI 或控制台应用程序项目。lib
: 用于构建库。subdirs
: 用于构建包含子项目的项目。这个模板允许你组织多个项目并在一个 .pro
文件中构建它们。选项 | 说明 |
---|---|
debug,release | 指定构建模式 |
console | 只用于app模板,指定项目是控制台程序 |
qt | 表示项目使用QT库 |
thread | 启用线程支持。当CONFIG包括qt时启用,这是缺省设置。 |
c++11 | 启用c++11支持。 |
windows | 只用于app模板 应用程序是一个windows的窗口应用程序 |
staticlib | 只用于“lib”模板,库一个静态库 |
dll | 只用于”lib”模板,库是一个共享库(dll) |
plugin | 只用于“lib”模板,库是一个插件,这会使dll选项生效 |
silent | 启用“安静”构建。 |
depend_includepath | |
QT |
1 | void printline(int width=10,char letter='*') |
1 | void quadratic(int a,int b,int c, double &root1,double& root2) |
void quadratic(int a,int b,int c, double &root1,double& root2);
1 | # |
1 | // |
!()[https://raw.githubusercontent.com/ddongzi/ddongzi.github.io/main/image/markdown/20240111173320.png]
1 | //concat |
![[Pasted image 20240111173850.png]]
string::npos
3. exercise:
1 | // a作为复制传入,不影响实参,b作为引用传入,会影响 |
1 | cout<<"name:"; |
1 | string name; |
1 |
|
1 | //字符串相加 |
1 | string s="hi"+"joe"; //c string + c string |
'\x0'
'\0'
"\x00"
表示一个含空字符的字符串(c语言),并不是空字符串。他的长度为1。 通过string构造即可转换空字符串。1 | // "\0000"后面的都无效,因为读到结尾了 |
1 | # |
1 | ifsteam input; |
不正确的:
1 | while(!input.fail()){ |
操作符,每次读取一个单词
1 | input>>word; |
1 | //读取所有单词 |
1 | string word; |
1 | Vector<int> nums {1,2,3}; |
int nums[5] {1,2,3}
1 | v.insert(2,34); |
1 | Grid<int> matrix(3,4); |
1 | int compuerSum(const Grid<int>& matrix); |
1 | Vector<Vector<int>> vv; |
1 | # |
stack[i]
索引访问1 | while(!s.isEmpty()){ |
1 | //好的做法 |
1 | void wordCount(const string & filename) |
1 | Map |
过程: 可以理解为树深度优先遍历。决策树
1 | snowflake(window,x,y,size,level); |
![[Pasted image 20240117204102.png]]
cantor set:
1 | void cantorSet(GWindow &window,int x,int y,int length, int level) |
1 | void cantorSet(GWindow &window,int x,int y,int length, int level) |
evaluate :
1 | /** |
1 | void printAllBinaryHelper(int digits,string output) |
穷举搜索常用递归实现。
exercise : printAllBinary(n)用递归实现
伪代码:
1 | function Explore(decisions): |
1 | int calls=0; |
![[Pasted image 20240123110501.png]]
优化: 去除不必要的递归
1 | int calls=0; |
![[Pasted image 20240123114433.png]]
Maze class:
1 | # |
“Arm’s” length recursion:
Escape Maze solution
1 | // This code is better. |
“Arm’s” length escapeMaze
1 | // This code is bad. It uses arm's length recursion. |
Exercise: sublists
sublists
that finds every possible sub-list of given vector. A sub-list of a vector V contains >=0 of V’s elements.1 | // v: avaliable, chosen: 目前已选择 |
The “8 Queens” problem
wiki
![[Pasted image 20240125120553.png]]
1 | void solveQueenHelper(Board& board , int col){ |
Classes;Arrays;Implementing a Collection
Elements of a class:
Interface vs code:
##include
inside .cpp filesClass declaration(.h)
1 | // |
Class example(v1)
1 | # |
using objects.
1 | BnakAccount ba1("may",1.0); |
The implicit parameter
Member func. bodies
1 | //ClassName.cpp |
Constructors
1 | BankAccount ba; |
1 | BankAccount ba ("A",1.2); |
Arrays(11.3)
type *name = new type[length];
type name[length];
we will not use this syntax.Initialized?
type *name = new type[length]; // uninitilized
type *name = new type[length](); // initilized to 0
两种对象创建方式:
Type obj
Exercise:
1 | # |
Pointers and Linked Lists
Linked data structures
Structs
1 | struct Date{ |
Null/garbage pointers
1 | int *p1=nullptr; |
Pointer to struct
Type *p=new Type(param);
1 | // 1) non-pointer |
A list node structure
1 | struct ListNode{ |
Traversing a list
1 | ListNode *cur=front; |
Linked list
Array/Linked List Class; PQs and Binary Heaps
The keyword const:
void foo(const BankAccount& ba){}
1 | // .h and .cpp |
1 | void foo(const ArrayStack& stack){ |
Operator overloading(6.2)
1 | returnType operator op(paramters); //.h |
cout<<stack<<endl;
实际上就是重载了<<。Make objects printable
1 | // 返回引用 |
-ostream is a base class, include io ...
1 | //.h |
Destuctor(12.3)
1 | //.h |
1 | ArrayStack::~ArrayStack(){ |
A LinkedIntList class
friend: 运算符重载写在成员函数,使得其可以访问成员变量(私有属性)。
friend ostream& operator <<(ostream& out, LinkedIntList& list);
返回指针和返回指针引用:
1 | //返回指针,即返回get 函数里result指针的 完全副本。 |
1 | # |
Priority Queues and Heaps
![[Pasted image 20240126130238.png]]
![[Pasted image 20240126130321.png]]
PQ as array/vector
![[Pasted image 20240126130432.png]]
Heaps
![[Pasted image 20240126130543.png]]
A perfect number is an integer that is equal to the sum of its proper divisors.
The first perfect number is 6
because its proper divisors are 1
, 2
, and 3
, and 1 + 2 + 3 = 6
. The next perfect number is 28
, which equals the sum of its proper divisors: 1 + 2 + 4 + 7 + 14
.
暴力搜索:遍历每个数,对每个数,找到所有的因子,检查和。
When you are prompted in the console to “Select the test groups you wish to run,” enter 0 for “None” and the program will instead proceed with the ordinary main
function.
Q1. Roughly how long did it take your computer to do the search? How many perfect numbers were found and what were they?
It would be more convenient to run several time trials in sequence and have the program itself measure the elapsed time. The SimpleTest framework can help with this task, so let’s take a detour there now.
SimpleTest:
TIME_OPERATION
: 最好10-60s
For now, focus on use of STUDENT_TEST
, EXPECT
, EXPECT_EQUAL
, and TIME_OPERATION
, as these are the features you will use in Assignment 1.
TIME_OPERATION
Q2. Make a table of the timing results for
findPerfects
that you observed. (old-school table of text rows and columns is just fine)
To visualize the trend, it may help to sketch a plot of the values (either by hand or using a tool like https://www.desmos.com/calculator).
Q3. Does it take the same amount of work to compute
isPerfect
on the number 10 as it does on the number 1000? Why or why not? Does it take the same amount of work forfindPerfects
to search the range of numbers from 1-1000 as it does to search the numbers from 1000-2000? Why or why not?
the first four perfect numbers pop out in a jif (6, 28, 496, 8128
) because they are encountered early in the search, but that fifth one is quite a ways off – in the neighborhood of 33 million.
Q4. Extrapolate from the data you gathered and make a prediction: how long will it take
findPerfects
to reach the fifth perfect number?
Here are some further explorations to do with SimpleTest:
divisorSum
by erroneously initializing total
to 1 instead of zero. Rebuild and run the tests again.STUDENT_TEST
case that calls isPerfect
on a few different negative inputs.Q5. Do any of the tests still pass even with this broken function? Why or why not?
However, there is a neat little optimization that can significantly streamline the process.The function divisorSum
runs a loop from 1
to N
to find divisors, but this is a tad wasteful, as we actually only need to examine divisors up to the square root of N
.In other words, we can take advantage of the fact that each divisor that is less than the square root is paired up with a divisor that is greater than the square root.
You are to implement a new function smarterSum
:long smarterSum(long n)
that produces the same result as the original divisorSum
but uses this optimization to tighten up the loop.
After adding new code to your program, your next step is to thoroughly test that code and confirm it is bug-free.Because you already have the vetted function divisorSum
at your disposal, a clever testing strategy uses EXPECT_EQUAL
to confirm that the result from divisorSum(n)
is equal to the result from smarterSum(n)
.
Q6. Explain your testing strategy for
smarterSum
and how you chose your specific test cases that lead you to be confident the function is working correctly.
Now with confidence in smarterSum
, implement the following two functions that build on it:
bool isPerfectSmarter(long n)
void findPerfectsSmarter(long stop)
The code for these two functions is very similar to the provided isPerfect
and findPerfects
functions, basically just substituting smarterSum
for divisorSum
. Again, you may copy-paste the existing implementations, and make small tweaks as necessary.
Now let’s run time trials to see just how much improvement we’ve gained.
Q7. Record your timing results for
findPerfectsSmarter
into a table.
Our previous algorithm grew at the rate N^2
, while this new version is N√N, since we only have to inspect √N divisors for every number along the way.
Q8. Make a prediction: how long will
findPerfectsSmarter
take to reach the fifth perfect number?
A Mersenne number is a number that is one less than a power of two, i.e., of the form 2k-1
for some integer k
. A Mersenne number that is prime is called a Mersenne prime. The Mersenne number 25-1
is 31 and 31 is prime, making it a Mersenne prime.
Mersenne primes are quite elusive; the most recent one found is only the 51st known and has almost 25 million digits! Verifying that the found number was indeed prime required almost two weeks of non-stop computing.
Back in 400 BCE, Euclid discovered an intriguing one-to-one relationship between perfect numbers and the Mersenne primes. Specifically, if the Mersenne number 2k-1
is prime, then 2(k-1) * (2k-1)
is a perfect number. The number 31 is a Mersenne prime where k = 5, so Euclid’s relation applies and leads us to the number 24 * (25-1) = 496
which is a perfect number. Neat!
The final task of the warmup is to leverage the cleverness of Euclid to implement a blazingly-fast alternative algorithm to find perfect numbers that will beat the pants off of exhaustive search. Buckle up!
Your job is to implement the function:
long findNthPerfectEuclid(long n)
The exhaustive search algorithm painstakingly examines every number from 1 upwards, testing each to identify those that are perfect. Taking Euclid’s approach, we instead hop through the numbers by powers of two, checking each Mersenne number to see if it is prime, and if so, calculate its corresponding perfect number. The general strategy is outlined below:
k = 1
.pow
)m
is prime or composite. (Hint: a prime number has a divisorSum
and smarterSum
equal to one. Code reuse is your friend!)m
is prime, then the value is a perfect number. If this is the nth
perfect number you have found, stop here.k
and repeat from step 2.The call findNthPerfectEuclid(n)
should return the n
th perfect number. What will be your testing strategy to verify that this function returns the correct result? You may find this table of perfect numbers to be helpful. One possibility is a test case that confirms each number is perfect according to your earlier function, e.g. EXPECT(isPerfect(findNthPerfectEuclid(n)))
.
1 | STUDENT_TEST("findNthPerfectEuclid(n)"){ |
Q9. Explain how you chose your specific test cases and why they lead you to be confident
findNthPerfectEuclid
is working correctly.
But if you try for the sixth, seventh, eighth, and beyond, you can run into a different problem of scale. The super-fast algorithm works in blink of an eye, but the numbers begin to have so many digits that they quickly exceed the capability of the long
data type.
You should have answers to questions Q1 to Q9 in short_answer.txt
.
Your code should also have thoughtful comments, including an overview header comment at the top of the file.
Soundex算法是一种用于对英语姓氏进行发音编码的算法。它旨在将类似发音的姓氏映射到相同的编码,从而允许在搜索引擎或数据库中快速查找发音相似的姓氏。
Traditional string matching algorithms that use exact match or partial/overlap match perform poorly in this messy milieu of real world data. In contrast, the Soundex system groups names by phonetic structure to enable matching by pronunciation rather than literal character match. This makes tasks like tracking genealogy or searching for a spoken surname easier.
The Soundex algorithm is a coded index based on the way a name sounds rather than the way it is spelled. Surnames that sound the same but are spelled differently, like “Vaska,” “Vasque,” and “Vussky,” have the same code and are classified together.
The Soundex algorithm operates on an input surname and converts the name into its Soundex code. A Soundex code is a four-character string in the form of an initial letter followed by three digits, such as Z452
. The initial letter is the first letter of the surname, and the three digits are drawn from the sounds within the surname using the following algorithm:
Digit | represents the letters |
---|---|
0 | A E I O U H W Y |
1 | B F P V |
2 | C G J K Q S X Z |
3 | D T |
4 | L |
5 | M N |
6 | R |
222025
becomes 2025
).Q10. What is the Soundex code for “Angelou”? What is the code for your own surname?
such as, Angelou(A524
),Curie (C600
) and “O’Conner” (O256
).
Q11. Before writing any code, brainstorm your plan of attack and sketch how you might decompose the work into smaller tasks. Briefly describe your decomposition strategy.
Brainstorm what those missing cases are and then add them. Think about edge cases that could lurk in the extremes or cases that are uniquely distinguished from the provided tests, such as a completely empty string or a string composed of only non-letter characters.
Your goal in writing tests is to enumerate a comprehensive set of tests that brings any bugs in the code to light so you can debug and fix them. Good tests are the key to writing robust software. A great developer is not only a great coder, but also a great tester.
Now run the tests while using the debugger. When you hit the breakpoint, single step through the call to lettersOnly
while watching the debugger’s variables pane to see how the values are changing. This “step-and-watch” approach is the same as you used in the Assignment 0 debugging tutorial.
Using the debugger should help you find the bug —once you understand it, go ahead and fix it!
1 | STUDENT_TEST("Test exclude of punctuation, digits, and spaces"){ |
Now for each subsequent step of the algorithm (encode, coalesce duplicates, and so on), follow the same process:
string soundex(string s)
This console program allows a user to perform a Soundex search on a database of surnames. Implement the following function prototype:
void soundexSearch(string filepath)
The one argument to soundexSearch
is the name of a text file containing a database of names.he program then repeatedly allows the user to enter a surname to look up in the database. For each surname that is entered, the program calculates the Soundex code of the entered name and then finds all names in the database that have a matching Soundex code.
Here are the steps for the program:
Vector<string>
.getLine
from "simpio.h"
will be helpful here.sort()
operation (you can use vec.sort()
where vec
is the name of your vector), and you can print a vector using the <<
operator, e.g. cout << vec << endl;
. Please note that the sort()
function sorts the vector in place and does not return a value.1 | Read file res/surnames.txt, 29409 names found. |
题目指导
Your task in this part of the assignment is to build a tool that models flooding due to sea level rise. (模拟海平面上升带来的洪水)
Your task is to implement a function,
Grid *floodedRegionsIn*(const Grid& terrain, const Vector& sources, double height);
Breadth-first search is typically implemented by using a queue that will process every flooded location.
1 | create an empty queue; |
Once you’re passing all the tests, click the “Rising Tides” option to see your code in action!
Some notes on this problem:
Many personality quizzes – including the one you’ll be building – are based on a what’s called the fivefactor model.:openness, conscientiousness, extraversion, agreeableness, and neuroticism.
As you take the personality quiz and answer questions, the computer updates your scores in these five categories based on how you answer each question. Your score in each category begins at zero and is then adjusted in response to your answers. At the end of the quiz, the program reports the fictional character whose scores are most similar to yours.
![[Pasted image 20240124141708.png]]
Your task is to implement the data processing framework that works behind the scenes to tabulate scores from the personality quiz and determines who the user is most similar to. We’ll use your code to select which questions to ask the user, to determine the user’s score from their quiz answers, to determine the similarity between the user and a fictional character, and to select the fictional character who is most similar to the user.
Question randomQuestionFrom(Set& unaskedQuestions);
Your should implement the function to do the following:
We recommend that you use the randomElement()
function, which is provided in "set.h"
.
If the input set provided to randomQuestionFrom is empty, then you should use the error()
function you saw in Assignment 1 to report an error.
Test.
1 | //-E +N I let others finish what they are saying |
Now, on to what you need to do for this milestone. We’ve written a piece of code that administers a personality quiz, recording the user’s answers in a variable of type Map. Here, the keys represent individual personality quiz questions, and the values represent the result the user typed in.As a refresher, a response of 1 means “strongly disagree,” while a response of 5 means “strongly agree.”
Your task is to write code that uses the data in that Map to determine the user’s OCEAN scores. Specifically, we’d like you to write a function :
Map scoresFrom(const Map& answers);
You should compute scores using the approach described earlier: for each question, weight the factors of that question based on the user’s answer (5 means “strongly agree” and adds doubles the weight of each factor; 4 means “agree” and adds the weight of each factor; 3 means “neutral” and adds zero to the weight of each factor, etc.).
As an example, suppose you were provided these questions and the indicated responses:
![[Pasted image 20240124144657.png]]
You should then ( result ) produce a Map where O maps to +5, E maps to -2, A maps to +1, and N maps to +4. To see where this comes from, let’s focus on the O component. The answer of 5 to “I am quick to understand things” adds 2 × (+1) = +2 to the O score, the answer of 4 to “I go my own way” adds 1 × (+1) = 1 to the O score, and the answer of 1 to “I become overwhelmed by events” adds (-2) × (-1) = +2 to the O score.
To summarize, here’s what you need to do:
Milestone Two
- Implement the scoresFrom function in YouGotHufflepuff.cpp.
- Use the provided tests to confirm that your code works correctly.
- Optionally, but not required: use STUDENT_TEST to add a test case or two of your own!
Some notes on this problem:
1 | for (KeyType var: map) { |
• Remember that the Map’s square bracket operator does automatic insertion if you look up a key that isn’t there. In the context of a Map, this means that if you look up a character that isn’t present in the map, it will automatically insert that key with the value 0.
• Although in the context of a personality quiz the keys in a Map will be some subset of O, C, E, A, and N, your function should work even if we have other keys in the factors maps. (For example, we might repurpose this code to work with a different set of factors than OCEAN.)
1 | Map<char, int> expected = { |
• Do not use the + or += operators to add two Maps. Adding them this way “form a new Map that uses all the key/value pairs from the two input maps, with duplicates resolved arbitrarily.” It does not mean “form a new Map by adding the two Maps’ elements pairwise.”
Once we have these scores, we need to find character is “closest” to the user’s scores. There are many ways to define “closest,” but one of the most common ways to do this uses something called the cosine similarity, which is described below.
![[Pasted image 20240124151254.png]]
Person 2 answered more questions, they had more opportunities to have numbers added or subtracted from each category
To correct for this, you’ll need to normalize the user’s scores. Borrowing a technique from linear algebra, assuming the user’s scores are o, c, e, a, and n, you should compute the value.
![[Pasted image 20240124151355.png]]
Your next task is to implement a function
Map<char,double> normalize(const Map<char,int>& scores);
that takes as input a set of raw scores, then returns a normalized version of the scores.
There is an important edge case you need to handle. If the input Map does not contain any nonzero values, then the calculation we’ve instructed you to do above would divide by zero. (Do you see why?) To address this, if the map doesn’t contain at least one nonzero value, you should use the error() function to report an error.
Milestone Three
- Implement the normalize function in YouGotHufflepuff.cpp.
- Use the provided tests to confirm that your code works correctly.
- Optionally, but not required: use STUDENT_TEST to add a test case or two of your own!
Some notes on this problem:
sqrt
function.One measure we can use to determine how similar they are is to compute their cosine similarity
![[Pasted image 20240124152824.png]]Your next task is to write a function double cosineSimilarityOf(const Map& lhs, const Map& rhs);
that takes as input two sets of normalized scores, then returns their cosine similarity using the formula given above.
Some notes:
1 | struct Person { string name; Map<char,int> scores; }; |
Person mostSimilarTo(const Map<char,int>& scores, const Set<Person>& people);
takes as input the user’s raw OCEAN scores and a Set of fictional people.
This function then returns the Person whose scores have the highest cosine similarity to the user’s scores.
As an edge case, if the people set is empty, you should call error() to report an error, since there’s no one to be similar to in that case.
Similarly, if the user’s scores can’t be normalized – or if any of the people in the input set have scores that can’t be normalized – you should call error() to report an error.
Some notes on this part of the problem:
This assignment has four parts to it, and you have seven days to complete it. We recommend that you start this assignment early and make slow, steady progress throughout the week. Here’s a recommended timetable:
As with many self-similar images, it’s defined recursively:
Your task is to implement a function
1 | void drawSierpinskiTriangle(GWindow& window, |
that takes as input a window in which to draw the Sierpinski triangle, coordinates of the three points defining the corners of that triangle, and the order of that triangle, then draws the Sierpinski triangle of that order.If the order parameter is less than zero, use the error()
function to report an error.
To draw a triangle, you can use this helpful drawTriangle
function that we’ve provided:
1 | void drawTriangle(GWindow& window, |
This function takes in a GWindow
in which to draw the triangle, along with the coordinates of its three corners, then draws a black triangle with the specified corner coordinates.
Part 1 Requirements
- Implement the
drawSierpinskiTriangle
function inSierpinski.cpp
. Don’t forget to handle the case where the order is negative.- Use the “Interactive Sierpinski” option to check your work. In particular, make sure your triangle looks correct after dragging the corner points around and changing the order of the triangle via the slider in the bottom of the window.
Some notes on this problem:
drawTriangle
, which we mentioned earlier in this handout. You don’t need to, say, call the drawPolarLine
function from lecture or anything like that.error()
in the case where the order is less than zero.With the exception of the people in the bottom row, each person splits their weight evenly on the two people below them in the pyramid.
![[Pasted image 20240126151719.png]]
In this question, you’ll explore just how much weight that is. Just so we have nice round numbers here, let’s assume that everyone in the pyramid weighs exactly 160 pounds.
Person A at the top of the pyramid has no weight on her back. People B and C each carry half of person A’s weight, so each shoulders 80 pounds.
Now, let’s consider the people in the third row. Focus on person E. That means she’s supporting a net total of 240 pounds.
Not everyone in that third row is feeling the same amount, though; let’s take person D for example.
Person D therefore ends up supporting
Going deeper in the pyramid, how much weight is person H feeling? Well, person H is supporting
There’s a nice, general pattern here that lets us compute how much weight is on each person’s back:
Write a recursive function
1 | double weightOnBackOf(int row, int col, int pyramidHeight); |
that takes as input the row and column number of a person in a human pyramid, along with the number of rows in the pyramid (its height), then returns the total weight on that person’s back.
(row,col) :
![[Pasted image 20240126153026.png]]
If the provided (row, col) position passed into weightOnBackOf is out of bounds you should use the error()
function to report an error. For example, if you’re asked to look up position (-1, 0), or position (2, 3), you’d always report an error. Position (3, 1) is in-bounds provided that the pyramid height is at least four, but would be out of bounds if the pyramid height was one, two, or three.
Milestone 1 Requirements
- Add at least one test to
HumanPyramids.cpp
to cover a case not tested by the other test cases. This is a great opportunity to check that you understand the problem setup.- Implement the
weightOnBackOf
function from HumanPyramids.cpp to report the weight on the back of the indicated person in a human pyramid. Test your solution thoroughly before proceeding, noting that your implementation will be slow if you look deep in the pyramid. Don’t forget to callerror()
if the input coordinates are out of bounds!
Some notes on this first milestone:
double
type works in C++, the values that you get back from your program in large pyramids might contain small rounding errors that make the value close to, but not exactly equal to, the true value (we’re talking about very small errors – less than a part in a thousand). Don’t worry if this happensIt takes a long time to say how much weight is on the back of the person in row 30, column 15. Why is this?
Think about what happens if we call weightOnBackOf(30, 15)
. (For simplicity of exposition in this section, we’re going to leave off the height parameter.) This makes two new recursive calls: one to weightOnBackOf(29, 14)
, and one to weightOnBackOf(29, 15)
. This first recursive call in turn fires off two more: one to weightOnBackOf(28, 13)
and another to weightOnBackOf(28, 14)
. The second recursive call then calls weightOnBackOf(28, 14)
and weightOnBackOf(28, 15)
.
This means that there’s a redundant call being made to weightOnBackOf(28, 14)
.For example, calling weightOnBackOf(30, 15)
makes over 600,000,000 recursive calls!
There are many techniques for eliminating redundant calls. One common approach is memoization . Intuitively, memoization works by making an auxiliary table keeping track of all the recursive calls that have been made before and what value was returned for each of them. Then, whenever a recursive call is made, the function first checks the table before doing any work. If the the recursive call has already been made in the past, the function just returns that stored value.
Wrapper Function:
1 | Ret functionRec(Arg a, Table& table) { |
Some notes on this milestone:
weightOnBackOf
function. Instead, make it a wrapper around a new function that actually does all the recursive magic.In conversational English, emphasizing different words in a sentence can change its meaning. For example, the statement
“what are YOU doing?”
places the emphasis on the person being spoken to, with the connotation being “what are you, as opposed to the other people here, doing?” On the other hand, the statement
“what are you DOING?”
puts the emphasis on “doing,” with the connotation that the speaker is exasperated or surprised by the listener’s behavior. Contrast that with
“WHAT ARE YOU DOING?,”
which sounds like someone is shouting, or
“what ARE you doing?,”
which reads as a request for clarification about what the listener happens to be doing at the moment.
All in all, there are sixteen different ways we could emphasize the words in this sentence:
Your task is to write a function:
1 | Set<string> allEmphasesOf(const string& sentence); |
that takes as input a string
containing a sentence, then returns a Set
containing all the ways of capitalizing the words in that sentence.
The input to this function is a string representing a sentence. It’s probably a lot easier to work not with a single string representing a whole sentence, but with a Vector<string>
representing all the pieces of that sentence.
For this purpose, we’ve provided you with
1 | Vector<string> tokenize(const string& sentence); |
which takes as input a sentence, then returns a Vector<string>
representing the individual units that make up that sentence.
For example, given the input
1 | Quoth the raven, "Nevermore." |
the tokenize
function would return the Vector<string>
shown here:
1 | [Quoth] [ ] [the] [ ] [raven] [,] [ ] ["] [Nevermore] [.] ["] |
![[Pasted image 20240126214846.png]]
These strings are formed by considering all possible ways of rendering each word in both upper-case and lower-case, keeping the rest of the input string (spaces, commas, periods, etc.) unchanged.
You should consider a word to be any string that starts with a letter.
######## Part Three Requirements
allEmphasesOf
in WhatAreYouDoing.cpp
so that it returns a set of all the different ways the words in the input sentence can be capitalized. The non-word strings in the sentence should be included in the output unchanged from the original.Some notes on this problem:
tokenize
exactly once to convert the input string into a sequence of tokens; there’s no need to call tokenize
repeatedly.allEmphasesOf
on the strings "Happy birthday!"
, "HAPPY BIRTHDAY!"
, and "hApPy BiRtHdAy!"
should all be the same.isalpha
function from the header file <cctype>
to do this. You don’t need to handle the case where a string has letters in it but not in the first position.toUpperCase
and toLowerCase
functions from "strlib.h"
to convert a string to upper-case or lower-case. Be careful – these functions don’t modify their arguments, and instead return new strings with the case of the letters changed.":-)"
) or a series of numbers ("867-5309"
).stringSplit
function or TokenScanner
type to break the original string apart into words. Instead, rely on our handy tokenize
function, which we’ve written for you specifically for this assignment.toUpperCase
or toLowerCase
on non-word tokens. If your recursion branches, making one call where the punctuation symbol is converted to upper case and another call where the punctuation symbol is converted to lower case, then it will do a lot of unnecessary work regenerating the same strings twice. (Do you see why?)string
type is is pretty bad when it comes to handling non-English characters. As a result, if you feed perfectly reasonable statements like “नमस्ते” or “ٱلسَّلَامُ عَلَيْكُمْ” or “你好” into the tokenize
function, the results may be completely wrong.You’d like to schedule her hours in a way that gets her to produce the most value.
For each shift(上班伦茨), you have an estimate of how much money the employee would produce if she worked that shift. Ideally, you’d have her work every shift, but alas, that isn’t possible. There are two restrictions. First, the employee has a limited number of hours she’s allowed to work each week. Second, you can’t assign the employee to work concurrent shifts.Given these restrictions, what shifts should you assign to your new hire?
For example, imagine that your new employee is available for twenty hours each week. Here’s the different shifts she could take, as well as how much value (measured in dollars) she’ll produce in each:
Your task is to write a function
1 | Set<Shift> highestValueScheduleFor(const Set<Shift>& shifts, int maxHours); |
that takes as input a Set
of all the possible shifts, along with the maximum number of hours the employee is allowed to work for, then returns which shifts the employee should take in order to generate the maximum amount of value.
Here, Shift
is a struct
containing information about when the shift is (what day of the week it is, when it starts, and when it ends). It’s defined in the header file Shift.h
.
Chances are that you won’t need to read any of the fields of this struct
, and that instead you’ll want to use these functions from Shift.h
:
1 | int lengthOf(const Shift& shift); // Returns the length of a shift. |
Now, let’s talk strategy. We recommend approaching this problem in the following way.
Select, however you’d like, some shift out of shifts
. If the employee can’t take this shift, either because it overlaps with a shift she’s already been assigned or because it would exceed her total hour limit, then the only option is to not give the employee that shift.
Otherwise, there are two options: you could either give the employee the shift, or not give the employee the shift. One of these two options will be better than the other, or they’ll be tied for how much value is produced.You won’t know which one is better until you try them out, so (recursively) explore both options and report whichever one leads to more value.
As you’re working on this one, make sure to test thoroughly! Craft smaller examples (say, with between zero and five shifts) and see how your code does.
Think of all the tricksy cases that might arise – long shifts that aren’t worth much, short shifts worth a lot, overlapping shifts that are equally good, etc. – and make sure your code handles those cases well.
######## Problem Four Requirements
highestValueScheduleFor
using the strategy we outlined above. Select some shift and think about what happens if you assign the employee that shift (if that’s even an option) and what happens if you don’t assign the employee that shift. Take whichever option leads to a better result, and report the set of shifts you’d pick if you chose that route. Use the error()
function to report an error if numHours < 0
.STUDENT_TEST
. Ideally, test something not already covered.Some notes on this problem:
maxHours
is negative you should call error()
to report an error.There are three problems in this assignment. The first one is a warmup, and the next two are the main coding exercises.
“What is my program doing, and why is that different than what I intended?”
We can visualize this problem by drawing a circle for each person, then drawing lines connecting pairs of people who would be comfortable working with one another. Here’s an example of what that might look like:
We can encode this diagram as a Map<string, Set<string>>
, where each key in the map represents a person and each value represents the set of people they could potentially partner with.
we need to decide which people we will actually partner together.we can do better than all three of the above options. Here’s a better matching:
Here, we pair A with B, pair C with D, pair E with F, and pair G with H. Such an arrangement is called a perfect matching.
More generally, a perfect matching is a way to pair people off so that
However, depending on who wants to work with one another, it’s not always possible to find perfect matchings.Take a minute to convince yourself why a perfect matching isn’t possible in each case.
Your first task is to write a function
1 | bool hasPerfectMatching(const Map<string, Set<string>>& possibleLinks, |
that takes as input a group of people and the possible links between them, then determines whether a perfect matching exists.If one does, the function should return true
and set the matching
outparameter to hold one such perfect matching. If one doesn’t, the function should return false
, and the contents of matching can be whatever you want them to be.
The Pair
type represents a pair of strings, where the order of the strings doesn’t matter.
At each point in time,You’ll find either that everyone is paired off, or that there’s someone who isn’t paired.
######## Milestone One Requirements
STUDENT_TEST
for hasPerfectMatching
to Matchmaker.cpp
. This is a great way to confirm that you understand what the function you’ll be writing is supposed to do.hasPerfectMatching
function in Matchmaker.cpp
.Some notes and hints on this problem:
possibleLinks
are symmetric: if person A is a possible match for person B, then person B is also a possible match for person A. You can assume we’ll never call this function on an input where that isn’t the case.possibleLinks
who isn’t linked to anyone, meaning that they aren’t comfortable working with anyone. In that case, there’s no perfect matching.matching
outparameter. Once you’re sure that your code is always producing the right answer, update it so that you actually fill in matching
. Doing so shouldn’t require too much code, and it’s way easier to add this in at the end than it is to debug the whole thing all at once.matching
is empty when the function is called.false
, the final contents of matching
don’t matter, though we suspect your code will probably leave it blank.Map
and don’t care which key you get, use the function map.firstKey()
. To grab a key out of a Set
, use set.first()
. (Which of these functions, if any, you use in this function are up to you.)const
reference, you’re free to make extra copies of the arguments or to set up whatever auxiliary data structures you’d like. If you find you’re “fighting” your code – an operation that seems simple is taking a lot of lines – it might mean that you need to change your data structures.Indeed, there are two major shortcomings of this approach.
Unlike before, though, each one of those possible pairs will have an associated weight representing how good a match they would be.
![[Pasted image 20240203153346.png]]
This pairs off A with E and B with C for a total weight of 11. This is the highest weight of any matching here, and so it’s called a maximum-weight matching.
Notice that a maximum-weight matching might not always be a perfect matching.
Your next task is to implement a function
1 | Set<Pair> maximumWeightMatching(const Map<string, Map<string, int>>& possibleLinks); |
that takes as input a weighted preference graph, then returns a matching in that graph with the maximum possible weight.
possibleLinks["A"]["E"]
gives the weight of the potential link between A and E (here, 5),
To repeat an important point from above – a maximum weighted matching might not necessarily be a perfect matching. This means that your code must allow for the case where a particular person is not matched with anyone else.
######## Milestone Two Requirements
STUDENT_TEST
for maximumWeightMatching
to Matchmaker.cpp
. This is a great way to confirm that you understand what the function you’ll be writing is supposed to do.maximumWeightMatching
function in Matchmaker.cpp
.possibleLinks
between two people who aren’t linked, you’ll see a weight of 0 even though no such link exists. However, there may, independently, be existing links between people of weight 0.possibleLinks
map, indicating two people that would be actively unhappy to be matched with one another. No maximum-weight matching will ever use a negative-weight edge. Depending on how you approach this problem, you may find that you need to add code to account for this case, or you may not.