Spring semester 2022/23
Dealing with files, streams, vectors and iterators.
Problems are included in the source file. Problems marked with "Example" are supposed to be explained during the workshop. The ones marked with "Practice" are supposed to be solved by the students mostly on their own.
https://en.cppreference.com/w/cpp/numeric/random/uniform_int_distribution
There are several ways of generating random numbers in C++. The one described below is a more modern way, but not the only way.
To generate a number you need two objects: a generator and a distribution. The generator is the source of randomness, you can think of it as "the result of a tossed coin" or "a dice roll". The distribution is a function, a set of rules that transforms generator's output into the numbers distributed in a desired way.
For example, you want to generate coin flips - numbers 0 and 1 - but you only have a dice with numbers 1 to 6. In this case, the generator will be the dice, with its output being a uniformly distributed integer from a set {1, 2, 3, 4, 5, 6}. The distribution (in C++ terms) will be, for example, a function "take number X. If it's even, return 0. If it's odd, return 1". Now if you plug generator's output (uniformly distributed numbers 1, 2, 3, 4, 5 and 6) into the desired distribution function, you will get uniformly distributed numbers 0 and 1, which is the goal.
The process that goes in C++ code is similar, but more complex.
Additionally, a generator needs a seed, an initializer number. Random number generators are usually implemented as deterministic functions that always return the same "random" sequence when restarted. For the dice roll example, you can think of it as a video of someone rolling the dice. If you haven't seen the video yet, the output is random for you, it can be any number from 1 to 6. But you replay the same video (restart the application), you already know which numbers will come up. To prevent this, you select a different video when restarting the application. This represents using a new seed in the generator. A commonly used approach is to use the current time as seed, for example.
#include <random>
#include <iostream>
int main()
{
std::random_device rd; //Will be used to obtain a seed for the random number engine
// Standard mersenne_twister_engine seeded with rd() - the generator.
// std::mt19937 is its type, "gen" is just a variable name, it can be anything.
std::mt19937 gen(rd());
// Distribution. Here it's uniform distribution of integers of type int, from 1 to 6.
// distrib is a variable name.
std::uniform_int_distribution<int> distrib(1, 6);
for (int n = 0; n < 10; ++n)
{
//Use `distrib` to transform the random unsigned int generated by gen into an int in [1, 6]
std::cout << distrib(gen) << ' ';
}
std::cout << '\n';
}Create a function called genThreeDigitNumber(const int& randomState).
The function returns a random three digit number as std::string.
Use the randomState variable as seed in the generator. Generate digits from 0 to 9.
Example
std::mt19937 gen(randomState);
std::uniform_int_distribution<int> distr(0,9);
int rNum = distr(gen); // random numberCreate a new vector newVec as VecString and fill it with random numbers.
newVec size should be the same as the size of the vector obtained from getSurvivorSurnames.
Improve your solution of exercise 4 by implementing the following modifications:
First, move the reading of the line outside of the function "extractDataFromLine". Follow this algorithm:
- Read the line from the file with
std::getlineingetFareForClassand save it as a string. - Pass the string to
extractDataFromLineor an analogous function. - Convert this string to a stringstream and extract data, just like you're doing currently.
- Move the code that checks if the data was read correctly from the input stream to
getFareForClass.
Second, use a loop to extract data from a single line.
- Read data from the stringstream until
','. - Check the current index. If it is the same as the index if data you want, extract the data.
- Repeat until reading fails.
Modify the program so that if the data cannot be parsed, it prints a helpful message and quits instead of crashing.
std::stoi and std::stod use exception to handle cases when data isn't parsed correctly. We haven't talked about exceptions yet, so the explanation will be about std::stringstream. If you want, you can try using exceptions instead.
When std::stringstream cannot parse data, it will set failbit to true. You can check that it's set by using the usual if (inputStream) approach.
To solve this exercise, modify data conversion from std::stoi and std::stod to conversion via writing data to std::stringstream and reading this data back. Make sure you call sstr.clear() before reusing an empty std::stringstream object. Preferably, try moving data conversion to a separate function where this should not be an issue.