Code submission on SPOJ gives runtime error (SIGABRT)

后端 未结 1 1963
南笙
南笙 2020-12-07 04:06

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

相关标签:
1条回答
  • 2020-12-07 04:39

    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
    }
    
    0 讨论(0)
提交回复
热议问题