I have done an exercise on SPOJ to practice advanced algorithms.
The Problem Statement is as follows:
Harish went to a supermarket to buy e
Since this is a SPOJ question, and you're not given the test data, what you should do is to randomize the tests until you get a failure. That way, you may be able to get a sample case that fails. This is called fuzzing, and is a technique that can be used in your question.
The following will work for the cases that cause segmentation faults, and in some cases, to verify if a given output matches the expected output. In other words, instead of trying to figure out the test data, let the computer generate the tests for you.
The way you do this is to look at the constraints that the question gives you, and generate random data that fits the constraints. Since they are all integers, you can do this by using the <random>
header, and using a uniform_int_distribution
.
Here is a sample of fuzzing the data using the following constraints for N
, K
, and the data for prices:
Constraints:
0 < N <= 100
0 < K <= 100
0 < price <= 1000
OK, so given this information, we can take your exact code, remove the cin
statements, and replace everything with randomized data that fit the constraints. In addition, we can test for an out-of-bounds access if we use at()
to access the vectors in the function that causes the issue.
Given all of this information we can start with changing main
to produce random data that fit the constraints of the question:
#include <random>
#include <algorithm>
//...
int main()
{
// random number generator
std::random_device rd;
std::mt19937 gen(rd());
// Prices will be distributed from -1 to 1000
std::uniform_int_distribution<> distrib(-1, 1000);
// N and K are distributed between 1 and 100
std::uniform_int_distribution<> distribNK(1, 100);
// This one will be used if we need to replace 0 in the Price vector with
// a good value
std::uniform_int_distribution<> distribPos(1, 1000);
// our variables
int Friends;
int Kilogram;
vector<int> Price;
// we will keep going until we see an out-of-range failure
while (true)
{
try
{
// generate random N and K values
Friends = distribNK(gen);
Kilogram = distribNK(gen);
// Set up the Price vector
Price = std::vector<int>(Kilogram + 1, 0);
// Generate all the prices
std::generate(Price.begin() + 1, Price.end(), [&]() { return distrib(gen); });
// Make sure we get rid of any 0 prices and replace them with a random value
std::transform(Price.begin() + 1, Price.end(), Price.begin() + 1, [&](int n)
{ if (n == 0) return distribPos(gen); return n; });
// Now test the function
std::cout << BuyingApple(Price, Friends, Price.size() - 1) << std::endl;
}
catch (std::out_of_range& rError)
{
std::cout << rError.what() << "\n";
std::cout << "The following tests cause an issue:\n\n";
// The following tests cause an issue with an out-of-range. See the data
std::cout << "Friends = " << Friends << "\nK = " << Kilogram << "\nPrice data:\n";
int i = 0;
for (auto p : Price)
{
std::cout << "[" << i << "]: " << p << "\n";
++i;
}
return 0;
}
}
}
Given all of this, we can change the BuyingApple
function by replacing [ ]
with at()
:
int BuyingApple(vector<int> PriceaTag, int Friends, int KilogramsToBuy)
{
vector<vector<int>> Table(Friends + 1, vector<int>(KilogramsToBuy + 1, 0));
for (int i = 1; i <= Friends; i++)
{
for (int j = 0; j <= i; j++)
{
Table.at(i).at(j) = INT32_MAX;
if (j == 0)
Table[i][0] = 0;
else if (PriceaTag[j] > 0)
Table[i][j] = MinValueOf(Table[i][j], Table.at(i - 1).at(i - j) + PriceaTag.at(j));
}
}
return (Table[Friends][KilogramsToBuy] == 0) ? -1 : Table[Friends][KilogramsToBuy];
}
Now we have an automatic case generator, and will catch and display any cases that could cause an issue with the vectors. Note that we keep looping forever until we get a test case that "crashes". We then output the crashed case and can now use those values to debug the issue.
We used std::generate
and std::transform
as an illustration of how to populate a vector (or any sequence container your test uses), and how to specialize the test (like making sure that Price
has no 0
values). Another SPOJ question may need other specializations, but hopefully you get the basic idea.
Here is a Live Example.
We see that a test case caused an out-of-range
exception to be thrown. The main
function has a try/catch
to process this error, and we can see the data that caused the issue.
So it seems that if we have more friends than apples, the issue occurs where we go out-of-bounds. I will not attempt to fix the issue, but you now have a test case where the input fails.
In general, you can use this technique with many, if not most of the "online judge" sites if the site doesn't show you failing test cases.
Edit:
Updated the lambda in the std::transform
to only replace 0
in the Price
vector.
Edit: Here is a random string fuzzer that can produce fuzzed string data.
You can control the number of strings, the minimum and maximum size of each string, and the alphabet of characters that will be used when generating each string.
#include <random>
#include <string>
#include <vector>
#include <iostream>
struct StringFuzzer
{
unsigned int maxStrings; // number of strings to produce
unsigned int minSize; // minimum size of a string
unsigned int maxSize; // maximum size of the string
std::string alphabet; // string alphabet/dictionary to use
public:
StringFuzzer() : maxStrings(10), minSize(0), maxSize(10), alphabet("abcdefghijklmnopqrstuvwxyz")
{}
StringFuzzer& setMaxStrings(unsigned int val) { maxStrings = val; return *this; };
StringFuzzer& setMinSize(unsigned int val) { minSize = val; return *this; };
StringFuzzer& setMaxSize(unsigned int val) { maxSize = val; return *this; };
StringFuzzer& setAlphabet(const std::string& val) { alphabet = val; return *this; };
std::vector<std::string> getFuzzData() const
{
// random number generator
std::random_device rd;
std::mt19937 gen(rd());
// Number of strings produced will be between 1 and maxStrings
std::uniform_int_distribution<> distribStrings(1, maxStrings);
// string size will be distributed between min and max sizes
std::uniform_int_distribution<> distribMinMax(minSize, maxSize);
// Picks letter from dictionary
std::uniform_int_distribution<> distribPos(0, alphabet.size() - 1);
std::vector<std::string> ret;
// generate random number of strings
unsigned int numStrings = distribStrings(gen);
ret.resize(numStrings);
for (unsigned int i = 0; i < numStrings; ++i)
{
std::string& backStr = ret[i];
// Generate size of string
unsigned strSize = distribMinMax(gen);
for (unsigned j = 0; j < strSize; ++j)
{
// generate each character and append to string
unsigned pickVal = distribPos(gen);
backStr += alphabet[pickVal];
}
}
return ret;
}
};
int main()
{
StringFuzzer info;
auto v = info.getFuzzData(); // produces a vector of strings, ready to be used in tests
info.setAlphabet("ABCDEFG").setMinSize(1); // alphabet consists only of these characters, and we will not have any empty strings
v = info.getFuzzData(); // now should be a vector of random strings with "ABCDEFG" characters
}