C++ Sample Project - Need help with algorithm - c++

I had one of my friends send me this programming assignment so I could brush up on some of my C++ skills. Below is the program description and my proposed algorithm. Could someone please provide some feedback/alternative solutions:
Problem:
This program creates word search puzzles - Words are printed at random locations in a rectangular grid. Words may be horizontal or vertical and may be forward (left-to-right or top-to-bottom) or reversed (right-to-left or bottom-to-top). Unused grid squares are filled with random letters. The program should take a set of word lists as input and produce two files as output. The first lists, for each puzzle, the list of words in the puzzle, followed by the puzzle itself. The second should show where in each puzzle the words are located, without the random filler letters
Our input file contains the following:
A number n > 0 (representing the # of words in the puzzle) followed by that many words. For example:
3
FRODO
GIMLI
ARAGORN
N will not be larger than 10
We need to create the puzzle using a multidimensional array of size 12 x 12
Requirements:
1. Two output files - One containing the puzzle words and he puzzles, one with only the solutions and no filler characters
2.There needs to be as many horizontal words as there are vertical words
3. A 1/3 of the words needs to be reversed
4. There needs to be at least two intersections in the puzzle
Proposed Algorithm:
1. Create two multidimensional arrays - one for the puzzle and one for the solutions
2. Create a one-dimensional array that contains the various letters of the alphabet
3. Fill the puzzles array with random letters of the alphabet (using a pseudo-random # generator and the array from step # 2)
4. Begin reading the input file
5. Read in n
6. While counter is less than n, read in words, also have a counter for vertical words and horizontal words
7. For each word, find the length of the string
8. Find a random array location to insert the word.
9. If random location index + string length <= 12 or if random location index - string length is >= 0 (to ensure that the word will fit forward or reverse) insert the word
10. Also insert the word into the solutions array
12. Reuse arrays to insert all the words in the input file (in a similar manner)
I am still unsure as to how I can ensure that at least two intersections exist.
I am also concerned that my proposed algorithm is unnecessarily convoluted.
Your feedback is greatly appreciated!
OK, Here's as far as I got into the coding process, before I decided to go back and revisit the algorithm:
#include <iostream>
#include <fstream>
#include <iomanip>
#include <string>
#include <ctime>
using namespace std;
//Error Codes
const int INPUT_FAIL = 1;
const int PUZZLES_OUTPUT_FAIL = 2;
const int SOLUTIONS_OUTPUT_FAIL = 3;
//Function Declarations/Prototypes
void OpenFiles(ifstream& input, ofstream& puzzles, ofstream& solutions);
//PRE: The filestream objects exist and their address locations are passed in
//POST: The filestreams are opened. If they cannot be opened, an error message is printed to screen
// and the program is terminated.
void FillArray(char puzzle[][12], char alphabet[]);
//PRE: The address of the array is passed in
//POST: The array is filled with a random set of
void CreatePuzzle(char puzzle[][12], ifstream& input, ofstream& puzzles, ofstream& solutions);
//PRE: The address of the puzzle array,the address of the ifstream object and the addresses of the
// ofstream objects are passed in.
//POST: The data in the input file is read and the words are input into the puzzle AND the puzzle
// and solutions are printed to file.
void PrintPuzzle(char puzzle[][12], ofstream& output);
//PRE: The address of the puzzle array and the ofstream object is passed in
//POST: The puzzle is output to the file
int main()
{
//Seed the pseudo random generator
srand(time(NULL));
//Declare the filestream objects
ifstream input;
ofstream puzzles, solutions;
//Declare the 2D array
char puzzle[12][12];
char solution[12][12];
//Declare an alphabet array
char alphabet[27] = {"ABCDEFGHIJKLMNOPQRSTUVWXYZ"};
/*char alphabet[27] = {'A','B','C','D','E','F','G','H','I','J','K','L',
'M','N','O','P','Q','R','S','T','U','V','W',
'X','Y','Z'};*/
//Attempt to open files
OpenFiles(input, puzzles, solutions);
//Fill puzzle array with random letters of the alphabet
FillArray(puzzle, alphabet);
//Print puzzle
PrintPuzzle(puzzle, puzzles);
//Read in data to create puzzle
input >> numwords;
return 0;
}
//Function definitions
void OpenFiles(ifstream& input, ofstream& puzzles, ofstream& solutions)
{
//Attempt to open files
input.open("input.txt");
puzzles.open("puzzles2.txt");
solutions.open("solutions2.txt");
//Ensure they opened correctly
if (input.fail())
{
cout << "Input file failed to open!" << endl;
exit(INPUT_FAIL);
}
if (puzzles.fail())
{
cout << "Output file - puzzles.txt failed to open!" << endl;
exit(PUZZLES_OUTPUT_FAIL);
}
if (solutions.fail())
{
cout << "Output file - solutions.txt failed to open" << endl;
exit(SOLUTIONS_OUTPUT_FAIL);
}
}
void FillArray(char puzzle[][12], char alphabet[])
{
int tmp;
for(int i = 0; i < 12; i++)
{
for(int j = 0; j < 12; j++)
{
tmp = rand()%26;
puzzle[i][j] = alphabet[tmp];
}
}
}
void PrintPuzzle(char puzzle[][12], ofstream& output)
{
for(int i = 0; i < 12; i++)
{
for(int j = 0; j < 12; j++)
{
output << puzzle[i][j] << " ";
}
output << endl;
}
}
void CreatePuzzle(char puzzle[][12], ifstream& input, ofstream& puzzles, ofstream& solutions)
{
string pword; //#the puzzle word being read
int numwords; //# of words in a given puzzle
char tmparray[13];
int wordlength = 0;
int startloc;
//Read the number of words to be used in the puzzle
input >> numwords;
int vwords = numwords/2; //#of vertical words
int rwords = numwords/3; //# of reversed words
int hwords = (numwords - (numwords/2)); //# of horizontal words
for(int i = 0; i < numwords; i++)
{
//Read the word into our tmparray
input >> pword;
tmparray[] = pword;
wordlength = pword.length();
//Find a random array location to begin inserting the words
startloc = rand()%12;
int tmpcount = 0; //a temporary counter to ensure that
for(tmpcount; tmpcount <= 1; tmpcount ++)startloc + wordlength < 12)
{
for(int j = 0; j <= wordlength; j++)
{
puzzle[startloc][startloc]

Try it on paper first
Then make it work (in code)
Then make it fast /efficent / elegant
edit - Sorry I wasn't being sarcastic, this was before the OP posted code, and it wasn't clear they had attempted the problem.

My first suggestion would be don't bother pre-filling the array with anything - just stick th words in, and fill in the gaps randomly once you're finished.

A few thoughts/suggestions:
I think you could do this with one single 2D array instead of two. It seems simpler in my mind, though of course you may find I'm wrong when you sit down to actually implement this.
At step 9, instead of finding a spot where the word could fit either forward or reversed, first decide which way it's going to go in (perhaps using a random number generator). Then pick a spot and check the first condition, and just insert the word, either forwards or backwards. Remember this is a 2D array though, so you'll need to look at both the x and y coordinates of the point you chose. You'll also have to look at the words you've already placed in the grid, to make sure you don't overwrite something that's already there.
For finding intersections, think about how you would do it by hand (like Martin said). You've already placed a few words in the grid and now you want to add a new one. Suppose there aren't many intersections yet so you'd like to have the current word intersect one of the ones already in teh grid, if possible. How do you know if an intersection is possible, and how do you know where to place the word so that it creates an intersection?

My first thoughts are:
Place the words first and then randomly fill the gaps. I think it will be much easier for you to visualise it that way and also be easier to check if the word placement is done correctly.
After placing the first word I would save the word into an array. After checking if the second word is small enough to fit the puzzle I would have the program find the common letters of word 1 and 2. If they have a common letter, place the second word so the two words intersect(of course you must first check if the way you try to place word 2 is legal aka if it fits the puzzle the way you are trying to place it). Word 3 is the same way except look for possible intersections between words 1 and 2.If there is no possible intersection try the next word. If you can't have 2 or more intersections after placing all words, empty the array and replace the first word in a different random position. After having placed words in such a way that at least two intersections exist, you can continue and place the rest of the words.

Related

Filling a 2D array with white space (input from file)

I'm having trouble adding white space to the 2D array "item". In the end, I essentially want the data in file (quote.txt) to be able to index properly with its line number. I've made the array 9(row) by 20(col) which is largest sentence in my file and where ever there are not 20 column units of data I want to populate it with a white space so I can index my array accordingly.
I've tried using vector of vectors but it gets super confusing.
#include <iostream>
#include <sstream>
#include <fstream>
#include <vector>
#include <array>
#include <string>
using namespace std;
int main()
{
string file_name;
ifstream fin("quote.txt");
while(!fin)
{
cout << "Error Opening File! Try again!" << endl;
cout << "Enter file name: ";
cin >> file_name;
}
string item[9][20];
for (int row = 0; row < 9; row++)
{
for (int col = 0; col < 20; col++)
{
fin >> item[row][col];
//cout << item[row][col] << endl;
}
}
for (int k = 0; k < 20; k++)
{
cout << item[0][k] << endl;
}
}
Explanation: I'm trying to populate my item 2d array with the contents in quote.txt but since the sentence length varies I cannot use for loop and say column is 20 units because it bleeds into the next row and screws up the indexing. My solution is that I want to add a white space (filler) so that I can iterate over with my for loop and every content in each row has 20 columns. That way I can use the index of rows to look at each row in the text file. Basically, I want the text file to be a 2D array in which I can find each element(word) through using [row][col] indices.
Text File: "quote.txt"
People often say that motivation doesn t last Well neither does bathing that s why we recommend it daily Ziglar
Someday is not a day of the week Denise Brennan Nelson
Hire character Train skill Peter Schutz
Your time is limited so don t waste it living someone else s life Steve Jobs
Sales are contingent upon the attitude of the salesman not the attitude of the prospect W Clement Stone
Everyone lives by selling something Robert Louis Stevenson
If you are not taking care of your customer your competitor will Bob Hooey
The golden rule for every businessman is this: Put yourself in your customer s place Orison Swett Marden
If you cannot do great things do small things in a great way Napoleon Hill
What the Program is suppose to do?
The program is suppose to allow me to find a word by user input. say the word is "of" it is suppose to output which line numbers it is on. Similarly if I input "of People" it outputs the line number
I would probably do it something like this:
// The dynamic vector of strings from the file
std::vector<std::vector<std::string>> items;
std::string line;
// Loop to read line by line
while (std::getline(fin, line))
{
// Put the line into an input string stream to extract the "words" from it
std::istringstream line_stream(line);
// Add the current line to the items vector
items.emplace_back(std::istream_iterator<std::string>(line_stream),
std::istream_iterator<std::string>());
}
After this the items vector will contain all words on all lines. For example items[1] will be the second line, and items[1][2] would be the third word on the second line ("not" with the file contents you show).
Going by the stated purpose of the program (to find a word or a phrase in the file, and report which line numbers those are found on) you don't need to store the lines at all.
All you need to do is read each line into a string, replace all tabs and multiple spaces with a single space, and see if the word (or phrase) is found on that line. If it is found then store the line number in a vector. Then discard the current line as you read the next one.
After all of the file have been processed, just report the line-numbers from the vector.

Trying to read a single character at a time into an array of indefinite size

I am a CS student working on a c++ project. We have been instructed to declare a struct and use it to read in an array of chars and keep a tally of how many letters are used in the string. We are not allowed to use a string; it MUST be an array of our declared struct.
The input must be as long as the user wants; the code has to be able to accept new lines of input and be terminated by '.'
I'm really struggling here. I don't even know where to begin. I've thrown together some code as best-guess for what to do, but it crashes after pressing "." then enter, and I don't know why.
//declare struct
struct data
{
int tally = 0;
char letter;
};
//size of string to read in at a time
const int SIZE_OF_CHUNK = 11;
int main()
{
//input chunk of struct
data input[SIZE_OF_CHUNK];
int placemark,
length;
cout << "Enter sequence of characters, '.' to terminate:" << endl;
do
{
for (int index = 0; (input[index].letter != '\0') && (input[index - 1].letter != '.'); index++)
{
cin >> input[index].letter;
placemark++;
}
//I intend to put something here to handle if the code
needs to read in another chunk, but I want to fix the crashing
problem first
}
while (input[placemark].letter != '.');
//print out what was read in, just to check
for (int index = 0; input[index].letter != '\0'; index++)
{
cout << input[index].letter;
}
return 0;
}
I've tried looking up how to read in a single character but haven't found anything helpful to my circumstances so far. Any tips on what I'm doing wrong, or where I can find helpful resources, would be very much appreciated.
Are you sure you must use a declared struct?
If you just want to count the number of times a character has appeared, you don't need to store the character; you just need to store the number of times it appeared. So just unsigned lettersCount[26], and each index maps to a letter (i.e. index 0 means a, index 1 means b). Whenever a letter appears, just increase the count of that index.
You can map a letter to the index by making use of ASCII. Every letter is represented by a decimal number that you can look it up at ASCII table. For example, the letter a is represented by the decimal value 97, b is 98 and so on. The number increases successively, which we can make use of. So if you want to map a letter to an index, all you need to do is just value - 97 or value - 'a'. For example, if you read in the letter a, take away 97 from that and you'll get 0, which is what you want. After getting the index, it's just a simple ++ to increment the count of that letter.
Regarding the treatment of uppercase and lowercase (i.e. treat them the same or differently), it'll be up to you to figure it out how to do it (which should be fairly simple if you can understand what I've explained).

C++: Creating a word "letter after letter" in string data type

I'm learning C++ for my exam and one thing is bugging me.
I had a file with 25 words (let's call it "new.txt") and a file with 1000 words ("words.txt").
I had to check how many times a word from new.txt appears in words.txt. And after this I had to check how many times does a "mirror" of a word for new.txt appears in words.txt (mirror meaning the word from right to left => car = rac..)
My idea was to make three arrays: newword[25], words[1000], mirror[25] and then go one from there.
I know how to do this with "char" data type. But i wanted to try doing it with "string" type.
Here is the code:
string mirrors(string word) //function that writes the word from the back
{
int dl=word.length();
string mir;
for (int q=0;q<dl;q++)
{
mir[q]=word[dl-q-1]; //first letter of a new word is a last letter of the original word
}
return mir;
}
int main()
{
ifstream in1 ("words.txt");
ifstream in2 ("new.txt");
string words[1000], newword[25], mirror[25]; //creating arrays
for (int x=0;x<1000;x++) //filling the array with words from words.txt
{
in1>>words[x];
}
for (int y=0;y<25;y++) //filling the array with words from new.txt
{
in2>>newword[y];
}
in1.close();
in2.close();
for (int z=0;z<25;z++)
{
mirror[z]=mirrors(newword[z]);
}
out.close();
return 0;
}
And here is the problem...
When I'm changing the order of the letters, the string from "mirror" does not print using normal cout<
So my question is... Is there something with string data types that makes it impossible to print using one command after creating a word letter after letter, or is there something I have no clue about?
Because the word is there, it is created in this array. But cout<
I'm sorry if the question is not clear but it's my first time posting here...
string mirrors(string word) {
int dl = word.length();
string mir; // here you declare your string, but is's an empty string.
for (int q = 0; q < dl; q++) {
// by calling mir[q] you are referencing to the [0, 1 ... dl-1] char of empty string (so it's size = 0) so it's undefined bhv/error.
// mir[q]=word[dl-q-1]; //first letter of a new word is a last letter of the original word
mir = mir + word[dl - q - 1]; // you rather wanted to do smth like this
}
return mir;
}
using new as a variable name is not a very good idea as #Johny Mop pointed
Jbc to możesz też po polsku zadać pytanko w komentarzu :).
First of all you try to access symbols in empty std::string, which leads to UB. In practice all of that is unnessesary:
std::string mirrors( const std::string &word) //function that writes the word from the back
{
return std::string( word.rbegin(), word.rend() );
}
is enough. As for your program, it would be much better to read content of file "new.txt" into memory, and std::set or std::unordered_set would be much better for lookup. Then create 2 instances std::map<std::string,int> (or std::unordered_map if you do not care of order) and read file "word.txt" one by one, count and update those maps accordingly:
std::unordered_set<std::string> new_words; // this should be populated from file "new.txt"
std::map<std::string,int> counts, mcounts;
// reading loop of file "words.txt"
std::string word = ...;
counts[ word ] += new_words.count( word );
word = mirrors( word );
mcounts[ word ] += new_words.count( word );
then you would have all your counts.

C++ Strings Arrays ifstream Delimiters

I am making a simple program in C++ that takes sentences/comments from Youtube from a file, these sentences all end with "!!!!". I need to get these sentences into an array list. The rest of the project isn't important, its comparing the individual words in each array and counting the number of negative and positive words from two other arrays which I got working into arrays because they are all separated by white spaces.
I am not sure how to set up the delimiter of "!!!!".
I know there are a total of 94 sentences.
I am aware that I hard coded the number of positive and negatives in my two working arrays, that is not an issue I am currently concerned about, I can improve this later once it all works.
In summary: I need to make a string array that is 94 strings long that takes input from a file, where every string/sentence ends with
"!!!!"
After that I should be able to figure out how to separate every word in each string and match them to the words in the other 2 arrays and count how many match for each.
Any help would be greatly appreciated, thanks in advance.
Here is the code I got so far.
#include <iostream>
#include <fstream>
#include <string>
using namespace std;
int main() {
string posArr[2006];
ifstream positive("positive-words.txt");
if (positive.is_open()) {
for (int i = 0; i < 2006; i++) {
positive >> posArr[i];
}
}
positive.close();
string negArr[4783];
ifstream negative("negative-words.txt");
if (negative.is_open()) {
for (int i = 0; i < 4783; i++) {
negative >> negArr[i];
}
}
negative.close();
string commentsArr[94];
ifstream comments("youtubecomments-dataset.txt");
//I tried a bunch of different code that didn't work so I erased it.
// cout << posArr[0] << endl; //tested the array is working
// cout << negArr[0] << endl;
cin.ignore();
cin.get();
return 0;
}
Here are the first few lines from the Negative txt file:
2-faced
2-faces
abnormal
abolish
abominable
Here are the first few lines from the Positive txt file:
a+
abound
abounds
abundance
abundant
Here are the first few sentences from the comments text file:
Sync system sucks big time, a total turn off when considering this car.!!!!40mpg ain't that good these days, niether is a 5 speed gearbox!!!!On the Explorer Base the radio is a lot easier and I paired my phone up no
problem. Everything worked but the voice command doesn't work.!!!!I have one of these as a rental right now. The electronics aren't really
that hard to figure out... But the automatic transmission is HORRIBLE. It's
juttery, gringy, and jumpy. And the gears shift all over the place. I'd
never buy one of these with an auto tranny. I bet it's great in manual.!!!!you're exactly as tall as i am :)!!!!
It is not structured, sentences start on the same line, and the spacing is bad in some places, but they all end with !!!!
There are 94 in total.

Logic for reading rows and columns from a text file (textparser) C++

I'm really stuck with this problem I'm having for reading rows and columns from a text file. We're using text files that our prof gave us. I have the functionality running so when the user in puts "numrows (file)" the number of rows in that file prints out.
However, every time I enter the text files, it's giving me 19 for both. The first text file only has 4 rows and the other one has 7. I know my logic is wrong, but I have no idea how to fix it.
Here's what I have for the numrows function:
int numrows(string line) {
ifstream ifs;
int i;
int row = 0;
int array [10] = {0};
while (ifs.good()) {
while (getline(ifs, line)) {
istringstream stream(line);
row = 0;
while(stream >>i) {
array[row] = i;
row++;
}
}
}
}
and here's the numcols:
int numcols(string line) {
int col = 0;
int i;
int arrayA[10] = {0};
ifstream ifs;
while (ifs.good()) {
istringstream streamA(line);
col = 0;
while (streamA >>i){
arrayA[col] = i;
col++;
}
}
}
edit: #chris yes, I wasn't sure what value to return as well. Here's my main:
int main() {
string fname, line;
ifstream ifs;
cout << "---- Enter a file name : ";
while (getline(cin, fname)) { // Ctrl-Z/D to quit!
// tries to open the file whose name is in string fname
ifs.open(fname.c_str());
if(fname.substr(0,8)=="numrows ") {
line.clear();
for (int i = 8; i<fname.length(); i++) {
line = line+fname[i];
}
cout << numrows (line) << endl;
ifs.close();
}
}
return 0;
}
This problem can be more easily solved by opening the text file as an ifstream, and then using std::get to process your input.
You can try for comparison against '\n' as the end of line character, and implement a pair of counters, one for columns on a line, the other for lines.
If you have variable length columns, you might want to store the values of (numColumns in a line) in a std::vector<int>, using myVector.push_back(numColumns) or similar.
Both links are to the cplusplus.com/reference section, which can provide a large amount of information about C++ and the STL.
Edited-in overview of possible workflow
You want one program, which will take a filename, and an 'operation', in this case "numrows" or "numcols". As such, your first steps are to find out the filename, and operation.
Your current implementation of this (in your question, after editing) won't work. Using cin should however be fine. Place this earlier in your main(), before opening a file.
Use substr like you have, or alternatively, search for a space character. Assume that the input after this is your filename, and the input in the first section is your operation. Store these values.
After this, try to open your file. If the file opens successfully, continue. If it won't open, then complain to the user for a bad input, and go back to the beginning, and ask again.
Once you have your file successfully open, check which type of calculation you want to run. Counting a number of rows is fairly easy - you can go through the file one character at a time, and count the number that are equal to '\n', the line-end character. Some files might use carriage-returns, line-feeds, etc - these have different characters, but are both a) unlikely to be what you have and b) easily looked up!
A number of columns is more complicated, because your rows might not all have the same number of columns. If your input is 1 25 21 abs 3k, do you want the value to be 5? If so, you can count the number of space characters on the line and add one. If instead, you want a value of 14 (each character and each space), then just count the characters based on the number of times you call get() before reaching a '\n' character. The use of a vector as explained below to store these values might be of interest.
Having calculated these two values (or value and set of values), you can output based on the value of your 'operation' variable. For example,
if (storedOperationName == "numcols") {
cout<< "The number of values in each column is " << numColsVal << endl;
}
If you have a vector of column values, you could output all of them, using
for (int pos = 0; pos < numColsVal.size(); pos++) {
cout<< numColsVal[pos] << " ";
}
Following all of this, you can return a value from your main() of 0, or you can just end the program (C++ now considers no return value from main to a be a return of 0), or you can ask for another filename, and repeat until some other method is used to end the program.
Further details
std::get() with no arguments will return the next character of an ifstream, using the example code format
std::ifstream myFileStream;
myFileStream.open("myFileName.txt");
nextCharacter = myFileStream.get(); // You should, before this, implement a loop.
// A possible loop condition might include something like `while myFileStream.good()`
// See the linked page on std::get()
if (nextCharacter == '\n')
{ // You have a line break here }
You could use this type of structure, along with a pair of counters as described earlier, to count the number of characters on a line, and the number of lines before the EOF (end of file).
If you want to store the number of characters on a line, for each line, you could use
std::vector<int> charPerLine;
int numberOfCharactersOnThisLine = 0;
while (...)
{
numberOfCharactersOnThisLine = 0
// Other parts of the loop here, including a numberOfCharactersOnThisLine++; statement
if (endOfLineCondition)
{
charPerLine.push_back(numberOfCharactersOnThisLine); // This stores the value in the vector
}
}
You should #include <vector> and either specific std:: before, or use a using namespace std; statement near the top. People will advise against using namespaces like this, but it can be convenient (which is also a good reason to avoid it, sort of!)