grabbing data sets from a file with an arbitrary amount of spaces - c++

**No direct answers or code examples please, this is my homework which i need to learn from. I'm looking for help concerning the algorithm i need to develop.
I seem to be having a logic error in coming up with a solution for a portion of my class work, the program involves multiple files, but here is the only relevant portion:
I have a file PlayerStats that holds the stats for a basketball player in:
rebounds
points
assists
uniform #
my initial reaction would be to create a while loop and read these into a temporary struct that holds these values, then create a merge function that merges the values of the temp struct with the inital array of records, simple enough?
struct Baller
{
//other information on baller
int rebounds;
int assists;
int uniform;
int points;
void merge(Baller tmp); //merge the data with the array of records
}
//in my read function..
Baller tmp;
int j = 0;
inFile << tmp.uniform << tmp.assists << tmp.points << tmp.rebounds
while(inFile){
ArrayRecords[j].merge(tmp);
j++;
//read in from infile again
}
The catch:
The file can have an arbitrary number of spaces between the identifiers, and the information can be in any order(leaving out the uniform number, that is always first). e.g.
PlayerStats could be
11 p3 a12 r5 //uniform 11, 3 points 12 assists 5 rebounds
//other info
OR
11 p 3 r 5 a 12 //same exact values
What I've come up with
can't seem to think of an algorithm to grab these values from the file in the correct order, i was thinking of something along these lines:
inFile << tmp.uniform; //uniform is ALWAYS first
getline(inFile,str); //get the remaining line
int i = 0;
while(str[i] == " ") //keep going until i find something that isnt space
i++;
if(str[i] == 'p') //heres where i get stuck, how do i find that number now?
else if(str[i] == 'a')
eles if(str[i] = 'r'

If you're only going to check one letter, you could use a switch statement instead of if / else, that would make it easier to read.
You know where the number starts at that point, (hint: str[i+1]), so depending on what type your str[] is, you can either use atoi if its a char array, or std::stringstream if it's an std::string.
I'm tempted to give you some code, but you said not too. If you do want some, let me know and I'll edit the answer with some code.
Instead of using a 'merge' function, try using an std::vector so you can just push_back your structure instead of doing any 'merging'. Besides, your merge function is basically a copy assignment operator, which is created by the compiler by default (you don't need to create a 'merge' function), you just need to use = to copy the data across. If you wanted to do something special in your 'merge' function, then you should overload the copy assignment operator instead of a 'merge' function. Simples.

Do something like that:
int readNumber () {
while isdigit (nextchar) -> collect in numberstring or directly build number
return that number;
}
lineEater () {
Read line
skip over spaces
uniform=readNumber ();
haveNum=false;
haveWhat=false;
Loop until eol {
skip over spaces
if (isdigit)
number=readNumber ();
skip over spaces
haveNum=true;
else
char=nextChar;
haveWhat=true;
if (haveChar and haveNum) {
switch (char) {
case 'p' : points=number; break;
...
}
haveNum=false;
haveWhat=false;
}
}
or, if you are more ambitous, write a grammar for your input and use lex/yacc.

Related

How do I use flag variables to produce output for a program designed to search an array for a string provided via user input?

I'm working on an ungraded practice assignment and am struggling with the C++ code required. The assignment parameters are to write a program which takes a string provided by user input, searches an array containing 10 elements, and sets the value of a flag variable depending on whether or not user input matches a string contained within the array. The program then outputs a phrase if the string was not found, as determined by referencing the value of the flag variable.
I have the following code so far:
// MichiganCities.cpp - This program prints a message for invalid cities in Michigan.
// Input: Interactive
// Output: Error message or nothing
#include <iostream>
#include <string>
using namespace std;
int main()
{
// Declare variables
string inCity; // name of city to look up in array
const int NUM_CITIES = 10;
// Initialized array of cities
string citiesInMichigan[] = {"Acme", "Albion", "Detroit", "Watervliet", "Coloma", "Saginaw", "Richland", "Glenn", "Midland", "Brooklyn"};
bool foundIt = false; // Flag variable
int x; // Loop control variable
// Get user input
cout << "Enter name of city: ";
cin >> inCity;
// Write your loop here
for(x=0; x<NUM_CITIES; x++){
// Write your test statement here to see if there is
// a match. Set the flag to true if city is found.
if(citiesInMichigan[x] == inCity)[
set foundIt = true,;
break;
]
}
// Test to see if city was not found to determine if
// "Not a city in Michigan" message should be printed.
if(foundIt == false){
cout << "City not in Michigan.";
return 0;
}
} // End of main()
I'm fairly certain what I've got here should do what I'm trying to do, but I get syntax errors requesting brackets [] in odd places and I'm lost on what I'm doing wrong.
I'm not looking for someone to provide correct code for me to simply copy, as I'm trying to learn. I'm looking for someone who can explain what I've done wrong, what rules I'm breaking, and/or what steps I can take to get this code working.
You have the right idea, but have a couple of syntactic mistakes.
First, blocks in C++ (and many other languages), are denoted by curly braces ({ and }), not square brackets like you have in your condition.
Second, setting a value to a variable is done by the assignment operator, = (i.e., somevariable = somevalue). There is no "set" keyword in C++.
To put those two points together, the condition inside the loop should look like this:
if (citiesInMichigan[x] == inCity) {
foundIt = true;
break;
}

Edit string by calling it using concatenation in C++

I'm a very new C++ user (and a new StackOverflow user at that), and I'm trying to code a very basic Tic-Tac-Toe (Naughts and Crosses) game. I'm not sure how to render the board as it is updated.
My main question is if it is possible to call a string using concatenation. I have an array set up that indexes the states of the 9 spaces of the board using a 0 for empty, a 1 for an X, and a 2 for an O. If I set up 9 variables in a user-defined renderBoard() function named bit1, bit2, etc; Can I call them this way:
void renderBoard()
{
int i = 1;
string bit1;
string bit2;
string bit3;
string bit4;
string bit5;
string bit6;
string bit7;
string bit8;
string bit9;
while (i < 10)
{
if (Spaces[i] = 0)
{
(bit + i) = * //This is the main bit I'm wondering about
}
else
{
//Check for 1, 2, and edit the string bits accordingly
}
++i;
}
//Put all of the strings together, as well as some more strings for adding the grid
//Output the whole concatenated string to the command line
}
If anyone knows of a better way to do this, please let me know. I've tried Googling and rifling through various C++ help websites, but I find it difficult to express my particular case through anything other than a long-winded and specific explanation.
Thanks for you help!!
If I correctly understood your problem, your problem is that you want to access the strings named bit1, bit2, etc using a variable i like bit + i.
And no, you cannot do that!
It will throw a compile time error.
Please correct me if I didn't get what you are looking for.
But one question is still in my mind that why are you using string variables bit1, bit2 etc?
I think you just want to store single digit value in those strings. If this is the case, you can just use a single string of length 9.
You can do this as follows:
int i = 0; //because string indices start from 0 and also array indices.
string bit(9, ' '); //declare a string of length 9 with default value of a space (you can modify it with your default value)
while (i < 9) { // i < 9 because highest index will be 8
if (Spaces[i] == 0) {
bit[i] = '*';
} else {
}
++i;
}
Declaring 9 variables like this is apparently wrong. What you are looking for is an array.
std::array<std::string, 9> bits;
(You need #include <array> and #include <string>.)
Then, you can traverse the string using a for-loop: (in C++, arrays are indexed starting from zero, not one)
for (std::size_t i = 0; i < 9; ++i) {
// operate on bits[i]
}
In the for-loop, you can use the subscript operator to access the element: bits[i].
Finally, to put all the strings together, use std::accumulate:
std::accumulate(bits.begin(), bits.end(), std::string{})
(You need #include <numeric>.)

Key Value Pair implementation C

I have a .txt file that stores student names along with two of their best marks. If a student for some reason, i.e. dropping out of course, fails to pass a course, then no marks are recorded.
My file looks like this
Samuel= 90.5, 95.9
Bill= 25.2, 45.3
Tim
Anthony= 99.9, 12.5
Mark
Rob
Basically, Tim, Mark and Rob failed the course and hence their marks are not stored. Also to differentiate between a failed mark and a pass mark, I have used the = symbol. Basically, I want to store all the names into memory alongside their associated values.
This is my implementation, however it is flawed in the sense that I have declared a double *marks[2] array to store all six marks, when clearly it will only store 3. I am having trouble storing the values into the double array.
This is my code...
istream& operator>> (istream& is, Students& student)
{
student.names = new char*[6];
for (int i=0; i<10; i++)
{
student.names[i] = new char[256];
student.marks[i] = new double[2];
is.getline(student.names[i], sizeof(student.names));
for (int j=0; j < 256; j++)
{
if((student.names[i][j] == '='))
{
int newPos = j + 1;
for (int k = newPos; k < 256; k++)
{
student.names[i][k - newPos] = student.names[k];
}
}
}
}
}
How can I go about storing the values of the students with the valid marks? Please, no use of vectors or stringstreams, just pure C/C++ char arrays
You have a few options, you could use a struct like so
struct Record {
std::string name;
double marks[2];
};
And then stick that into something like std::vector<Record> or an array of them like
Records *r = new Records[1000];
You could also keep three different arrays (either automatically allocated or dynamically allocated or even std::vector), one to hold the name, two to hold the marks.
In each case you would just indicate a fail by some thing like the marks being zero.
Also, you can use
std::string name;
double first, second;
std::cin >> name;
if (name[name.size() - 1] == '=')
std::cin >> first >> second;
And this will parse the input like you want it to for a single line. Once you've done that you can wrap the whole thing in a loop while sticking the values you get into some sort of data structure that I already described.
Hope that gives you a few ideas on where to go!
Here's a strategy:
First of all you need to implement a struct to hold the key-value pair, I suggest the following:
struct Student {
char name[30];
double marks[2];
};
Note that you can give the dimension of the char array inside the struct if you know that the length will never be higher. (which is given here)
Now what you need is to know how many lines are in your ifstream, you could make a loop of is.getline() calls to get there. (don't forget to call is.clear() and is.seekg(0) when finished, to be at the beginning for the real loop)
When you know how many lines are in your ifstream you can use dynamically cast the Array of your struct with the actual length of your file:
Student * students = new Student[lineCount]; // line count of is
As you can see, there's no need to have a std::vector to hold the values. Consider that the getline() loop may be an overkill just to get the line count, alternatively you could give a length to Students at compile-time by making an array with a length that will never be overpassed.
(e.g. Student students[128];)
Now you need to parse the lines, i'd suggest you make a loop like the following (line by line):
// int parseLine ( char* line, char* name, double* marks ) { ...
bool hasMarks=false;
int iLine=0; // Line pos iterator
int iName=0; // Name pos iterator
char mk1Str[4]; // String buffer, Mark 1
char mk2Str[4]; // String buffer, Mark 2
while(line[iLine]!='\0')
{
if(line[iLine]=='=')
{
hasMarks=true;
name[iLine]='\0';
for(int iMark=0;iMark<4;iMark++)
{
mk1Str[iMark]=line[iLine+iMark+2];
mk2Str[iMark]=line[iLine+iMark+8];
// ^^ You can harcode the offsets (2,8) since they don't change
}
break;
}
name[iName++]=line[iLine];
iLine++;
}
Now what you need is to parse the marks to double values, for this you could use the atof() function that works with char*. The bool hasMarks helps you know if a student has defined marks, if not, you could define dummy values like -1 for the mark fields of your struct.
I think this works quite well for your case...

Recursive String Transformations

EDIT: I've made the main change of using iterators to keep track of successive positions in the bit and character strings and pass the latter by const ref. Now, when I copy the sample inputs onto themselves multiple times to test the clock, everything finishes within 10 seconds for really long bit and character strings and even up to 50 lines of sample input. But, still when I submit, CodeEval says the process was aborted after 10 seconds. As I mention, they don't share their input so now that "extensions" of the sample input work, I'm not sure how to proceed. Any thoughts on an additional improvement to increase my recursive performance would be greatly appreciated.
NOTE: Memoization was a good suggestion but I could not figure out how to implement it in this case since I'm not sure how to store the bit-to-char correlation in a static look-up table. The only thing I thought of was to convert the bit values to their corresponding integer but that risks integer overflow for long bit strings and seems like it would take too long to compute. Further suggestions for memoization here would be greatly appreciated as well.
This is actually one of the moderate CodeEval challenges. They don't share the sample input or output for moderate challenges but the output "fail error" simply says "aborted after 10 seconds," so my code is getting hung up somewhere.
The assignment is simple enough. You take a filepath as the single command-line argument. Each line of the file will contain a sequence of 0s and 1s and a sequence of As and Bs, separated by a white space. You are to determine whether the binary sequence can be transformed into the letter sequence according to the following two rules:
1) Each 0 can be converted to any non-empty sequence of As (e.g, 'A', 'AA', 'AAA', etc.)
2) Each 1 can be converted to any non-empty sequences of As OR Bs (e.g., 'A', 'AA', etc., or 'B', 'BB', etc) (but not a mixture of the letters)
The constraints are to process up to 50 lines from the file and that the length of the binary sequence is in [1,150] and that of the letter sequence is in [1,1000].
The most obvious starting algorithm is to do this recursively. What I came up with was for each bit, collapse the entire next allowed group of characters first, test the shortened bit and character strings. If it fails, add back one character from the killed character group at a time and call again.
Here is my complete code. I removed cmd-line argument error checking for brevity.
#include <iostream>
#include <fstream>
#include <string>
#include <iterator>
using namespace std;
//typedefs
typedef string::const_iterator str_it;
//declarations
//use const ref and iterators to save time on copying and erasing
bool TransformLine(const string & bits, str_it bits_front, const string & chars, str_it chars_front);
int main(int argc, char* argv[])
{
//check there are at least two command line arguments: binary executable and file name
//ignore additional arguments
if(argc < 2)
{
cout << "Invalid command line argument. No input file name provided." << "\n"
<< "Goodybe...";
return -1;
}
//create input stream and open file
ifstream in;
in.open(argv[1], ios::in);
while(!in.is_open())
{
char* name;
cout << "Invalid file name. Please enter file name: ";
cin >> name;
in.open(name, ios::in);
}
//variables
string line_bits, line_chars;
//reserve space up to constraints to reduce resizing time later
line_bits.reserve(150);
line_chars.reserve(1000);
int line = 0;
//loop over lines (<=50 by constraint, ignore the rest)
while((in >> line_bits >> line_chars) && (line < 50))
{
line++;
//impose bit and char constraints
if(line_bits.length() > 150 ||
line_chars.length() > 1000)
continue; //skip this line
(TransformLine(line_bits, line_bits.begin(), line_chars, line_chars.begin()) == true) ? (cout << "Yes\n") : (cout << "No\n");
}
//close file
in.close();
return 0;
}
bool TransformLine(const string & bits, str_it bits_front, const string & chars, str_it chars_front)
{
//using iterators so store current length as local const
//can make these const because they're not altered here
int bits_length = distance(bits_front, bits.end());
int chars_length = distance(chars_front, chars.end());
//check success rule
if(bits_length == 0 && chars_length == 0)
return true;
//Check fail rules:
//1. next bit is 0 but next char is B
//2. bits length is zero (but char is not, by previous if)
//3. char length is zero (but bits length is not, by previous if)
if((*bits_front == '0' && *chars_front == 'B') ||
bits_length == 0 ||
chars_length == 0)
return false;
//we now know that chars_length != 0 => chars_front != chars.end()
//kill a bit and then call recursively with each possible reduction of front char group
bits_length = distance(++bits_front, bits.end());
//current char group tracker
const char curr_char_type = *chars_front; //use const so compiler can optimize
int curr_pos = distance(chars.begin(), chars_front); //position of current front in char string
//since chars are 0-indexed, the following is also length of current char group
//start searching from curr_pos and length is relative to curr_pos so subtract it!!!
int curr_group_length = chars.find_first_not_of(curr_char_type, curr_pos)-curr_pos;
//make sure this isn't the last group!
if(curr_group_length < 0 || curr_group_length > chars_length)
curr_group_length = chars_length; //distance to end is precisely distance(chars_front, chars.end()) = chars_length
//kill the curr_char_group
//if curr_group_length = char_length then this will make chars_front = chars.end()
//and this will mean that chars_length will be 0 on next recurssive call.
chars_front += curr_group_length;
curr_pos = distance(chars.begin(), chars_front);
//call recursively, adding back a char from the current group until 1 less than starting point
int added_back = 0;
while(added_back < curr_group_length)
{
if(TransformLine(bits, bits_front, chars, chars_front))
return true;
//insert back one char from the current group
else
{
added_back++;
chars_front--; //represents adding back one character from the group
}
}
//if here then all recursive checks failed so initial must fail
return false;
}
They give the following test cases, which my code solves correctly:
Sample input:
1| 1010 AAAAABBBBAAAA
2| 00 AAAAAA
3| 01001110 AAAABAAABBBBBBAAAAAAA
4| 1100110 BBAABABBA
Correct output:
1| Yes
2| Yes
3| Yes
4| No
Since a transformation is possible if and only if copies of it are, I tried just copying each binary and letter sequences onto itself various times and seeing how the clock goes. Even for very long bit and character strings and many lines it has finished in under 10 seconds.
My question is: since CodeEval is still saying it is running longer than 10 seconds but they don't share their input, does anyone have any further suggestions to improve the performance of this recursion? Or maybe a totally different approach?
Thank you in advance for your help!
Here's what I found:
Pass by constant reference
Strings and other large data structures should be passed by constant reference.
This allows the compiler to pass a pointer to the original object, rather than making a copy of the data structure.
Call functions once, save result
You are calling bits.length() twice. You should call it once and save the result in a constant variable. This allows you to check the status again without calling the function.
Function calls are expensive for time critical programs.
Use constant variables
If you are not going to modify a variable after assignment, use the const in the declaration:
const char curr_char_type = chars[0];
The const allows compilers to perform higher order optimization and provides safety checks.
Change data structures
Since you are perform inserts maybe in the middle of a string, you should use a different data structure for the characters. The std::string data type may need to reallocate after an insertion AND move the letters further down. Insertion is faster with a std::list<char> because a linked list only swaps pointers. There may be a trade off because a linked list needs to dynamically allocate memory for each character.
Reserve space in your strings
When you create the destination strings, you should use a constructor that preallocates or reserves room for the largest size string. This will prevent the std::string from reallocating. Reallocations are expensive.
Don't erase
Do you really need to erase characters in the string?
By using starting and ending indices, you overwrite existing letters without have to erase the entire string.
Partial erasures are expensive. Complete erasures are not.
For more assistance, post to Code Review at StackExchange.
This is a classic recursion problem. However, a naive implementation of the recursion would lead to an exponential number of re-evaluations of a previously computed function value. Using a simpler example for illustration, compare the runtime of the following two functions for a reasonably large N. Lets not worry about the int overflowing.
int RecursiveFib(int N)
{
if(N<=1)
return 1;
return RecursiveFib(N-1) + RecursiveFib(N-2);
}
int IterativeFib(int N)
{
if(N<=1)
return 1;
int a_0 = 1, a_1 = 1;
for(int i=2;i<=N;i++)
{
int temp = a_1;
a_1 += a_0;
a_0 = temp;
}
return a_1;
}
You would need to follow a similar approach here. There are two common ways of approaching the problem - dynamic programming and memoization. Memoization is the easiest way of modifying your approach. Below is a memoized fibonacci implementation to illustrate how your implementation can be speeded up.
int MemoFib(int N)
{
static vector<int> memo(N, -1);
if(N<=1)
return 1;
int& res = memo[N];
if(res!=-1)
return res;
return res = MemoFib(N-1) + MemoFib(N-2);
}
Your failure message is "Aborted after 10 seconds" -- implying that the program was working fine as far as it went, but it took too long. This is understandable, given that your recursive program takes exponentially more time for longer input strings -- it works fine for the short (2-8 digit) strings, but will take a huge amount of time for 100+ digit strings (which the test allows for). To see how your running time goes up, you should construct yourself some longer test inputs and see how long they take to run. Try things like
0000000011111111 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBBBBAAAAAAAA
00000000111111110000000011111111 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBBBBAAAAAAAA
and longer. You need to be able to handle up to 150 digits and 1000 letters.
At CodeEval, you can submit a "solution" that just outputs what the input is, and do that to gather their test set. They may have variations so you may wish to submit it a few times to gather more samples. Some of them are too difficult to solve manually though... the ones you can solve manually will also run very quickly at CodeEval too, even with inefficient solutions, so there's that to consider.
Anyway, I did this same problem at CodeEval (using VB of all things), and my solution recursively looked for the "next index" of both A and B depending on what the "current" index is for where I was in a translation (after checking stoppage conditions first thing in the recursive method). I did not use memoization but that might've helped speed it up even more.
PS, I have not run your code, but it does seem curious that the recursive method contains a while loop within which the recursive method is called... since it's already recursive and should therefore encompass every scenario, is that while() loop necessary?

Making an array of pointers to structs or objects in C++

So I'm basically just trying to take in some file input, and then take that data and put it into several structs. The only issue I'm having is with the naming of the pointers to the structs. The struct's themselves are supposed to represent students and I wanted to set each pointer as one of their names rather than an arbitrary variable. I tried to do this in a way that I'm assuming is syntactically wrong for it didn't work. In the code below, I increment the for loop with the temp array because each 4th position is a new student. Any ideas on how I could go about this?
#include<iostream>
#include<iomanip>
#include"student.h"
#include"creditcard.h"
#include<fstream>
using namespace std;
int main ()
{
string creditcards[20];
int i;
int x;
int amount;
string temp[20];
ifstream infile;
string filename;
int count;
int numstudents;
string newstring="";
string pointers[20];
cout<<"enter the file name of which you've stored your"<<endl
<<"credit card infomation"<<endl;
getline(cin,filename,'\n');
infile.open(filename.c_str());
count=0;
getline(infile,temp[count],'\n');
while(! infile.eof())
{
count++;
getline(infile,temp[count],'\n');
numstudents= (count/4);
if(numstudents < 1 || count%4 != 0)
{
cout<<"incorrect data file"<<endl;
}
}
cout<<numstudents<<endl;
for(i=0,x=0; i<numstudents;i++,x+4)
{
student *temp[x];
temp[x] = new student;
pointers[i] = temp[x];
}
for(i=0;i<numstudents;i+4)
{
cout<<temp[i]<<endl;
}
return 0;
}
Ok, let's start from the top.
Your code was (before I reformatted it) a mess. Messy code is harder to read and more likely to have bugs.
You have 3 arrays, each containing 20 strings. Why do you need so many?
One of them is named temp; having to use that as a variable name is a good indicator that you're mishandling data somewhere.
You're declaring int count relatively early on, then initializing it to 0 later. While not necessarily a bad thing, that's not the best method (do both at once, when needed).
You can declare local variables more than one in a line, but you don't need to declare them all at the top of the function. That's not necessary in C++.
int main ()
{
string creditcards[20];
int i = 0, x = 0, amount = 0;
(legal, but might not be needed)
It's typically better to declare and initialize a variable at the same time, just before you need it:
int count = 0;
getline(infile, temp[count], '\n');
I remember seeing that reading until you hit eof isn't recommended, although I'm not entirely sure on that. You may want to change this:
while ( !infile.eof() )
{
Now, the first actual mistake I see here is that you read a line, increment count, then read another line before acting. Is that intentional, and if so, why is it necessary? Doing the getline and increment inside the loop would be more readable and potentially more reliable.
count++;
getline(infile, temp[count], '\n');
This line is a bug, I think:
for(i=0,x=0; i<numstudents;i++,x+4)
The last section does i++, x+4. It does not change x.
The next loop after that handles i in the same way this loop uses x, so you can probably combine those two.
Now, on top of all that, massive temp arrays are not the solution to this problem (or any other that I can think of).
To store this kind of data, you'll want to look into a std::map<std::string, student*> or std::vector<student*>. The vector will allow you to push the new student struct to the back when necessary, and the map will allow you to key them based on name and retrieve that later, something like so:
typdef map<string, student*> studentmap;
studentmap students;
studentmap::iterator iter = students.find("Bob");
if ( iter != students.end() )
{
student * bob = iter->second;
// Work with data
}
It's a much better way of handling this, and will take a lot of the guess work out of what you're doing now.
If you want to be able to reference the students by name, consider using a map<string, student> or map<string, student*>.
This will allow you to refer to individual students via students["Jack"] or students["Jill"].