COMP6771 - Advanced C++ Programming
Weekly Exercise - Week 3
With my own solution (might be incorrect)
There are many ready-made components in the STL that make common programming tasks extremely easy.
When it comes to using containers like std::vector
and algorithms like std::sort
, many useful things can be found in the <functional>
header.
Your task is to complete the sort_descending()
function in src/3.1/sort_descending.cpp
.
You can find its documentation in src/sort_descending.h
.
You also need to write at least two tests in src/3.1/sort_descending.test.cpp
to make sure your code is correct!
C++auto sort_descending(std::vector<int>& numbers) -> void
{
auto des_order = [](int a, int b) -> bool {
return a > b;
};
std::sort(numbers.begin(), numbers.end(), des_order);
}
A powerful aspect of std::sort
is being able to change the meaning of "sorted" by supplying a different comparator to the sort.
In olden times, this would be done by crafting a separate function each time and supplying a function pointer.
As of C++11, this is now all doable inline with lambda
functions.
How might we use a lambda function to sort a vector of strings by the number of vowels in each string? Furthermore, if the number of vowels are equal, then the strings should be lexicographically sorted.
In src/3.2/vsort.cpp
, there is a stub for a function that accepts a vector of strings and sorts it in ascending order according to the above rules.
You need to implement this function using a lambda as the custom comparator. You should also write at least two tests to verify that your code is correct.
C++auto vsort(std::vector<std::string>& vs) -> void
{
auto judge_vowel = [](const char& c) {
std::vector<char> targets = {'a', 'e', 'i', 'o', 'u', 'A', 'E', 'I', 'O', 'U'};
return std::find(targets.begin(), targets.end(), c) != targets.end();
};
auto count_vowel = [&judge_vowel](const std::string &str) {
return std::count_if(str.begin(), str.end(), judge_vowel);
};
auto sort_dis = [&count_vowel](const std::string& a, const std::string& b) -> bool {
if (count_vowel(a) != count_vowel(b)) {
return count_vowel(a) < count_vowel(b);
}
return a < b;
};
std::sort(vs.begin(), vs.end(), sort_dis);
}
In src/3.3/marks.txt
there is a newline separated file of anonymous marks from a previous offering of this course!
To show off your budding C++ prowess, you have decided to calculate the median, average, highest, and lowest mark from this newline-separated list using nothing but the C++ Standard Library. In particular:
In your enthusiasm, you decided to code your program in a modular style. So, in addition to a calculate_stats()
function, you also made a separate function that loads in the marks from a file!
In reflection, you remembered your old Professor's words, and decided to write at least two tests overall to make sure your code is correct.
Both functions should be coded in src/3.3/stats.cpp
, but more documentation is in src/stats.h
. Your tests should be implemented in src/3.3/stats.test.cpp
.
C++auto read_marks(const std::string& path) -> std::vector<int>
{
std::ifstream file(path);
if (!file.is_open()) {
std::cerr << "Error: Could not open file " << path << std::endl;
return {};
}
std::vector<int> marks;
std::string line;
while(std::getline(file, line)) {
std::istringstream iss(line);
int mark;
while (iss >> mark) {
marks.push_back(mark);
}
}
file.close();
return marks;
}
auto calculate_stats(const std::vector<int>& marks) -> stats
{
stats result;
result.highest = *std::max_element(marks.begin(), marks.end());
result.lowest = *std::min_element(marks.begin(), marks.end());
auto sum = static_cast<double>(std::accumulate(marks.begin(), marks.end(), 0));
result.avg = static_cast<int> (sum);
auto temp_vec = marks;
std::sort(temp_vec.begin(), temp_vec.end());
if (temp_vec.size() %2 == 1) {
auto median_index = temp_vec.size() / 2;
result.median = temp_vec.at(median_index);
}
else {
auto median_index = temp_vec.size() / 2;
result.median = (temp_vec.at(median_index) + temp_vec.at(median_index - 1)) / 2;
}
return result;
}
std::map
is one of the Standard Library's most widely used types (owing mainly to the fact that the need for an associative array appears in many places).
One not-so-common but still vitally important operation on a map is to invert its keys and values. For example,
cppauto m = std::map<std::string, int>{{"hi", 42}, {"bob", 6771}};
the inversion of m
would be
cppauto n = std::map<int, std::string>{{42, "hi"}, {6771, "bob"}};
As you can see, the keys have been swapped with their values and vice-versa.
Your task is to implement the invert()
operation for a map of std::string
to int
, namely std::map<std::string, int>
.
However, rather than a simple inversion, there is an added constraint.
If, after inversion, the same key appears more than once (which can happen due to values having different keys in the original map), only the key/value pair with the longest string should ultimately be in the resulting map.
For example, for the map m
,
cppauto m = std::map<std::string, int> {
{"a", 6771},
{"ab", 6771},
{"abc", 6771},
{"xyz", 6772},
};
it's inversion should be:
cppauto n = std::map<int, std::string> {
{6771, "abc"},
{6772, "xyz"},
};
In src/3.4/invert.cpp
, implement the invert
operation and in src/3.4/invert.test.cpp
, write at least three tests to ensure your code is correct!
C++auto invert(const std::map<std::string, int>& mp) -> std::map<int, std::string>
{
auto imap = std::map<int, std::string> {};
for (const auto& p : mp) {
if (imap.find(p.second) != imap.end()) {
if (imap.at(p.second).size() < p.first.size()) {
imap.at(p.second) = p.first;
}
}
else {
imap.insert({p.second, p.first});
}
}
return imap;
}
The various containers in the C++ Standard Library have different performance characteristics for different operations. For example, linear search in std::vector
is presumed to be faster than in std::list
, but potentially slower than in a std::deque
.
In this activity, we will put these assumptions to the test with a microbenchmark!
In src/3.5/microbenchmark.h
, there is documentation for a function that is to run a microbenchmark for a list, deque, and vector simultaneously. This function should return a timings
structure, which is also in the provided header file.
Complete this function in src/3.5/microbenchmark.cpp
. We have provided a random-number generator in there for you to use.
When you are done, you should also write at least one test in src/microbenchmark.test.cpp
. You may wish to use the test as a way to verify/test your assumption about which container is the fastest for linear lookup...
Though not mandatory, you may also want to write a small program that uses your benchmarking code and does experiments to see under what conditions which container is faster. If you do so, it may help to consider these questions:
Feel free to discuss your answers to these questions with your tutor.
Hint: a useful library that deals with all things related to time (and dates!) is std::chrono
.
C++auto microbenchmark_lookup(int n_repetitions, int n_elems) -> timings
{
timings time_record = {0.0, 0.0, 0.0};
for (int i = 0; i < n_repetitions; i++) {
std::list<int> ilist;
std::vector<int> ivec;
std::deque<int> ideq;
for (int j = 0; j < n_elems; j++) {
int random_num = rand();
ilist.push_back(random_num);
ivec.push_back(random_num);
ideq.push_back(random_num);
}
auto check_num = rand();
const auto list_start = std::chrono::steady_clock::now();
const auto ilist_find = std::find(ilist.begin(), ilist.end(), check_num);
const auto list_end = std::chrono::steady_clock::now();
const std::chrono::duration<double> list_duration = list_end - list_start;
time_record.list_avg_time += list_duration.count();
const auto ivec_start = std::chrono::steady_clock::now();
const auto ivec_find = std::find(ivec.begin(), ivec.end(), check_num);
const auto ivec_end = std::chrono::steady_clock::now();
const std::chrono::duration<double> vec_duration = ivec_end - ivec_start;
time_record.vec_avg_time += vec_duration.count();
const auto ideq_start = std::chrono::steady_clock::now();
const auto ideq_find = std::find(ideq.begin(), ideq.end(), check_num);
const auto ideq_end = std::chrono::steady_clock::now();
const std::chrono::duration<double> deq_duration = ideq_end - ideq_start;
time_record.deque_avg_time += deq_duration.count();
(void)ilist_find;
(void)ivec_find;
(void)ideq_find;
}
time_record.list_avg_time /= n_repetitions;
time_record.vec_avg_time /= n_repetitions;
time_record.deque_avg_time /= n_repetitions;
return time_record;
}
Consider the following code:
cpp#include <iostream>
#include <vector>
int main() {
std::vector<int> temperatures = {32, 34, 33, 28, 35, 28, 34};
for (int i = 0; i < temperatures.size(); ++i) { // (*)
std::cout << temperatures.at(i) << " ";
}
std::cout << "\n";
for (const auto &temp : temperatures) { // (^)
std::cout << temp << " ";
}
std::cout << "\n";
for (auto iter = temperatures.cbegin(); iter != temperatures.cend(); ++iter) { // (&)
std::cout << *iter << " ";
}
std::cout << "\n";
}
Answer the following questions:
temperatures
vector.temperatures
in reverse. Which loop in this code is easiest to change and why?cbegin
and cend
to rbegin
and rend
.temperatures.begin()
and temperatures.rend()
?end()
or rend()
is "one-past-the-end", and so is never dereferenceable, unlike begin()
.begin()
== rend()
since the beginning of a range is the end of its reversal.begin()
returns an iterator
whereas rend()
returns reverse_iterator
. Everything else is the same.rend()
would only compare equal to begin()
if temperatures
was empty.Answer the following questions:
cpp#include <vector>
int main() {
const std::vector<int> v;
auto iter = v.begin();
}
What iterator type and category is iter
?
const_iterator
/ contiguousconst_iterator
/ contiguouscpp#include <vector>
int main() {
const std::vector<int> v;
auto iter = v.cbegin();
}
What iterator type and category is iter
?
const_iterator
/ contiguousconst_iterator
/ contiguouscpp#include <vector>
int main() {
const std::vector<int> v;
auto iter = (*v.begin())++;
}
What iterator type and category is iter
?
const_iterator
/ contiguousconst_iterator
/ contiguouscpp#include <list>
int main() {
std::list<int> li;
auto iter = li.cbegin();
}
What iterator type and category is iter
?
const_iterator
/ bi-directionalcpp#include <forward_list>
int main() {
std::forward_list<int> forward_li;
auto iter = forward_li.cbegin();
}
What iterator type and category is iter
?
const_iterator
/ forwardcpp#include <forward_list>
int main() {
const std::forward_list<int> forward_li;
auto iter = (*forward_li.begin())++;
}
What iterator type and category is iter
?
const_iterator
/ forwarditer
is an int
cpp#include <set>
int main() {
std::set<int> st;
const auto iter = st.begin();
}
What iterator type and category is iter
?
cpp#include <string>
int main() {
std::string s;
auto iter = s.begin();
}
What iterator type and category is iter
?
cpp#include <iterator>
#include <iostream>
int main() {
auto iter = std::istream_iterator<int>(std::cin);
}
What iterator type and category is iter
?
const_iterator
/ inputcpp#include <iterator>
#include <iostream>
int main() {
auto iter = std::ostream_iterator<int>(std::cout, " ");
}
What iterator type and category is iter
?
const_iterator
/ outputAnswer the following quetsions:
cppauto first(const std::vector<int> &v, const int needle) {
for (auto i = v.begin(); i != v.end(); ++i) {
if (*i == needle) {
return i;
}
}
return v.end();
}
What standard algorithm can this code be replaced by?
cppauto second(std::vector<int> &v, std::vector<int>::iterator new_first) {
auto copy = std::vector<int>(v.begin(), new_first);
v.erase(v.begin(), new_first);
return v.insert(v.end(), copy.begin(), copy.end());
}
What standard algorithm can this be replaced by?
cppauto third(std::span<float> floats) {
auto v = std::vector<float>{};
for (auto f : floats) {
v.push_back(f);
}
auto m = std::numeric_limits<float>::max();
for (auto f : v) {
if (f < m) m = f;
}
auto M = std::numeric_limits<float>::min();
for (auto f : v) {
if (M < f) M = f;
}
return std::make_pair(m, M);
}
What sequence of standard algorithms can this reasonably be replaced by?
本文作者:Jeff Wu
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!