Search code examples
c++sortingvectorsetrelational

Using a set to store a custom class, only the first insert works


Base question Please read this question, even if you don't have time to look at all the other details. :) - I need to store multiple objects of my custom class (Tuple) into a set. That class contains only a vector of string. (As required by the project). How do I implement this so that it recognizes each element uniquely (and therefore only eliminates identical vectors)? 2 - I was suggested by my professor that I could inherit from vector string in my custom class (Tuple) so that I wouldn't have to write my own "operator<"

  • 1 - Is this true?
  • 2 - If not is there something else I could use inheritance from to get this functionality?

Further Details, if you have time :)

I've looked around on the web for a bit and haven't found a solution to my problem. First off, I will admit this is a school assignment (A simple Relational Database) so I have some requirements to follow (even If I don't now see why some of these things are to be done this way).

  • Required Classes

  • --> "Relation" ------> must use a std::set to hold Tuples

  • -->"Tuple" ------> is a list of "attribute values"

I was given the hint in class that if I extended the vector of string that the I would not to write my own operator< in class "Tuple". I don't know that this is a kosher suggestion my professor gave, but not having to write a complicated operator< class sounds appealing to me.

The Issue: I need to store a set of "Tuples" a class which I am to create. The problem I have right now is that only the first "Tuple" is stored. I used a vector alongside the set just so I could see if my code is trying to do what I want, and it looks like my problem is that the set is recognizing each "Tuple" I insert as equivalent to the already existing "Tuple", thus I end up with a set of only 1 "Tuple" even though I should have more.

I've included code that I see as relevant below. To keep you from looking at way to much stuff, at the bottom I show the function that is inserting new "Tuples" into an already existing "Relation", if you look in the Relation class you'll see the function "insertTuple(Tuple tupIn);" which receives a tuple and adds it to the set in question.

What I need to know?

  • Why is only one item being stored in the set?
  • How do I fix this so that all non-duplicate items are stored on the set? (given that I am not all insertions are duplicates, because they are not).

Thank you StackOverflow, you are a bunch of intelligent, patient human beings!

Tuple.h

 #include <iostream>
 #include <vector>
 #include "DatalogProgram.h"
 using namespace std;


 class Tuple : public vector<string>
 {
 private:
     vector<string> attributevals;
 public:
     Tuple();
     Tuple(Predicate input);
     ~Tuple();
     vector<string> returnAttributeVals();
     string toString();
 };

Tuple.cpp

 #include "Tuple.h"
 Tuple :: Tuple()
 {

 }
 Tuple :: Tuple(Predicate input)
 {
     // Here I will populate the tuple!
     attributevals.clear();
     for(int x = 0; x < input.returnParams().size(); x++)
     {
         //cout << "Adding value to Tuple: " << endl;
         //cout << input.returnParams().at(x).toString() << endl;
         attributevals.push_back(input.returnParams().at(x).toString());
     }
 }
 Tuple :: ~Tuple()
 {
     attributevals.clear();
 }
 vector<string> Tuple :: returnAttributeVals()
 {
     return attributevals;
 }

 string Tuple :: toString()
 {
     string value = "";
     for(int x = 0 ; x < attributevals.size() ; x++)
     {
         value = value + attributevals.at(x) + " -|- ";
     }
     return "Attribute Values : \n" + value;
 }

Relation.h

 #include <iostream>
 #include <set>
 #include <vector>
 #include <string>
 #include "Scheme.h"
 #include "Tuple.h"
 //#include "DatalogProgram.h"
 #pragma once
 using namespace std;

 class Relation
 {
 private:
     string Rname;
     Scheme Rscheme;
     set<Tuple> Rtuples;
     vector<Tuple> RtempVec;
 public:
     Relation(string nameIn, Scheme schemeIn);
     Relation(string nameIn, Scheme schemeIn, set<Tuple> tuplesIn);
     ~Relation();
     void insertTuple(Tuple tupIn);
     void select();
     void project();
     void rename();
     void testOutput();
     string returnName();
     Scheme returnScheme();
     set<Tuple> returnTuples();
     string toString();
 };

Relation.cpp #include "Relation.h"

 Relation :: Relation(string nameIn, Scheme schemeIn)
 {
     Rname = nameIn;
     Rscheme = schemeIn;
 }
 Relation :: Relation(string nameIn, Scheme schemeIn, set<Tuple> tuplesIn)
 {
     Rname = nameIn;
     Rscheme = schemeIn;
     Rtuples = tuplesIn;
 }
 Relation :: ~Relation()
 {
     Rname.clear();
     Rscheme.clear();
     Rtuples.clear();
 }
 void Relation :: insertTuple(Tuple tupIn)
 {
     cout << Rname << "  ->  Inserting the following Tuple :  \n" << tupIn.toString() << endl;
     Tuple temp = tupIn;
     Rtuples.insert(temp);
     cout << "Current # of tuples in this relation" << Rtuples.size() << endl;
     RtempVec.push_back(temp);
 }
 string Relation :: returnName()
 {
     return Rname;
 }
 Scheme Relation :: returnScheme()
 {
     return Rscheme;
 }
 set<Tuple> Relation :: returnTuples()
 {
     return Rtuples;
 }
 string Relation :: toString()
 {
cout << "Printing Relation " + Rname << endl;
cout << "Number of Tuples: " << Rtuples.size() << endl;
cout << "Number of tuples in temp Vector" << RtempVec.size() << endl;
set<Tuple> temp = Rtuples;
string result = "";
result = Rname + "\n" + Rscheme.toString() + "\n";
std::set<Tuple>::iterator setiter;
for(setiter = Rtuples.begin(); setiter!=Rtuples.end();setiter++)
{
    cout << "looping through tuples" << endl;
    Tuple temp = *setiter;
    result = result + " " + temp.toString() + "'\n";

}
return result;
 }
 void select();
 void project();
 void rename();
 void testOutput();

Now I've already Included way more code than anyone would want to look at but here is the function that creates the tuples I'm trying to insert into the relation (which go into the set).

 void Database :: ProcessFacts(vector<Predicate> input)
 {
     // So for facts I need to match the ID, then add the Paramaters to the tuple in the relation.
     // this means I itterate through the Relations already present, if it maches then I add a tuple!
     for (int x = 0; x < Drelations.size(); x++)
     {
         for(int y = 0; y < input.size() ; y++)
         {
             if(input.at(y).returnID() == Drelations.at(x).returnName())
             {
                 cout << "adding a tuple to  -->  " << Drelations.at(x).returnName() << endl;
                 cout << "Tuple is: " << Tuple(input.at(y)).toString() << endl;
                 Drelations.at(x).insertTuple(Tuple(input.at(y)));
             }
         }
     }
 }

Solution

  • The problem is that you are inheriting from vector to avoid writing your own operator<, but you are storing the data in a member vector. The inherited operator< has no idea about the member vector, and since you never add anything to the inherited vector all of your objects compare as the same since all empty vectors are the same.

    Inheriting from a standard container will work, but it isn't always the best idea since they do not have a virtual destructor. For this assignment it should be okay, but you should do some research so you are aware of the issues in the future.

    Here's an example that demonstrates the issue with your current code as well as showing two alternatives using inheritance and a member vector. I'd recommend the member vector since it avoids the issues of inheriting from a standard container and the operator< is just one line, but it's up to you.

    #include <cstdlib>
    #include <iostream>
    #include <set>
    #include <string>
    #include <vector>
    
    class ProblemTuple : public std::vector<std::string>
    {
    public:
        void AddAttribute(const std::string& item)
        {
            attributes.push_back(item);
        }
    
        void Print() const
        {
            for(const std::string& a : attributes)
            {
                std::cout << a << " ";
            }
            std::cout << "\n";
        }
    private:
        std::vector<std::string> attributes;
    };
    
    class InheritedTuple : public std::vector<std::string>
    {
    public:
        void AddAttribute(const std::string& item)
        {
            push_back(item);
        }
    
        void Print() const
        {
            for(const std::string& a : *this)
            {
                std::cout << a << " ";
            }
            std::cout << "\n";
        }
    };
    
    class CompositeTuple
    {
    public:
        void AddAttribute(const std::string& item)
        {
            attributes.push_back(item);
        }
    
        void Print() const
        {
            for(const std::string& a : attributes)
            {
                std::cout << a << " ";
            }
            std::cout << "\n";
        }
    
        bool operator<(const CompositeTuple& rhs) const
        {
            return attributes < rhs.attributes;
        }
    
    private:
        std::vector<std::string> attributes;
    };
    
    std::string randomString()
    {
        std::string ret;
        for(int i = 0; i < 3; ++i)
        {
            ret += 'a' + (std::rand() % 26);
        }
        return ret;
    }
    
    template<typename T>
    void PrintSet(const std::string& title, const std::set<T>& set)
    {
        std::cout << title << "\n";
        for(const auto& s : set)
        {
            std::cout << "   ";
            s.Print();
        }
    }
    
    int main()
    {
        //Not calling srand on purpose so the result should be the same every time.
        std::set<ProblemTuple> ProblemSet;
        std::set<InheritedTuple> InheritedSet;
        std::set<CompositeTuple> CompositeSet;
    
        for(int i = 0; i < 3; ++i)
        {
            ProblemTuple pt;
            InheritedTuple it;
            CompositeTuple ct;
            for(int j = 0; j < 3; ++j)
            {
                std::string s(randomString());
                pt.AddAttribute(s);
                it.AddAttribute(s);
                ct.AddAttribute(s);
            }
            ProblemSet.insert(pt);
            InheritedSet.insert(it);
            CompositeSet.insert(ct);
        }
    
        PrintSet("Problem", ProblemSet);
        PrintSet("Inherited", InheritedSet);
        PrintSet("Composite", CompositeSet);
    
        return 0;
    }