class MapClass holds copies of class DataStruct so that instances of class Action only have to store the key value associated with the DataStruct:

struct DataStruct
{
	friend bool operator< (DataStruct const &lhs, DataStruct const &rhs)
	{
		if (lhs.xData < rhs.xData)
			return true;
		else if (lhs.xData > rhs.xData)
			return false;
		else if (lhs.yData < rhs.yData)
			return true;
		else if (lhs.yData > rhs.yData)
			return false;
		else 
			return false;
	}
	double xData,yData;
	int keyVal;
};

class MapClass
{
	std::map<int,DataStruct> dsMap;
	std::set<DataStruct> dsSet;
public:
	int AddDataStruct(DataStruct &ds)
	{
		// Check to see if ds already entered
		if (dsSet.count(ds) > 0)
		{
			return ds.keyVal;
		}
		else
		{
			ds.keyVal = (int)dsMap.size()+1;
			dsMap.insert(pair<int,DataStruct>(ds.keyVal,ds));
			return ds.keyVal;
		}
	}
};

class Action
{
	int dataKey;
	// member functions do stuff
};

This way I can have multiple instances of Action using the same copy of DataStruct while using the memory of only one DataStruct. And when I print to file all instances of Action are easy to compare visually as I only have one keyVal to look at in a csv file.

My question is this, is there a more elegant way to accomplish the same thing? A better data container?

It appears that

a. There are going to be a very large number of DataStruct and Action objects (since the memory footprint of DataStruct is quite small).

b. DataStruct objects are added, but never removed (since the key is based on the size of the map).

In that case, using both a std::map<> and a std::set<> is unnecessary; a std::vector<> (with the key as the position in the sequence) and a std::set<> would suffice.

Something like this:

struct DataStruct
{
    double xData, yData ;
    std::vector<DataStruct>::size_type keyVal ;
    friend bool operator< ( const DataStruct& a, const DataStruct& b ) ;
};


class MapClass
{
    static std::vector<DataStruct> seq ;
    static std::set<DataStruct> set ;

    public:
        typedef std::vector<DataStruct>::size_type pos_t ;

        static pos_t AddDataStruct( const DataStruct& ds )
        {
            auto iterator = set.find( ds ) ;
            if( iterator != set.end() ) return iterator->keyVal ;
            else
            {
                pos_t pos = seq.size() ;
                seq.push_back(ds) ;
                seq.back().keyVal = pos ;
                set.insert( seq.back() ) ;
                return pos ;
            }
        }

        static const DataStruct& get( pos_t pos ) { return seq[pos] ; }
};

class Action
{
   MapClass::pos_t dataKey ;

   void do_stuff()
   {
      const DataStruct& ds = MapClass::get(dataKey) ;
      // ...
   }
};

with some additional trickery, more memory can be saved - make the value_type of the std::set<> a position in the sequence held by std::vector<>

If Datastruct objects also need to be removed as the program is running, this won't work; you could use boost::bimap<> in that case.
http://www.boost.org/doc/libs/1_47_0/libs/bimap/doc/html/index.html

friend bool operator< (DataStruct const &lhs, DataStruct const &rhs)
	{
		if (lhs.xData < rhs.xData)
                // ...

It depends on the range of values and how close two values are to each other; in general comparing floating point values this way can give you results that you may not expect.

#include <limits>
#include <cmath>
#include <iostream>

int main()
{
    const double ubound = std::sqrt( std::numeric_limits<double>::max() ) ;
    const double delta = 10.0 ;

    for( double d = 10.0 ; d < ubound ; d *= d )
    {
        double a = d - delta ;
        double b = d + delta ;
        std::cout << "a<b ? " << std::boolalpha << (a<b) << '\n' ;
    }
}
Comments
excellent!

Thank you. With the vector I'll have faster access to the elements and a smaller footprint. I should have thought of that. I didn't know that very large doubles do not compare well either.

And yes, both
a. There are going to be a very large number of DataStruct and Action objects (since the memory footprint of DataStruct is quite small).

b. DataStruct objects are added, but never removed (since the key is based on the size of the map).
are true.

This question has already been answered. Start a new discussion instead.