1
\$\begingroup\$

I found Project Euler some time ago and I thought I'd enjoy trying those problems.

I'm using C++11 (Visual Studio 2012). I thought I'd program a Manager to test each problem and the time it takes my solution to run. I programmed this:

ProblemsManager.cpp

#include "ProblemsManager.h"

#include <algorithm>
#include <iostream>
#include <cstdio>
#include <string>
#include <sstream>
#include <ctime>

ProblemsManager::ProblemsManager() {

}


ProblemsManager::~ProblemsManager() {

}

ProblemsManager & ProblemsManager::getInstance() {
    static ProblemsManager instance;
    return instance;
}

void ProblemsManager::registerProblem(int n, std::function<PSolution()> solution) {
    if (registeredProblems.find(n) != registeredProblems.end()) {
        throw std::exception("That problem is already registered!");
    } else if (testProblems.find(n) != testProblems.end()) {
        throw std::exception("That problem is already registerd as a test problem!");
    }

    registeredProblems[n] = solution;
}

void ProblemsManager::registerTestProblem(int n, std::function<PSolution()> testSolution) {
    if (registeredProblems.find(n) != registeredProblems.end()) {
        throw std::exception("That problem already has a valid solution!");
    } else if (testProblems.find(n) != testProblems.end()) {
        throw std::exception("That problem is already registered!");
    }

    testProblems[n] = testSolution;
}

void ProblemsManager::run() {
    printHeaders();

    if (testProblems.size() > 0) {
        for (const auto & entry : testProblems) {
            log<<"Testing problem "<<entry.first<<": \n";
            auto start = std::chrono::steady_clock::now();
            PSolution sol = entry.second();
            auto end = std::chrono::steady_clock::now();
            log<<"Solution reported: "<<sol<<". ("<<timeDifference(start,end).c_str()<<")\n";
            std::cout<<"Press any key to continue.\n";
            getchar();
        }
    } else {
        for (const auto & entry : registeredProblems) {

            // Redirect std::cout (debug msg from the solution to every problem
            std::streambuf * coutBuff = std::cout.rdbuf();
            std::stringstream debug;
            std::cout.rdbuf(debug.rdbuf());

            auto start = std::chrono::steady_clock::now();
            PSolution sol = entry.second();
            auto end = std::chrono::steady_clock::now();

            // Restore std::cout to screen
            std::cout.rdbuf(coutBuff);

            // Reusing the debug stream for the problem to show the information
            debug.str(std::string()); // clear stream

            debug<<"Problem "<<entry.first;

            for (int i= debug.str().size(); i< TIME_MARGIN; i++) {
                debug<<" ";
            }
            debug<<"Time: "<<timeDifference(start,end).c_str();

            auto timeInSeconds = std::chrono::duration_cast<std::chrono::seconds>(end - start).count();

            if (timeInSeconds > MAX_TIME_SECONDS) {
                debug<<" (too much)";
            }

            for (int i= debug.str().size(); i< ANSWER_MARGIN; i++) {
                debug<<" ";
            }
            debug<<"Solution: "<<sol;

            log<<debug.str();
            log.endl();
        }
    }
}

void ProblemsManager::printHeaders() {
    /// Get datetime
    time_t now = time(NULL);

    #pragma warning(push) // Disabling warnings on VS
    #pragma warning(disable: 4996) // 4996 for _CRT_SECURE_NO_WARNINGS equivalent
    struct tm * ptm = localtime(&now);
    #pragma warning(pop)

    char buffer[32];
    strftime (buffer, 32, "%d/%m/%Y %H:%M:%S", ptm);

    log<<"Report generated on: "<<buffer;
    log.endl();
    log.endl();

    // From http://www.informit.com/articles/article.aspx?p=1881386&seqNum=2
    log<<"Clock data:";
    log.endl();

    log<<"\tPrecision: "<<
        static_cast<double>(std::ratio_multiply<std::chrono::steady_clock::period,std::kilo>::type::num)
        /
        static_cast<double>(std::ratio_multiply<std::chrono::steady_clock::period,std::kilo>::type::den)
        <<" milliseconds.";
    log.endl();

    log<<'\t'<<(std::chrono::steady_clock::is_steady?"Steady clock.":"Warning! Non steady clock. Measurements may be inaccurate");
    log.endl();

    log.endl();
    log<<"-----------------------------";
    log.endl();
    log.endl();
}

std::string ProblemsManager::timeDifference(std::chrono::steady_clock::time_point t1, std::chrono::steady_clock::time_point t2) {
    auto msTotalTime = std::chrono::duration_cast<std::chrono::milliseconds>(t2 - t1).count();

    if (msTotalTime >= 0) {
        std::string retValue = "";
        if (msTotalTime >= 1000) {
            retValue += std::to_string(msTotalTime / 1000);
            retValue += "s";
        }
        retValue += std::to_string(msTotalTime % 1000);
        retValue += "ms";
        return retValue;
    } else {
        std::string retValue = "-";
        retValue += timeDifference(t2,t1);
        return retValue;
    }
}

const std::string ProblemsManager::logger::LOG_PATH = "Resources/report.log";

ProblemsManager::logger::logger() : fileStream() {}

ProblemsManager::logger::~logger() {}

void ProblemsManager::logger::endl() {
    fileStream<<std::endl;
    std::cout<<std::endl;
}

ProblemsManager.h

#pragma once

#include <functional>
#include <map>
#include <chrono>
#include <fstream>

#include "PSolution.h"

class ProblemsManager {
public:
    static ProblemsManager & getInstance();

    void registerProblem(int n, std::function<PSolution()> solution);
    void registerTestProblem(int n, std::function<PSolution()> testSolution);

    void run();

    void printHeaders();

private:
    ProblemsManager();
    ~ProblemsManager();
    ProblemsManager(const ProblemsManager &);
    void operator=(const ProblemsManager &);

    std::string static timeDifference(std::chrono::steady_clock::time_point start, std::chrono::steady_clock::time_point end);

    std::map<int, std::function<PSolution()>> registeredProblems;
    std::map<int, std::function<PSolution()>> testProblems;

    static const int TIME_MARGIN = 12;
    static const int ANSWER_MARGIN = 38;

    static const int MAX_TIME_SECONDS = 60;

private:
    class logger {
    private: 
        std::ofstream fileStream;
        static const std::string LOG_PATH;
    public: 
        logger();
        ~logger();

        void endl();

        template<class T> 
        friend logger & operator<<(logger &log, const T & bytes) {
            if (!log.fileStream.is_open()) {
                log.fileStream.open(LOG_PATH);
                if (log.fileStream.fail()) {
                    perror("Problem opening log file");
                }
            }
            log.fileStream<<bytes;
            std::cout<<bytes;
            return log;
        }
    };

    logger log;
};

Then, I can just use Problems.h:

#pragma once

#include "ProblemsManager.h"
#include "PSolution.h"

#include <functional>
#include <iostream>

class Problem {
public:
    Problem(int n, std::function<PSolution()> f) {
        ProblemsManager::getInstance().registerProblem(n,f);
    }

    ~Problem() {

    }
};


class TestProblem {
public:
    TestProblem(int n, std::function<PSolution()> f) {
        ProblemsManager::getInstance().registerTestProblem(n,f);
    }

    ~TestProblem() {

    }
};

And add a .cpp file (Problem1.cpp) with the solution for a problem like this:

#include "../ProblemsManager/Problems.h"

#include <iostream>

using namespace std;

static Problem p(1,
    [] () -> PSolution {
    // Code for the problem...

    return solution;
}
);

The problem I have is: when I run all the problems, some of them run slowly. For example, I get something like this (pastebin ID: WYjbqgc0):

Note: Problem 52
Time: 133s171ms
Solution: 142857

But, if I change Problem to TestProblem in Problem52.cpp, so that only this problem can be executed (pastebin ID: t803wLvE):

Note: Solution
Reported: 142857 (2s156ms)

I mainly have two questions:

  1. Why do the performances differ?
  2. How can I improve my ProblemManager?

Also, I have noticed it take a long time for the program to terminate (after the return, in main()). Is that because of all the static variables? If so, how can I fix that?

\$\endgroup\$
0

1 Answer 1

1
\$\begingroup\$

First of all, stylistic matters:

Why are you making ProjectManager a singleton? There is no need to require that only one exists: it doesn't use of any global state except itself, and the logging path could easily be passed in using the constructor (along with any of the other "constants" that seem rather arbitrary). If you want to treat problems as more than just int, std::function<PSolution()> tuples you can write a class for them, but as things are I don't see you using it as such.

Neither do I see why you'd want to hide the logger. A stream which functions as a bundle of other streams sounds like something you may want to use in multiple projects; write it up properly and use it.

I'm not sure why you've made the Problem/TestProblem distinction. They sound like they have weaker restrictions: let that be decided by the constructor parameters or run parameters or template parameters, but don't duplicate code just for that. Especially seeing as how you ignore real problems entirely if there are any testProblems available.

What you're doing with the debug stream looks like too much trouble for me. If you want to ignore all output from the problems, why not just set the rdbuf to nullptr?

You don't handle the case where a solution takes infinite time to execute. Perhaps it should only wait MAX_TIME_SECONDS and after that kill the thing?

Why are you first writing everything to debug, and only then to log?

As for your performance problems: quite frankly, I'm not sure. Does only adding one problem to the ProblemManager and running it as a normal problem work? Is the difference only in what is printed, or is it actually present?

\$\endgroup\$
5
  • \$\begingroup\$ Thanks for all your suggestions. About the singleton, you're right, i'll be changing that. I will also rewrite the logger properly. The Problem/TestProblem distinction allows me to test just a problem without having to wait for all other problems to run. I didn't know I could set rdbug to nullptr, I'll be doing that. About killing the problems if they take longer than MAX_TIME_SECONDS to run, i would like to do that, but I am jnot dealing with threads for just now. I write to debug and then to log just to make formatting easier, but maybe there is another more elegant and efficient way. \$\endgroup\$
    – José D.
    Aug 18, 2013 at 17:12
  • \$\begingroup\$ Still, my main worries are the performance problem and the stylistic regarding having one .cpp file with all the static functions and a Problem variable static. Is that a good idea? \$\endgroup\$
    – José D.
    Aug 18, 2013 at 17:15
  • \$\begingroup\$ @Trollkemada: No; as soon as you make it not-a-singleton, you should also make the problems non-static. You might also want to look into a function to run only a single problem, and then have the parameterless run function call that for every element. It'll be much easier to debug that way. \$\endgroup\$
    – Komi Golov
    Aug 18, 2013 at 17:33
  • \$\begingroup\$ But with all non-static how can I make a problem "register" in some way from its .cpp file so that it is runned? (In fact, i just remembered that's the reason why I made ProblemsManager a singleton) \$\endgroup\$
    – José D.
    Aug 18, 2013 at 17:49
  • \$\begingroup\$ @Trollkemada: Register it from somewhere in main. \$\endgroup\$
    – Komi Golov
    Aug 18, 2013 at 18:22

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.