Function Practice

Function Practice#

Here are some practice problems to help us understand functions.

  1. Write a function called swap that takes two numbers a and b and swaps their values. There is no return value.

    solution
    Listing 60 swap.cpp#
    #include <iostream>
    #include <format>
    
    void swap(double& a, double& b) {
        double tmp = a;
        a = b;
        b = tmp;
    }
    
    void swap(int& a, int& b) {
        int tmp = a;
        a = b;
        b = tmp;
    }
    
    
    int main() {
    
        double x{1.0};
        double y{2.0};
    
        std::cout << std::format("x = {}, y = {}\n", x, y);
    
        swap(x, y);
    
        std::cout << std::format("x = {}, y = {}\n", x, y);
    
        int a{-100};
        int b{100};
    
        std::cout << std::format("a = {}, b = {}\n", a, b);
    
        swap(a, b);
    
        std::cout << std::format("a = {}, b = {}\n", a, b);
    }
    

    Here we write 2 different versions of swap() —one that takes double and one that takes int. We can do this, even though the function name is the same, because C++ can tell them apart by the datatypes they take. This is a concept called function overloading.

    The functions are void since they don’t explicitly return anything. Instead the values passed in as arguments are modified directly (hence, using references, &, in the function declaration).

    Tip

    Swapping objects is a key part of a sorting algorithm, so C++ provides std::swap.

  2. Write a function sum that accepts a std::vector of numbers and returns the sum.

    solution
    Listing 61 sum.cpp#
    #include <iostream>
    #include <vector>
    
    double sum(const std::vector<double>& vec) {
    
        double s{};
        for (auto e : vec) {
            s += e;
        }
    
        return s;
    }
    
    int main() {
    
        std::vector<double> v{1.0, 2.0, 3.0, 4.0, 5.0};
    
        auto s = sum(v);
    
        std::cout << "the sum is " << s << std::endl;
    
    }
    

    This is pretty straightforward, with one implementation note. We accept the vector argument vec as a const reference. This is to prevent C++ from making a potentially-expensive copy (since the default is pass-by-value). By marking it const we are ensuring that the function sum cannot modify the contents of vec.

  3. Functions can be called recursively (i.e. the function calls itself on a smaller problem).

    Let’s use recursion to write a function that computes the factorial of an integer.

    solution
    Listing 62 fac.cpp#
    #include <iostream>
    #include <format>
    
    long fac(long N) {
    
        if (N < 0) {
            return 0;
        }
    
        if (N == 0) {
            return 1;
        } else {
            return N * fac(N-1);
        }
    
    }
    
    int main() {
    
        int A{5};
        std::cout << std::format("{}! = {}\n", A, fac(A));
    }
    

    We use a long datatype here to help deal with integer overflow (we will still overflow for a moderate sized integer though, even with long).

    We also add a check to ensure that the input integer, N is positive. Otherwise, we will create infinite recursion and the program will crash. Later we’ll see how we can handle this with an assertion.

  4. Let’s rewrite our Prime numbers example to use a function called is_prime() that takes an int and returns a bool indicated whether the input number is prime.

    solution
    Listing 63 primes.cpp#
    #include <iostream>
    #include <cmath>
    
    bool is_prime(int N) {
    
        bool prime{true};
    
        int max_factor = static_cast<int>(std::sqrt(N));
    
        for (int q = 2; q <= max_factor; ++q) {
            if (N % q == 0) {
                prime = false;
                break;
            }
        }
    
        return prime;
    }
    
    int main() {
    
        const int n_max = 100;
    
        for (int n = 2; n <= n_max; ++n) {
            if (is_prime(n)) {
                std::cout << n << " is prime!" << std::endl;
            }
        }
    
    }
    

    This rewrite shows how using functions can make the code easier to understand. Now the loop in main is much more readable, because we can see it is just checking if the loop variable is prime, using the is_prime() function. And we can understand is_prime() on its own. So by splitting things up, we it allows us to understand the code in logical pieces.

  5. Let’s rewrite our Homework #4 problem of creating a vector of Point and finding the distance from the origin to use a function that finds the distance for a single Point.

    solution
    Listing 64 points.cpp#
    #include <cmath>
    #include <format>
    #include <iostream>
    #include <vector>
    #include <cmath>
    
    struct Point {
        double x;
        double y;
    };
    
    double dist(const Point p) {
        //double d = std::sqrt(std::pow(p.x, 2.0) + std::pow(p.y, 2.0));
        double d = std::hypot(p.x, p.y);
        return d;
    }
    
    int main() {
    
        std::vector<Point> points{Point{.x=1.5, .y=2.3},
                                  Point{.x=3.2, .y=1.6},
                                  Point{.x=5.3, .y=0.1},
                                  Point{.x=8.6, .y=2.5}};
    
        for (auto p : points) {
            std::cout << std::format("Point ({:5.2f}, {:5.2f}) is {:6.3f} from the origin.\n",
                                     p.x, p.y, dist(p));
    
        }
    
    }
    

    This is a straightforward change to the code. The main utility here is that we can now easily use our dist() function elsewhere in our code as needed.

  6. Write a function called linspace that takes a minimum and maximum coordinate, \(x_\mathrm{min}\) and \(x_\mathrm{max}\), and a number of points, \(N\) and returns a vector with \(N\) points equally spaced between \(x_\mathrm{min}\) and \(x_\mathrm{max}\) (with those endpoints included).

    E.g., linspace(0, 1, 11) would return {0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0}.

    solution
    Listing 65 linspace.cpp#
    #include <iostream>
    #include <vector>
    
    std::vector<double> linspace(double xmin, double xmax, int N) {
    
        // compute spacing between points
        double dx = (xmax - xmin) / static_cast<double>(N - 1);
    
        // create an empty vector
        std::vector<double> xvec{};
    
        // loop over point, compute x, add to the vector
        for (int i = 0; i < N; ++i) {
            double x = xmin + i * dx;
            xvec.push_back(x);
        }
    
        // return new vector
        return xvec;
    
    }
    
    
    int main() {
    
        auto v = linspace(0.0, 1.0, 11);
    
        for (auto e : v) {
            std::cout << e << " ";
        }
        std::cout << std::endl;
    
    }
    

    This is a good example of a function that returns a std::vector.