In-Class Review II#

This is a review of some of the concepts we covered since the previous midterm.

Important

This set of examples is not exhaustive, so you should go back and read through the notes, run the example codes, and look at my homework solutions.

std::vector#

  1. What header file do we need to include to work with vectors?

    solution

    We need to #include <vector>.

  2. Given std::vector<int> a{4, 7, 2, 9};

    1. Write one line that prints the number of elements in a.

      solution
      std::cout << a.size() << std::endl;
      
    2. Write one line that prints the first element.

      solution
      std::cout << a[0] << std::endl;
      
    3. Write one line that changes the last element to 10.

      solution

      There are a few ways. We could use .back() which returns a reference to the last element:

      a.back() = 10;
      

      We could index it using a.size()-1 (the -1 accounts for the fact that we use 0-based indexing:

      a[a.size()-1] = 10;
      
    4. Write one line that adds 8 to the end of the vector.

      solution

      We use .push_back() for this:

      a.push_back(8);
      
  3. Create an empty vector, vec, of integers, and then add the integers 1, 2, and 3 to it.

    solution
    std::vector<int> vec{};
    vec.push_back(1);
    vec.push_back(2);
    vec.push_back(3);
    

strings and formatting#

  1. Given std::string s = "hello";

    1. Write one line that prints the length of s.

      solution

      We use .size(), just like with vectors:

      std::cout << s.size() << std::endl;
      
    2. Write one line that prints the first character.

      solution

      We index it starting with 0:

      std::cout << s[0] << std::endl;
      
  2. Given std::string first = "Ada"; and std::string last = "Lovelace";, write one line that creates full equal to "Ada Lovelace".

    solution

    We can use the + operator:

    auto full = first + " " + last;
    
  3. Given std::string s = "cat";, write one line that changes it to "cats".

    solution

    The easiest way to do this is via addition, updating s:

    s += "s";
    
  4. How would you use std::format to print out two double precision quantities, \(x\) and \(y\), to make a sentence like "x = 2.0, y = 3.0"?

    solution

    Given:

    double x{2};
    double y{3};
    

    we can do:

    std::cout << std::format("x = {:.1f}, y = {:.1f}\n", x, y);
    

    here the .1f means “one digit after the decimal point and use fixed-point formatting”.

    Important

    When using std::format, we need to include the <format> header and also compile with C++20 support, -std=c++20.

struct#

  1. Consider a point in spherical coordinates, with components for \(r\), \(\theta\), \(\phi\)

    1. Create a struct called SphericalPoint that holds the data for a single point.

      solution
      struct SphericalPoint {
          double r{};
          double theta{};
          double phi{};
      };
      

      Note the ; at the end of the struct definition.

    2. Initialize a SphericalPoint named p for the point \((r, \theta, \phi) = (1, \pi/4, \pi/8)\)

      solution

      The cleanest way to do this is:

      SphericalPoint p{.r=1.0,
                       .theta=std::numbers::pi/4.0,
                       .phi=std::numbers::pi/8.0};
      

      Where we use std::numbers to get the value of \(\pi\).

      Important

      As with std::format, this is a C++20 feature, so we need to enable C++20 support when compiling.

    3. We can get the \(x\), \(y\), and \(z\) components as:

      \[\begin{split}x &= r \sin\theta \cos\phi \\ y &= r \sin\theta \sin\phi \\ z &= r \cos\theta\end{split}\]

      Show the lines of code that compute this from p

      solution

      We use the . operator to access the components of our struct. Our code can be:

      double x = p.r * std::sin(p.theta) * std::cos(p.phi);
      double y = p.r * std::sin(p.theta) * std::sin(p.phi);
      double z = p.r * std::cos(p.theta);
      
  2. Consider the struct:

    struct Rectangle {
        double width{};
        double height{};
    };
    

    Create a Rectangle named box with width 4.0 and height 2.5. Then write an expression that computes its area.

    solution

    After we create our Rectangle, we access the components using the . operator:

    Rectangle box{.width=4.0, .height=2.5};
    double area = box.width * box.height;
    

Functions#

  1. Write a function square that takes a double named x and returns x*x.

    solution
    double square(double x) {
        return x * x;
    }
    
  2. Write a function is_zero that takes an int n and returns true if n is zero.

    solution

    Our return type will be bool in this case:

    bool is_zero(int n) {
        bool result{};
        if (n == 0) {
            result = true;
        }
        return result;
    }
    

    here, bool result{}; initializes result to false.

  3. Write a function print_hello that takes no arguments and prints "Hello".

    solution

    When a function does not return a value, we use the void keyword:

    void print_hello() {
        std::cout << "Hello" << std::endl;
    }
    
  4. Write a function add that takes two integers and returns their sum.

    solution
    int add(int x, int y) {
        return x + y;
    }
    
  5. Imagine we write a function doit that takes a function as an argument. We want to pass in a function like:

    int add(int x, int y) {
        return x + y;
    }
    

    How would we write the argument for this in our function doit?

    solution

    We use std::function to define the function interface. In the < >, we need to indicate the return type and the types of the arguments. For this case, it would be <int(int, int)> (we take two int and return an int).

    A possible implementation of doit is:

    int doit(std::function<int(int, int)> f, int a, int b) {
        return f(a, b);
    }
    

    Also remember you need to #include <functional>.

  6. Explain difference between a function that looks like:

    void f1(double x) {
        // do stuff
    }
    

    and

    void f2(double &x) {
        // do stuff
    }
    
    solution

    Function f1 takes x as a copy—this is the default behavior in C++. In contrast, f2 takes x as a reference—this means that it has access to the memory where the argument in the calling function lives.

    The difference here is that any changes to x done in the function body would not be seen by the caller when using f1, but they would see the changes when using f2.

Conditionals#

  1. Given int x;, write an if/else statement that prints "even" if x is even and "odd" otherwise.

    solution
    if (x % 2 == 0) {
        std::cout << "even" << std::endl;
    } else {
        std::cout << "odd" << std::endl;
    }
    
  2. Given double grade;, write an if statement that prints "pass" if the grade is 60 or higher.

    solution
    if (grade >= 60) {
        std::cout << "pass" << std::endl;
    }
    

    Notice that we use >= and not just > to ensure that 60 is a pass.

  3. Given std::string s;, write an if statement that prints "empty" if the string is empty.

    solution
    if (s.size() == 0) {
        std::cout << "empty" << std::endl;
    }
    

    alternately we could do:

    if (s.empty()) {
        std::cout << "empty" << std::endl;
    }
    
  4. Write an if-test that checks if an integer a is greater than 0 and a boolean test is true.

    solution

    We use the && operator for “and”:

    if (a > 0 && test) {
        // do stuff
    }
    

    If we wanted “or”, we would use ||.

Loops#

  1. Write a for loop that prints the integers 0 through 4.

    solution
    for (int i = 0; i < 5; ++i) {
        std::cout << i << std::endl;
    }
    
  2. Given std::vector<int> a{1,2,3,4};, write a loop that prints each element of a.

    solution
    for (auto e : a) {
        std::cout << e << std::endl;
    }
    
  3. What is the difference in the way that these two loops work, given a vector vec?

    for (auto e : vec) {
        // do stuff
    }
    

    vs.

    for (auto &e : vec) {
        // do stuff
    }
    
    solution

    This is essentially the same as our f1 vs. f2 function example above. The second case uses a reference to access the element of the vector (&e). This means that it is working directly on the memory in vec, so any changes we do to e in the second loop example are reflected back in vec.

  4. Given std::string s = "hello";, write a loop that prints each character on its own line.

    solution

    We can loop over characters using a ranged-for loop, just like with a vector.

    for (auto c : s) {
        std::cout << c << std::endl;
    }
    
  5. Given int n = 5;, write a while loop that prints 5 4 3 2 1.

    solution
    int n{5};
    while (n > 0) {
        std::cout << n << " ";
        --n;
    }
    

    The important bit is to remember to decrement n inside of the loop. We can do either --n or n--.

  6. What is the difference between break and continue when used inside a loop?

    solution

    break exits out of the loop completely, while continue will jump to the next value in the loop.

Putting it together#

  1. Given std::vector<int> a{1,2,3,4,5};, write 1 to 3 lines that print all elements greater than 3.

    solution
    for (auto e : a) {
        if (e > 3) {
            std::cout << e << std::endl;
        }
    }
    
  2. Given std::string s = "apple";, write 1 to 2 lines that print "long" if the string has more than 4 characters.

    solution
    if (s.size() > 4) {
        std::cout << "long" << std::endl;
    }
    
  3. Write a function is_even that returns whether an integer is even.

    solution
    bool is_even(int n) {
        if (n % 2 == 0) {
             return true;
        }
        return false;
    }
    
  4. Given std::vector<std::string> words{"red", "blue", "green"};, write 1 to 3 lines that print each word.

    solution
    for (auto w : words) {
        std::cout << w << std::endl;
    }
    
  5. Write a function that takes a vector of strings and returns the length of the longest string.

    solution
    int longest(const std::vector<std::string>& words) {
        int len{};
        for (auto w : words) {
            len = std::max(len, static_cast<int>(w.size()));
        }
        return len;
    }
    

    This is similar to a recent homework. One thing to take care of is that w.size() returns a std::size_t, so we need to cast to an int for the std::max() (it wants both arguments to be the same type).

Numerical methods#

  1. Our centered difference has a form:

    \[\left . \frac{df}{dx} \right |_{x_0} = \frac{f(x_0 + \Delta x) - f(x_0 - \Delta x)}{2 \Delta x} + \mathcal{O}(\Delta x^2)\]
    1. What is the meaning of \(\mathcal{O}(\Delta x^2)\)?

      solution

      This is telling us about the truncation error of the method. When we derived this expression from a Taylor expansion, we truncated the series at a finite number of terms. This tells us that the error from that truncation is “of-order \(\Delta x^2\)”.

    2. If we get an error \(E\) using a value \(\Delta x\), what error should we expect if we use \(\Delta x / 2\)?

      solution

      Since this is second-order accurate, cutting \(\Delta x\) by 2 reduces the error by \(2^2\) or 4, so we expect the error to be \(E/4\).

    3. What happens to the error if we make \(\Delta x\) close to machine epsilon?

      solution

      As we saw in Roundoff vs. truncation, when we make \(\Delta x\) too small, roundoff error begins to dominate, and our error grows.

  2. Given a function \(f(x)\), if we want to find the zero, \(x_0\), such that \(f(x_0) = 0\), what is the requirement on the starting conditions for the bisection algorithm?

    solution

    We require that an interval is provided, \([x_l, x_r]\), such that the root is contained in the interval. Mathematically, we require that the function change sign, \(f(x_l) \cdot f(x_r) < 0\).

  3. When we looked at integration, we saw the trapezoid rule and Simpson’s rule. Explain how they are different.

    solution

    Both methods use the set of points that sample the function to estimate the integral. In the trapezoid rule, two adjacent function values are connected, resulting in a trapezoid shape for the interval, and the integral for that interval is taken to be the area of the trapezoid.

    In Simpson’s rule, three adjacent points are fit with a parabola, and the area under that parabola is used to approximate the integral over the two intervals defined by the 3 function samples. This is more accurate, in general.