C++/File I/O

Advertisement


Question
Here's the thing: I want to use the random number function to give 2 random numbers, between 1 and int "max", for which I use this:

cin>>max;

int a=(rand()%(max-1))+2;
int b=(rand()%(max-1))+2;

and that seems to work fine.

Now, what I want is to use a combo of random numbers that have not been used before. I have been able to successfully get number combos into files called 1, 2, 3, etc. The files read

[number a]   [number b]

How can I use these files to create combos of random numbers that have not been used before(while keeping them between 1 and the value contained in int "max")?

Thanks for your help

Answer
First I would suggest you use srand to seed the random number generator used by rand to produce different sequences of random numbers. A good general purpose way to produce the value taken by srand is to pass it the returned value from a function like time - which gives the current system time in seconds that has elapsed since a certain point (midnight 1 January 1970) thus:

#include <cstdlib>
#include <ctime>

int main ()
{
 srand( time(NULL) );

// ...

}

Secondly, as you know how to write a file in C++ I assume you know how to read it. However, rather than filling up your file system with lots of files with a single pair of values in I would suggest you use one file and open it for reading, writing and appending:

#include <fstream>

int main()
{
   std::fstream value_stream( "random_pairs.dat"
         , std::ios::in | std::ios::out
         | std::ios::app
         );

   if ( value_stream.is_open() )
   {
   // read existing values
   // create new value pair
   // write (append) new value pair
   }
   else
   {
   // Handle unable to open data file error
   }
}

The above outline code shows how to open a file for reading, writing and appending rather than the usual read or write and truncate existing data first. The trick is to supply the required open mode flags required directly to the std::fstream constructor or open operation, rather than relying on the defaults provided by std::ifstream and std::ofstream.

However it is done assuming you can read all the previous random number pairs from the file(s) used to store them, the way to check that a new pair of numbers is different is to compare it with _all_ existing pairs. If it matches none of them you have a valid new pair of values, otherwise you have it already and will have to generate a new pair.

So you either have to load the previous pair data into memory or search through one or more files for each new pair. Loading the data into memory requires one pass through all the data and memory to store all the pairs and is much quicker to search through once in memory than leaving the data in files. Leaving the data in files is much, much slower to search but would not need memory to store all the values temporarily. Assuming you have enough memory then I suggest you read each value into memory and store them in a standard C++ library container such as a vector as std:pair objects. The std::pair type allows instances to be compared using ==, !=, <, <=, >, >=. This is useful as you can use standard C++ library algorithms to check for existing pairs. We will need the headers for the container, e.g. vector, the header for the algorithms, algorithm, and the header for pair, utility:

#include <utility>
#include <vector>
#include <algorithm>

We use a vector of pairs. I shall create type aliases name for the particular std:pair and std::vector types we need, and later define an object of this collection type to store the exiting random number pairs:

typedef std::pair<int,int>       RandomPair;
typedef std::vector<RandomPair>  VectorRandomPair;

//...

VectorRandomPair exisiting_pairs;

Next we need to fill the collection with the stored data:

int first(0);
int second(0);
while ( get_random_number_pair(value_stream, first, second) )
{
   exisiting_pairs.push_back( std::make_pair(first, second) );
}

I assume we have some function called get_random_number_pair that takes information about the file we wish to extract data from (I have used the value_stream fstream object from my single file example, but maybe you need to pass a file number to allow the building of a filename to open and read values from or something), and returns two int values to local variables first and second (i.e. these are passed by non-const reference). It returns a bool when no more data is available. In the case of the single file example this will be when the end of file is reached (i.e. stream.eof() is true), or possibly on some format failure (stream.fail() is true) or error (stream.bad() is true). In the case of one pair per file it would be when there are no more files to read from. Of course we use the stream extraction operator (operator>>) to extract the values from the stream (once opened):

An example implementation of get_random_number_pair using a single file pre-opened on a readable stream would be:

bool get_random_number_pair(std::istream & stream, int & v1, int & v2)
{
   int tmpV1(0);
   int tmpV2(0);
   if ( stream.good() )
   {
       stream >> tmpV1;
   }

   if ( stream.good() )
   {
       stream >> tmpV2;
   }

   if ( stream.good() )
   {
       v1 = tmpV1;
       v2 = tmpV2;
   }

   return stream.good();
}

Values are read into temporary variables and only copied to the supplied output parameters v1 and v2 if all is ok. Hence v1 and v2 are not modified on end of file, format failure or fatal stream errors.

Now we have the existing pairs in memory we can compare new values to all values in the vector and write out the new value to the stream:

bool keep_trying(true);
while ( keep_trying )
{
   RandomPair newPair( std::make_pair(rand(), rand()) );

   if ( std::find( exisiting_pairs.begin()
         , exisiting_pairs.end()
         , newPair
         ) == exisiting_pairs.end()
      )
   {
       write_new_pair(value_stream, newPair.first, newPair.second);
       keep_trying = false; // all done
   }
}

The find algorithm returns the end of sequence position value if the value is not found in the sequence searched. Sequences in the C++ library always have a specified end point that is one past the end of the sequence, so this value is never part of the sequence of values processed by the algorithms.

So if find returns the end position of the existing pairs vector then it did not find the new pair and it can be added (in this case by calling some function to write the value to the appropriate place, similar notes as for get_random_number_pair apply) and we are done. Otherwise we go and try another new pair.

A point to note is that if you use my scheme of having a single file open for reading and writing you should clear the end of file flag before writing to the stream otherwise the stream will not be usable. The easiest way is to just clear all stream state flags:

   value_stream.clear();

or you can be specific and state which flags to clear:

   value_stream.clear(value_stream.rdstate() & ~std::ios::eofbit);

Which is a bit more cumbersome as you need to pass cleared bits as 0s and uncleared bits as 1s.

A potentially faster method would be to use an associative collection to store the pairs in, such as std::set. Some properties of std::set are that the values are stored sorted and you can only insert a value once in the set. Hence finding a value is quicker and if inserting a new pair into the set fails it is probably because the value is in the set already. To use set we include set instead of vector:

#include <set>

And we use a type like so:

typedef std::set<RandomPair>  SetOfRandomPair;

//...

SetOfRandomPair exisiting_pairs;

We fill the set with the stored data, using insert rather than push_back:

int first(0);
int second(0);
while ( get_random_number_pair(value_stream, first, second) )
{
   exisiting_pairs.insert( std::make_pair(first, second) );
}

And we use the set type's own find operation to see if we have new pairs already:

bool keep_trying(true);
while ( keep_trying )
{
   RandomPair newPair( std::make_pair(rand(), rand()) );

   if ( exisiting_pairs.find(newPair) == exisiting_pairs.end() )
   {
       write_new_pair(value_stream, newPair.first, newPair.second);
       keep_trying = false; // all done
   }
}

All the code should compile using a modern C++ compiler (I used VC++ 8 (a.k.a. 2005) to check it), however some errors may have crept in, for which I apologise. I used a really simple implementation of write_new_pair with no error checking:

void write_new_pair
( std::ostream & stream
, int const & v1
, int const & v2
)
{
 stream << v1 << ' ' << v2 << std::endl;
}

I have also not kept your code to limit the range of the random numbers in my examples but you should be able to add it in simply enough. All the code presented here is for demonstration purposes only and is not of production quality.

I do not have space, (I'm limited to 10,000 characters for AllExperts answers) to go into all the ins and outs of C++ library streams, collections and algorithms. I suggest you obtain a good reference such as "The C++ Standard Library A Tutorial and Reference" by Nicolai M. Josuttis. There is reference material online for example at http://www.sgi.com/tech/stl/, http://www.digilife.be/quickreferences/PT.htm and http://www.cplusplus.com/.

I would also recommend that you examine what it is you are really trying to achieve. It seems an awful lot of work to go to to ensure unique random number pairs each time the program is ever run. There are some nasty implications. Over time you will require more resources (time and space) to manage these values. Eventually the program will never complete as it can never find a new unique pair of numbers. The space required to check for values will increase as will the time to find any remaining unique pairs of values. You may run out of memory and run slower as more values have to be generated to locate a unique pair. The time to check for new values will also increase especially for the vector implementation. The set implementation should be fairly constant in its time to check for a new pair until memory starts paging. If and when these effects kick in depends on factors such as the size of max and your system specifications.

I suspect your real problem is that every time you ran the program you got the same two random values because you had not seeded the random number at all, let alone in a semi-random fashion. In which case using srand with a function like time may be all you needed!

C++

All Answers


Answers by Expert:


Ask Experts

Volunteer


Ralph McArdell

Expertise

I am a software developer with more than 15 years C++ experience and over 25 years experience developing a wide variety of applications for Windows NT/2000/XP, UNIX, Linux and other platforms. I can help with basic to advanced C++, C (although I do not write just-C much if at all these days so maybe ask in the C section about purely C matters), software development and many platform specific and system development problems.

Experience

My career started in the mid 1980s working as a batch process operator for the now defunct Inner London Education Authority, working on Prime mini computers. I then moved into the role of Programmer / Analyst, also on the Primes, then into technical support and finally into the micro computing section, using a variety of 16 and 8 bit machines. Following the demise of the ILEA I worked for a small company, now gone, called Hodos. I worked on a part task train simulator using C and the Intel DVI (Digital Video Interactive) - the hardware based predecessor to Indeo. Other projects included a CGI based train simulator (different goals to the first), and various other projects in C and Visual Basic (er, version 1 that is). When Hodos went into receivership I went freelance and finally managed to start working in C++. I initially had contracts working on train simulators (surprise) and multimedia - I worked on many of the Dorling Kindersley CD-ROM titles and wrote the screensaver games for the Wallace and Gromit Cracking Animator CD. My more recent contracts have been more traditionally IT based, working predominately in C++ on MS Windows NT, 2000. XP, Linux and UN*X. These projects have had wide ranging additional skill sets including system analysis and design, databases and SQL in various guises, C#, client server and remoting, cross porting applications between platforms and various client development processes. I have an interest in the development of the C++ core language and libraries and try to keep up with at least some of the papers on the ISO C++ Standard Committee site at http://www.open-std.org/jtc1/sc22/wg21/.

Education/Credentials

©2016 About.com. All rights reserved.