Module 1 Probability
A little bit of C++
First some tips on C++ programming (valid in this course only!!), based on experiences from earlier years:
- Use pointers sparingly (if at all), and never ever ever use new and delete.
- Learn to use std::vector or std::list.
- Concentrate on solving the problem you are asked to solve, i.e. do the maths and turn the maths into short clean mathematically looking code.
- The tasks in this course have fairly low programming complexity, so avoid too much generalisation.
- Don't think in terms of objects and classes until at least after you know for certain how to solve the problem.
- Don't waste time writing complicated interfaces with getters and setters.
- Good variable names can replace many lengthy comments, but when appropriate you should use short variable names as a mathematican would: E.g. indices are called "i" and "j", not "myStochasticMatrixRowIndex" and "myStochasticMatrixColumnIndex"
If you find yourself writing hundreds of lines of code for a short assignment, then you are most likely overthinking the problem, and not doing the kind of work which will lead to a successful outcome in this course.
Setup
Now the crash course on C++. We will be using
#include <vector>
#include <functional>
#include <iostream>
#include <algorithm>
#include <assert.h>
#include <numeric>
at various times, so we include them and we should be ready to get started. An alternative to std::vector is std::list which can be included as
#include<list>
Inline functions
A function can be many different things in C++ (more about that in Module 6). We are of course able to write a function which squares a number
// outside main()
int square1(int x) {
return x*x;
}
// inside main()
std::cout << square1(10) << std::endl;
But such a function ccan also be hidden inside an object
// outside main()
class square2class {
public:
int operator()(int x) {
return x*x;
};
};
// inside main()
square2class square2;
std::cout << square2(10) << std::endl;
We can assign functions to variables and move them about
std::function<int(int)> square3 = square1;
std::cout << square3(10) << std::endl;
square3 = square2;
std::cout << square3(10) << std::endl;
We can also define inline functions, which do not have any name, using lambdas.
std::function<int(int)> square4 = [](int x)->int {
return x*x;
};
std::cout << square4(10) << std::endl;
The syntax for a lambda expression is
[captures] (parameters) -> return type {
function body
};
Captures declare how out of scope variables should be passed to the function. We will ignore that for now.
An important feature of C++ is the ability to deduce a lot about types without your help. This is a shorter version of square5:
auto square5 = [](auto x) {
return x*x;
};
std::cout << square4(10) << std::endl;
The auto-keyword tells C++ to deduce type information. It doesn't always work as we would like, but that is always our own fault. Now, the really neat thing about the above declaration, is that we now have not one, but many functions. You can also do
std::cout << square5(10.1f) << std::endl;
and C++ automatically creates a float version of square5. Any type (even your own) which has operator * will be able to use square5. Other aspects of functions in general, and lambdas in particular, will be discussed more carefully in Module 6.
Vectors
Vectors (or lists) will be our favourite container for the first and last part of this course. We would like to write vectors (or lists) to the console as easily as we did in Python. It is common to use std::cout to write to the console in C++, so we stick with that. A few functions with a little bit of code is now required (we can skip the implementation):
template <typename T> std::ostream&
myout(std::ostream& os, const T& container)
template <typename T> std::ostream&
operator<<(std::ostream& os, const std::vector<T>& v)
template <typename T> std::ostream&
operator<<(std::ostream& os, const std::list<T>& v)
Lets declare two vectors v and w and a list l, consisting of $30$ integers each, all set to zero
std::vector<int> v(30, 0);
std::vector<int> v(30, 0);
std::vector<int> w(30, 0);
std::list<int> l(30, 0);
let us print and see what we got
std::cout << v << std::endl;
std::cout << w << std::endl;
std::cout << l << std::endl;
Lets put the numbers $1,\cdots, 30$ in $v$ and $l$ by iterating over the containers (this is how C++ does Python for-loop)
int i = 1;
for(auto& o : v) {
o = i;
i++;
}
i = 1;
for(auto& o: l) {
o = i;
i++;
}
We would like to manipulate vectors similar to what we did with lists in python. Now, C++ has features which are similar to functions like map, filter and reduce, but let us define our own to get pythonese syntax. We ignore the implementaiton of filter and reduce for now.
auto map = [](auto fun, auto& cnt) {
auto retval = cnt;
for (auto& elem : retval) {
elem = fun(elem);
}
return retval;
};
auto filter = [](auto pred, auto& cnt) {};
auto reduce = [](auto fun, auto cnt) {};
Calling
ls = map(f, ls);
ls = filter(p, ls);
res = reduce(f, ls);
on a list (or vector) ls in C++, is equivalent to
ls = list(map(f, ls))
ls = list(filter(p, ls))
res = reduce(f, ls)
in Python. In other words, our C++ versions creates copies of lists. We will think more carefully about implementing these functions in C++ in Module 6.
We can now initialise $w$ with values $1,2,3,\cdots$.
w = map([](int x) {
static int i = 0;
i++;
return x+i;
}, w);
Note that you do not need to pass a lambda to map. Any type of function would do, for example
// outside main
class Counter {
public:
int operator(int x) {
static int c = 0;
c++;
return c;
};
};
// inside main
Counter counter;
w = map(counter, w);
Lets remove numbers in $w$ which are bigger than $9$.
w = filter([](auto x) {
return x < 10;
}, w);
Finally we solve the problem with lists of squares that we did at the end of the python tutorial.
std::vector<int> squares(21, 0);
squares = map([](int x) {
static int i = -1;
i++;
return x + i;
}, squares);
squares = map([](int x) {
return x*x;
},squares);
std::vector<int> oddsquares = filter([](int x) {
return x % 2 == 1;
}, squares);
std::vector<int> altsquares = map([](int x) {
static int i = -1;
i++;
if (i % 2 == 0) return x;
else return -x;
}, squares);
std::vector<int> altoddsquares = map([](int x) {
static int i = -1;
i++;
if (i % 2 == 0) return x;
else return -x;
}, oddsquares);
int sumsquare = reduce([](int x, int y) {
return x + y;
}, altsquares);
int oddsum = reduce([](int x, int y) {
return x + y;
},altoddsquares);
You are now ready to start doing some functional programming in C++.