Skip to content

STL Algorithms

Frederik Rokkjær edited this page Sep 26, 2020 · 22 revisions

Exercise 1 istream_operator and ostream_operator

Exercise 1.1 Updating file reading

Inspect the code brought to your disposal. Currently all Product class objects are read in function productDBRead() using an old style loop. Use the newly learned std::istream_iterator to read the file. (Hint: Use std::back_inserter() - Why?)

Remember to test and verify


To make the read code more compact, we need to define 2 new istream_iterators, one for the file itself, and one for end of stream or eos. After that, std::copy() can be used to push the database contents into the ProductList vector

void productDBRead(ProductList &pl, const std::string &fileName)
{
  pl.clear();
  std::ifstream pFile(fileName.c_str());
  std::istream_iterator<Product> fileItr(pFile);
  std::istream_iterator<Product> eos;
  std::copy(fileItr, eos, std::back_inserter(pl));
}

After compilation, we can se that the program does as we expect(Note: product.db needs to have the file path for it to work) Output from code pt. 1

Exercise 1.2 Updating printout

The same old style is used for writing to std::cout in function printAll() - update the function such that it uses std::ostream_iterator.


Now to use the ostream_iterator we have to once again use std::copy() to print to terminal

void printAll(const ProductList &pl)
{
  std::cout << "##################################################"
            << std::endl;
  std::cout << "Printing out all products..." << std::endl;
  std::cout << "----------------------------" << std::endl;

  std::ostream_iterator<Product> plItr (std::cout, "\n");
  std::copy(pl.begin(), pl.end(), plItr);

  std::cout << "##################################################"
            << std::endl;
}

The output looks like this:

Output from code pt. 2

Exercise 1.3 Adding an item

Currently we can only read from the file and print out the contents. Next thing to do would naturally be to add an item. Add the missing code in addItem()


In our addItem() fuction, we ask for the name, price and quantity. That information we use to make a Product object, after that its simply pushed onto the ProductList Vector

void addItem(ProductList &pl) 
{
  std::string productName;
  float price;
  unsigned int sold;

  std::cout << "Please enter name for new Item: ";
  std::cin >> productName;

  std::cout << std::endl << "Please enter price for: " << productName << " ";
  std::cin >> price;

  std::cout << std::endl << "Please enter quantity sold: ";
  std::cin >> sold;

  Product P(productName, price, sold);
  pl.push_back(P);
}

Output from code pt. 3

Exercise 1.4: Writing product list to file

Having performed changes to the product list, we need to be able to save it for future use. Use std::ostream_iterator in productDBWrite() to perform the deed.


For adding items just simply make an ofstream and use std::copy() to write into the database

/**
   Write data to db file
*/
void productDBWrite(const ProductList &pl, const std::string &fileName)
{
  std::ofstream pFile(fileName.c_str());
  std::copy(pl.begin(), pl.end(), std::ostream_iterator<Product>(pFile, "\n"));
}

Output from code pt. 4

Pictured above we can see that the database has been updated with the product FredRokk

Exercise 2 Algorithmic manipulations

Exercise 2.1: To few items sold...

Inspecting the product list it becomes evident that not all products are equally highly valued. In this trial of times we wish only to have products in stock that actually sell well. Complete the function printPoorlySellingProducts() with a printout that only contains those products that have sold fewer than 10 in all. Use std::remove_copy_if() for copying those that are desired and std::ostream_iterator for writing to std::cout.


To get the poorly selling items, we simply have to use std::remove_copy_if() to remove the items that ARE selling. As the last argument of std::remove_copy_if() we need to make a lambda function, a function that is only defined in this functioncall. Lambdas don't need to be declared before use, its therefore its good to use in this context.

/**
 * Print poorly selling products
 */
void printPoorlySellingProducts(const ProductList &pl)
{
  std::cout << "##################################################"
            << std::endl;
  std::cout << "Printing out bad selling products..." << std::endl;
  std::cout << "----------------------------" << std::endl;

  std::remove_copy_if(pl.begin(), pl.end(),
                      std::ostream_iterator<Product>(std::cout, "\n"),
                      [](Product p) { return p.sold() >= 9; });

  std::cout << "##################################################"
            << std::endl;
}

Output from code pt. 5

As we can see the function does what it is meant to.

Exercise 2.2: Discount

In the following two exercises a discount is set on the products using two different approaches.

Exercise 2.2.1: Manipulating each element using std::for_each()

Use std::for_each() and set a new price on each product based on a discount of 10%. Again to try different solutions, two are sought.

  • Using a functor, where the overloaded function operator has the following signature: void operator()(Product& p)
  • Using a lambda Implement your solution(s) are to be implemented in addDiscountUsingForEach(). Test each and verify that they work.

In this exercise were using a lambda, since thats the most simple to use. We use a std::for_each() to discount every item by 10%, then the lambda, takes the price and multiply it by 0.9, since thats the same as subtracting 10%.

/**
 * Set a discount on all products - Using for_each()
 */
void addDiscountUsingForEach(ProductList &pl)
{
  std::for_each(pl.begin(), pl.end(),
                [](Product &p) { p.setPrice(p.price() * 0.9); });
}

the prices of the products on the database is:

Highland_Park 200 23
Glenlivet 150 12
Chivas_Regal 100 5
FredRokk 69 55
test 19 10

Output from code pt. 6

As you can see, the price has been lowered by 10%

Exercise 2.2.2: Transforming each element using std::transform()

Having tried using std::for_each() lets try using std::transform(). The idea in this exercise is to pass the (1) product list, (2) a discount calculating lambda and (3) std::ostream_iterator for printing out directly. The discount, itself, must supplied by the user via std::cin. This implies that the lambda we are to use is a bit different this time. It should be lambda returning a lambda. The first taking the discount supplied by the user. Furthermore use std::clamp to ensure that the given percentage is between 10 and 90. Implement your solution in addDiscountUsingTransform()


In this exercise we wont be using the lambda that returns a lambda, since the implementation is hard to do, and not a lot of information can be found on the web. std::transform() is defriend from std::for_each() since the lambda function needs to return the Product object.

/**
 * Set a discount on all products - Using transform()
 */
void addDiscountUsingTransform(ProductList &pl)
{
  std::vector<Product> tempVec(pl.size());
  std::copy(pl.begin(), pl.end(), std::back_inserter(tempVec));
  std::transform(pl.begin(), pl.end(), tempVec.begin(), [](Product &p) {
    int discount;
    std::cout << "Pleace inter discount percentage: ";
    std::cin >> discount;
    discount         = clamp(discount, 10, 90);
    float multiplier = (100 - (float)discount) / 100;
    float price_f    = (p.price() * multiplier);
    p.setPrice(price_f);
    return p;
  });
}

Just like last time this implementation works

Exercise 2.3: Calculating the total amount of products sold

An interesting metric could be the total amount of products sold. To calculate this we will be using the version of std::accumulate() that takes a binary operation as it’s last parameter. How should the lambda be formed in order for this to work?


std::accumulate() need the parameters first and last iterater, an initial number to start counting on, and a lambda function to know what to do with the Product objects. The lambda takes two arguments, the first will be an int refrence that carries the current sum, and the Product object reference.

/**
 * Calculate the total amount of sold products
 */
void calcTotalSoldProducts(ProductList &pl)
{
  std::cout << "##################################################"
            << std::endl;
  std::cout << "Total amount of products sold: "
            << std::accumulate(
                   pl.begin(), pl.end(), 0,
                   [](int &last, Product &p) { return last + p.sold(); })
            << std::endl;
  std::cout << "----------------------------" << std::endl;
}

The current sum of sold products in the database is 105, and the output after a test is: Output from code pt. 7

Exercise 2.4: Reflection

Discuss the pros and cons of using algorithms. E.g. In your opinion does the use improve clarity or does it add to the clutter? Include code snippets to substantiate your stance.


The std:: iterator functions are a really powerful tool to weald, since it automates some basic operations, that probably are useful for some applications. Also it is clear to see by these operations what is happening instead of forloops that perform the same work. Lastly, it is nice to have less code overall, to not clutter the code.

Exercise 3: Creating my own Iterator

DOESN'T APPLY SINCE WE DIDN'T DO THE TEMPLATE LAB CHALLANGE!

Exercise 4: Creating my own iterator adapter (OPTIONAL)

NOT DONE SINCE IT'S OPTIONAL