1
\$\begingroup\$

This is a code revision of a previous post and works well.

The purpose of this code is to produce a universe of points, randomly generated around predetermined centroids, provided from the user as either a vector of vectors or from a file. The final product is an output of sample points produced around the centroids, to be used for fake data analysis in another program. The objective here is brevity and speed. This most recent version is about 100 lines between the .h and .cpp.

Changes in This Version:

  • A critical error was discovered that was producing erratic point counts in the output. This was corrected by moving this->import_centroids() in cluster_set::cluster_set(std::ifstream & input_file, char delimiter) up one level, outside the respective while() loop.

  • A critical error was discovered in remainder computation, resulting in an incorrect number of points on output due to the remained getting eaten up immediately. The rem decrementor was moved one level up, outside the primary while() loop in void cluster_set::clustergen() to correct this error.

Future Implementations:

I am still very pleased with the state of this program, as nearly every major issue has been addressed...

  • The way this is currently coded, the default distribution must exist outside of the cluster_set member functions. The distribution function pointer (a member of cluster_set) then points to this global function. I run into scoping issues with the function pointer if I try to include the default function as a member variable, and I feel this would bloat the code unnecessarily to try to force this to work. One thing I like about the current state is the "smallness" of the code. It just feels a little "dangling" to have a global function in a .h.

  • The distribution currently works on a dimension-by-dimension basis: you pass in a dimension's value, you get back a random number near that value. In v0.2 it was observed that it may be valuable to code this in a point-by-point basis (you pass in an entire point, you get back an entire randomized dimensional set near that point). When I attempted this, it bloated the code - it is also something not needed for my purposes.

  • Error handling.

Notes:

  • I'm compiling in mingw, hence the use of std::chrono::system_clock::now().time_since_epoch().count() instead of std::random_device{}().

  • By community recommendation, I'm slowing updates to respect a 48-hour wait period to allow for response time.

  • I understand this-> is a matter of personal preference in most cases. The reason I'm partial to it is I often code when I'm tired and this-> reminds me that I'm looking at a member variable, not at something else (like a function parameter).

  • I am very happy with these recent updates - shout out to Justin for your pivotal suggestions on v0.2 and v0.3. A thank you to the rest of the community as well!

clustergen.h

#ifndef CLUSTERGEN_H
#define CLUSTERGEN_H

#include <fstream>
#include <vector>

double default_distribution(double &);        // The default distribution function (Normal)

class cluster_set {
    std::vector<std::vector<double>> centroids;   // Centroids around which to evenly generate all points
    double (*distribution)(double &);             // Changeable pointer to a distribution function

    void import_centroids(std::vector<std::vector<double>> &);   // Import centroids from vector

public:
    cluster_set(std::ifstream &, char);                // Import centroids from file with specified delimiter
    cluster_set(std::vector<std::vector<double>> &);   // Import centroids from vector on construction

    void clustergen(unsigned int, std::ostream &, char);
    void set_distribution(double (*new_distribution)(double &)) { this->distribution = new_distribution; }
};

#endif //CLUSTERGEN_H

clustergen.cpp

#include "clustergen.h"
#include <chrono>
#include <iostream>
#include <random>
#include <sstream>

double default_distribution(double & dimension) {
    static std::default_random_engine gen(std::chrono::system_clock::now().time_since_epoch().count());   // Random seed
    std::normal_distribution<double> distr(dimension, 1);
    return distr(gen);
}

// Import centroids from file with specified delimiter into a temporary vector - calls import_centroids()
cluster_set::cluster_set(std::ifstream & input_file, char delimiter) {
    this->distribution = default_distribution;
    std::string line;
    std::vector<std::vector<double>> temp_centroid_vector;
    while (std::getline(input_file, line)) {
        while ((line.length() == 0) && !(input_file.eof())) {
            std::getline(input_file, line);   // Skips blank lines in file
        }
        std::string parameter;
        std::stringstream ss(line);
        std::vector<double> temp_point;
        if ((line.length() != 0)) {
            while (std::getline(ss, parameter, delimiter)) {
                temp_point.push_back(atof(parameter.c_str()));
            }
            temp_centroid_vector.push_back(temp_point);
        }
    }
    this->import_centroids(temp_centroid_vector);
}

// Import centroids from vector on construction
cluster_set::cluster_set(std::vector<std::vector<double>> & centroid_vector) {
    this->distribution = default_distribution;
    this->import_centroids(centroid_vector);
}

// Primary centroid import function - Assures dimensional integrity by comparing all intake to the first point in the [centroids] vector
void cluster_set::import_centroids(std::vector<std::vector<double>> & centroid_vector) {
    for (auto centroid_vector_iter = centroid_vector.begin(); centroid_vector_iter != centroid_vector.end(); ++centroid_vector_iter) {
        if (this->centroids.empty()) {
            this->centroids.push_back(*centroid_vector_iter);
        } else if (centroid_vector_iter->size() == this->centroids.front().size()) {   // Assures dimensional integrity
            this->centroids.push_back(*centroid_vector_iter);
        }
    }
}

// Primary cluster generator - aborts if no centroids have been imported.
void cluster_set::clustergen(unsigned int k, std::ostream & output, char delimiter) {
    if (this->centroids.empty()) {
        output << "ERROR: No centroids have been imported. Aborting operation.";
        return;
    }
    if (k < this->centroids.size()) { k = this->centroids.size(); }
    const unsigned int n = k / this->centroids.size();   // Evenly distributes points across centroids
    unsigned int rem = k % this->centroids.size();       // Evenly distributes points across centroids
    for (auto centroid_iter = this->centroids.begin(); centroid_iter != this->centroids.end(); ++centroid_iter) {
        unsigned int subset = n + (rem ? 1 : 0);         // Evenly distributes points across centroids
        while (subset) {
            std::vector<double> temp_point;
            for (auto dimension_iter = centroid_iter->begin(); dimension_iter != centroid_iter->end(); ++dimension_iter) {
                temp_point.push_back(distribution(*dimension_iter));
            }
            for (auto temp_point_iter = temp_point.begin(); temp_point_iter != temp_point.end(); ++temp_point_iter) {
                if (temp_point_iter != temp_point.begin()) { output << delimiter; }
                output << (*temp_point_iter);
            }
            if (subset - 1) { output << "\n"; };
            --subset;
        }
        if (rem) { --rem; }   // Evenly distributes points across centroids
        auto centroid_iter_peek = centroid_iter;
        ++centroid_iter_peek;
        if (centroid_iter_peek != centroids.end()) { output << "\n"; };
    }
}

main.cpp

#include "clustergen.h"
#include <chrono>
#include <iostream>
#include <random>

double new_distribution(double & dimension) {
    static std::default_random_engine gen(std::chrono::system_clock::now().time_since_epoch().count());   // Random seed
    // static std::default_random_engine gen(std::random_device{}());   // Not randomizing??
    std::uniform_int_distribution<int> distr(-10, 10);
    return dimension + 10 * distr(gen);
}



double uni_distribution(double & dimension) {
    static std::default_random_engine gen(std::chrono::system_clock::now().time_since_epoch().count());   // Random seed
    // static std::default_random_engine gen(std::random_device{}());   // Not randomizing??
    std::uniform_int_distribution<int> distr(-20, 20);
    return dimension + distr(gen);
}


double new_normal_distribution(double & dimension) {
    static std::default_random_engine gen(std::chrono::system_clock::now().time_since_epoch().count());   // Random seed
    // static std::default_random_engine gen(std::random_device{}());   // Not randomizing??
    std::normal_distribution<double> distr(dimension, 10);
    return distr(gen);
}


int main() {
/*
    std::vector<std::vector<double>> v = {{-100, -100},
                                          {100,  100},
                                          {1000, 1000}};
    std::vector<std::vector<double>> v2 = {{-100, -100},
                                           {1},
                                           {100,  100},
                                           {1,    2, 3},
                                           {1000, 1000}}; // Dimensional mismatch

    std::ostream &output_console = std::cout;
    std::ofstream output_file;

    cluster_set my_clusters(v);                        // Vector constructor
    my_clusters.clustergen(11, output_console, ',');   // Generate 10 random points to the console (',' delimited)

    std::ofstream out2;
    out2.open("clustergen_out_2.dat");
    cluster_set my_clusters2(v2);                       // Vector constructor with invalid dimensional points (omitted)
    my_clusters2.set_distribution(new_distribution);    // Setting a user-defined distribution
    my_clusters2.clustergen(11, out2, ',');             // Generate 11 random points to "clustergen_out_2.dat" (',' delimited)

    std::ifstream v3;
    v3.open("clustergen_in.dat");
    std::ofstream out3;
    out3.open("clustergen_out_3.dat");
    cluster_set my_clusters3(v3,'$');          // File const. with user-spec. delimiter - blank lines and invalid dimensions omitted
    my_clusters3.clustergen(13, out3, '@');    // Generate 13 random points to "clustergen_out_3.dat" (',' delimited)


*/
    std::vector<std::vector<double>> v4 = {{0, 0, 0}};

    // Generate centroids
    std::ofstream centroid_out;
    centroid_out.open("centroids.dat");
    cluster_set centroids(v4);
    centroids.set_distribution(uni_distribution);
    centroids.clustergen(5, centroid_out, ',');
    centroid_out.close();   // Need to close out - "centroids.dat" is used in the following code

    // Generate points around the centroids
    std::ifstream v5;
    v5.open("centroids.dat");
    std::ofstream test_data_out;
    test_data_out.open("test_data.dat");
    cluster_set test_data(v5,',');
    test_data.clustergen(50000, test_data_out, ',');
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.