Extract fields and values from text line using C++ - c++

I am reading in a text file with lines of the format:
date =20170422,line =10,index =3,field =partType,lock =productCode1,bookmark=2/19/56,
I need to extract the name of the field (date, line, index, etc.) and its corresponding value into char field[] and char value[] variables. If necessary, I am allowed to modify the format of the lines.
My initial thinking was to use while loops and check for = and , characters but it was getting messy and it seems like there may be a cleaner way.

You could do something like the below example. Split the string by commas using getline from your file, then split use an istringstream to and getline to split it again by an equals sign.
#include<iostream>
#include<fstream>
#include<string>
#include<sstream>
int main()
{
std::ifstream file("test.txt");
std::string wholeLine, partOfLine;
while(std::getline(file, wholeLine, ',')) {
std::istringstream wholeLineSS(wholeLine);
while(std::getline(wholeLineSS, partOfLine, '=')) {
std::cout<<partOfLine<<std::endl;
}
}
return 0;
}

The program I post here extracts the parameters from one or more strings which are formatted as you require. The function extract extracts all the parameters contained in a string (of the format you specified) and insert their names and values in a structure (struct sParms) array.
You may compile the program as extract and execute it at the system prompt as:
username: ./extract "date =20170422,line =10,index =3,field
=partType,lock =productCode1,bookmark=2/19/56,"
The output will be the following:
[date]=[20170422]
[line]=[10]
[index]=[3]
[field]=[partType]
[lock]=[productCode1]
[bookmark]=[2/19/56]
You may execute the program with more than one string:
username: ./extract "date =20170422,line =10,index =3,field
=partType,lock =productCode1,bookmark=2/19/56," "yes=1, no=0"
The output will be the following:
[date]=[20170422]
[line]=[10]
[index]=[3]
[field]=[partType]
[lock]=[productCode1]
[bookmark]=[2/19/56]
[yes]=[1]
[no]=[0]
In the following line there's the code:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <libgen.h>
//Parameters: date =20170422,line =10,index =3,field =partType,lock =productCode1,bookmark=2/19/56,
#define SEPARATOR ','
#define ASSIGNEMENT '='
typedef struct sParms {
char * fieldName;
char * fieldValue;
} tsParms;
int loadString(char *to, const char *from);
int extract(tsParms **oparms, const char *inb);
// Retrieve buffer length
int loadString(char *to, const char *from)
{
int len=0;
while(*from<=32 && *from!=SEPARATOR && *from!=ASSIGNEMENT)
from++;
// Get the string value
while(*from>32 && *from!=SEPARATOR && *from!=ASSIGNEMENT) {
*(to+len++)=*from;
++from;
}
*(to+len++)=0;
return len;
}
int extract(tsParms ** oparms, const char *inb)
{
int cnt=0,j;
const char * end, *equ, *start;
char * buff;
tsParms * parms;
if (inb == NULL || strlen(inb) == 0 || oparms == NULL)
return 0;
// It counts the number of parms
end=strchr(inb,ASSIGNEMENT);
while(end) {
cnt++;
end=strchr(end+1,ASSIGNEMENT);
}
if (!cnt)
return 0;
/* Doing some considerations we may assume that the memory to use to store
* fields name and values is the same of the input string (inb)
*
* The space to store the pointers is cnt * sizeof(tsParms *).
*/
j=cnt * sizeof(tsParms) + strlen(inb);
parms = malloc(j+1);
memset(parms,0,j+1);
buff = (char *)(parms+cnt); // The memory area where we can save data!
start=inb;end=start;cnt=0;
do {
end=strchr(start,SEPARATOR);
equ=strchr(start,ASSIGNEMENT);
if (equ) {
//Get the field name
parms[cnt].fieldName=buff;
buff+=loadString(buff,start);
//Get the field value
start=equ+1;
parms[cnt].fieldValue=buff;
buff+=loadString(buff,start);
cnt++;
}
if (end)
start=end+1;
} while(end);
*oparms = parms;
return cnt;
}
int main(int argc, char *argv[])
{
int i,j,cnt=0,retval=0;
tsParms * parms=NULL;
if (argc<2) {
printf("Usage: %s \"string-1\" [\"string-2\" ...\"string-n\"]\n",basename(argv[0]));
return 1;
}
for(i=1; i<argc; i++) {
cnt=extract(&parms, argv[i]);
if (cnt!=0 && parms!=NULL) {
for(j=0;j<cnt;j++) {
printf("[%s]=[%s]\n",parms[j].fieldName,parms[j].fieldValue);
}
puts("");
free((void *)parms);
} else {
retval=1;
break;
}
}
return retval;
}

Related

A function uses a different amount of memory when I call it with the same conditions the second time

I have a problem with C++ and memory. Here's the pseudocode:
main.cpp
#include <iostream>
#include "seq.h"
int main(int argc, char *argv[]) {
SnpSite snp_site("/mnt/c/Users/manht/Downloads/s_typhi_wong_holt.aln.gz");
snp_site.test(); // run the first time
snp_site.test(); // run the second time
}
seq.h
#include "file_handler.h"
#include <stdio.h>
class SnpSite {
private:
string inputfile;
FileHandler fh;
public:
SnpSite(char* _inputfile);
int is_unknown(char base);
void test();
};
seq.cpp
#include "seq.h"
SnpSite::SnpSite(char* _inputfile) {
fh = FileHandler();
inputfile = _inputfile;
}
void SnpSite::test() {
string sample_name, seq;
this->fh.open(this->inputfile.c_str());
this->fh.assign_next_sample_to(&sample_name, &seq);
this->fh.close();
}
file_handler.h
#ifndef SEQ_H_
#include <zlib.h>
#include <utility>
#include <ctype.h>
#include "my_string.h"
#include <string>
using namespace std;
#define SEQ_H_
typedef bool (*match_func)(int c, int delimiter);
class FileHandler {
private:
gzFile file;
char buffer[2048]; // Static allocation for better performance.
int buffer_start, buffer_end;
bool eof;
void get_until(int delimiter, string *s);
public:
FileHandler();
FileHandler(int _buffer_size);
void open(const char* filename);
void close();
void assign_next_sample_to(string *name, string *seq);
int next_char();
bool is_eof();
};
#endif
file_handler.cpp
#include "file_handler.h"
FileHandler::FileHandler() {
buffer_start = -1;
buffer_end = -1;
eof = false;
}
void FileHandler::open(const char* filename) {
file = gzopen(filename, "r");
eof = false;
}
void FileHandler::close() {
gzclose(file);
}
int FileHandler::next_char() {
/* Read current character and increase cursor (buffer_start) by 1.*/
if (buffer_start >= buffer_end) {
buffer_end = gzread(file, buffer, 2048);
buffer_start = -1;
if (buffer_end == 0) eof = true;
}
return buffer[++buffer_start];
}
bool FileHandler::is_eof() {
return eof;
}
#define SEP_SPACE 0 // isspace(): \t, \n, \v, \f, \r
#define SEP_TAB 1 // isspace() && !' '
#define SEP_LINE 2 // line separator: "\n" (Unix) or "\r\n" (Windows)
#define SEP_MAX 2
// list of function to compare c and delimiter, need exactly 2 arguments.
bool match_space(int c, int delimter) {
return isspace(c);
}
bool match_tab(int c, int delimter) {
return isspace(c) && c != ' ';
}
bool match_newline(int c, int delimter) {
return c == '\n';
}
bool match_char(int c, int delimter) {
return c == delimter;
}
bool no_match(int c, int delimiter) {
return false;
}
// end list.
void FileHandler::get_until(int delimiter, string *s) {
/*
Read till delimiter and append bytes read to s.
When done cursor will be at the end of the line.
*/
match_func match; // function to check if a char match delimiter
switch (delimiter) {
case SEP_SPACE:
match = match_space;
break;
case SEP_TAB:
match = match_tab;
break;
case SEP_LINE:
match = match_newline;
break;
default:
if (delimiter > SEP_MAX) match = match_char;
else match = no_match;
}
// begin process
int i = buffer_start;
while (!match(buffer[i], delimiter)) {
if (buffer_start >= buffer_end) {
buffer_end = gzread(file, buffer, 2048);
buffer_start = 0;
i = 0;
if (buffer_end == 0) {
eof = true;
break;
}
}
while (!match(buffer[i], delimiter) && i < buffer_end) i++;
s->append((char*)(buffer + buffer_start), i - buffer_start);
buffer_start = i;
}
}
/*
Get next sample name and sequence, assign it to *name and *seq.
(Note: this function do not read quality score for QUAL file).
*/
void FileHandler::assign_next_sample_to(string *name, string *seq) {
/* Get next sample name and sequence, assign it to *name and *seq.*/
name->erase();
seq->erase();
int c;
while (!eof && (c = next_char()) != '>' && c != '#') {} // read until meet sample name
get_until(SEP_SPACE, name); // get sample name
while (!eof && (c = next_char()) != '>' && c != '#' && c != '+') {
if (c == '\n') continue;
get_until(SEP_LINE, seq); // read sequence
}
buffer_start--; // step back to the end of sequence
}
I don't use any dynamic allocation, and when I traced memory usage by PID in htop, I found something that I can't explain:
The first time I call test():
At the beginning of the function, my process uses 6168 KBytes.
At the end of the function, my process uses 13998 Kbytes.
The second time I call test():
At the beginning of the function, my process uses 6304 Kbytes.
At the end of the function, my process uses 21664 Kbytes.
The length of the seq variable is 4809037 and sample_name is 11 in both cases. I don't understand why memory usage is so different between them. Hope someone can find out and explain it to me, it helps me a lot. Thanks
This happens because of this line:
s->append((char*)(buffer + buffer_start), i - buffer_start);
Strings are dynamically allocated and every time the initial size is exceeded a new larger memory block is allocated. You can read more about this here: Chapter 4. Optimize String Use: A Case Study.

C++ error: munmap_chunk(): invalid pointer

My issue I assume is with &line.at(0). I had an error with it before, but dereferencing it took away the error. I am not sure why that is, as I was under the impression that my getline function would return the first line (one integer value that is the size of an array to sort), and the next would be a string of integers, separated by a space, that you put into the array that you then sort.
/* This Program is the main entry point
* in order to call insertion_sort
*/
#include <insertion_sort.cpp>
#include <utils.hpp>
#include <iostream>
#include <fstream>
#include <sstream>
#include <stdlib.h>
using namespace std;
int main(int argc, char const *argv[])
{
/**
* YOUR CODE HERE
* 1. Check for the number of arguments.
* Only proceed if the number of argument is correct,
* We will only check for ./main [INPUT_FILE] [OUTPUT_FILE]
*
*/
if(argc == 3){
/**
* YOUR CODE HERE
* 2. Read the file [INPUT_FILE]
*
*/
ifstream fin;
ofstream fout;
fin.open(argv[1]);
if (!fin.is_open()){
return -1;
}
string line;
string line2;
getline(fin, line);
getline(fin, line2);
int size = atoi(&line.at(0));
int iterator = 0;
/**
* YOUR CODE HERE
* 3. Build IntSequence
*/
IntSequence unsorted_array;
unsorted_array.length = size;
unsorted_array.array = new int[unsorted_array.length];
for (unsigned int i = 0; i < line2.length(); i++){
if (&line2.at(i) != " "){
unsorted_array.array[iterator] = atoi(&line2.at(i));
iterator += 1;
}
/**
* YOUR CODE HERE
* 4. Run Insertion Sort
*/
insertion_sort(unsorted_array);
/**
* YOUR CODE HERE
* 5. Write the file into [OUTPUT_FILE]
*/
fout.open(argv[2]);
if (fout.is_open()){
for (unsigned int i = 0; i < unsorted_array.length; i++){
fout << unsorted_array.array[i] << " ";
}
} else {
return -1;
}
}
fin.close();
fout.close();
return 0;
} else {
return -1;
}
}
In this line it seems that you are comparing a char * to another char *.
if (&line2.at(i) != " "){
I think that you intended to compare a char to a char instead, in other words check that the current character is not a space to then convert it to an integer.
if (line2.at(i) != ' '){
Otherwise your expression inside your if statement would likely return true even when you didn't count on it returning true, potentially overflowing your int array due to adding duplicate integers, since atoi ignores whitespace.

get function returns invalid value

I'm working on a code for a school project which I can't use strings.
I'm having problems getting the value for hourlyPay.
The program's output:
5 Christine Kim 4.94066e-324
Although, the file contains the following:
5 Christine Kim 30.00
#include <iostream>
#include <iomanip>
#include <fstream>
#include <cstring>
using namespace std;
//Class decleration
class Employee
{
private:
int id; //Employee ID.
char name[21]; //Employee name.
double hourlyPay; //Pay per hour.
public:
Employee(int initId=0, char [] =0, double initHourlyPay=0.0); //Constructor.
bool set(int newId, char [], double newHourlyPay);
int getId() { return id; }
const char * getName() { return name;}
double getHourlyPay() { return hourlyPay;}
};
Employee::Employee( int initId, char initName[], double initHourlyPay)
{
bool status = set( initId, initName, initHourlyPay);
if ( !status )
{
id = 0;
strcpy(name, "");
hourlyPay = 0.0;
}
}
bool Employee::set( int newId, char newName[], double newHourlyPay)
{
bool status = false;
if ( newId > 0)
{
status = true;
id = newId;
strcpy(name, newName);
hourlyPay = newHourlyPay;
}
return status;
}
const int MAX_SIZE = 100;
int main()
{
int id; //Employee ID.
char newName[21];
double hourlyPay; //Pay per hour.
Employee list[15]; //Array to store
ifstream masterFile; //Opens master file.
masterFile.open("master10.txt");
int count = 0;
if (masterFile)
{
for (count; count < 2; count++)
{
masterFile >> id;
masterFile.ignore();
masterFile.getline(newName, 21);
masterFile >> hourlyPay;
list[count].set(id, newName, hourlyPay);
}
}
masterFile.close(); //Close master file.
cout << list[0].getId() << " " << list[0].getName() << " " << list[0].getHourlyPay();
}
The original file contains more lines, but I narrowed it down in order to figure out my error.
What am I doing wrong?
I have figured it out.
#include <iostream>
#include <iomanip>
#include <fstream>
#include <cstring>
using namespace std;
//Class decleration
class Employee
{
private:
int id; //Employee ID.
char name[21]; //Employee name.
double hourlyPay; //Pay per hour.
public:
Employee(int initId=0, char [] =0, double initHourlyPay=0.0); //Constructor.
bool set(int newId, char [], double newHourlyPay);
int getId() { return id; }
const char * getName() { return name;}
double getHourlyPay() { return hourlyPay;}
};
Employee::Employee( int initId, char initName[], double initHourlyPay)
{
bool status = set( initId, initName, initHourlyPay);
if ( !status )
{
id = 0;
strcpy(name, "");
hourlyPay = 0.0;
}
}
bool Employee::set( int newId, char newName[], double newHourlyPay)
{
bool status = false;
if ( newId > 0)
{
status = true;
id = newId;
strcpy(name, newName);
hourlyPay = newHourlyPay;
}
return status;
}
const int MAX_SIZE = 100;
int main()
{
int id; //Employee ID.
char newName[21];
double hourlyPay; //Pay per hour.
Employee list[15]; //Array to store
ifstream masterFile; //Opens master file.
masterFile.open("master10.txt");
int count = 0;
if (masterFile)
{
for (count; count < 2; count++)
{
masterFile >> id;
masterFile.ignore();
masterFile.get(newName, 21);
masterFile >> hourlyPay;
list[count].set(id, newName, hourlyPay);
}
}
masterFile.close(); //Close master file.
cout << list[0].getId() << " " << list[0].getName() << " " << list[0].getHourlyPay();
}
I only changed getline to get and now it can read in the middle of a line with a limit of 20 chars.
I appreciate everyone's attention and help.
Data from the file is not correctly entered into the variables in your code. Here's a solution which is self explainatory.
for (count; count < 1; count++)
{
char secondname[11];
masterFile >> id;
masterFile.ignore();
masterFile >> newName;
masterFile >> secondname;
masterFile >> hourlyPay;
strcat(newName, " ");
strcat(newName, secondname);
list[count].set(id, newName, hourlyPay);
}
It's not a good idea to contain a name with blank space in the line, if you insist to do this, I think we can read the line and split the name and the hourlyPlay. The better way is that not contain blank space in the name, but use character like _, and replace the character with blank space after reading, the you can simple use >> to read each field
I modified a little to support multiline reading & printing, check the source:
#include <iostream>
#include <iomanip>
#include <fstream>
#include <cstring>
#include <vector>
using namespace std;
//Class decleration
class Employee
{
private:
int id; //Employee ID.
char name[21]; //Employee name.
double hourlyPay; //Pay per hour.
public:
Employee(int initId = 0, const char* = 0, double initHourlyPay = 0.0); //Constructor.
bool set(int newId, const char*, double newHourlyPay);
int getId() { return id; }
const char * getName() { return name; }
double getHourlyPay() { return hourlyPay; }
};
Employee::Employee(int initId, const char* initName, double initHourlyPay)
{
bool status = set(initId, initName, initHourlyPay);
if (!status)
{
id = 0;
strcpy(name, "");
hourlyPay = 0.0;
}
}
bool Employee::set(int newId, const char* newName, double newHourlyPay)
{
bool status = false;
if (newId > 0)
{
status = true;
id = newId;
strcpy(name, newName);
hourlyPay = newHourlyPay;
}
return status;
}
int main()
{
int id; //Employee ID.
double hourlyPay; //Pay per hour.
vector<Employee> list; //Array to store
ifstream masterFile; //Opens master file.
char line[256];
masterFile.open("master10.txt");
if (masterFile)
{
while (!masterFile.eof())
{
masterFile >> id;
masterFile.getline(line, sizeof(line));
char* last_word = strrchr(line, ' ');
line[last_word - line] = 0;
hourlyPay = atof(last_word + 1);
list.push_back(Employee(id, line, hourlyPay));
}
}
//Close master file.
masterFile.close();
for (size_t i = 0; i < list.size(); ++i)
cout << list[i].getId() << " " << list[i].getName() << " " << list[i].getHourlyPay() << endl;
}
So.. you really want to do this with a plain old array of Employee and plain old character arrays and cstring?
There is absolutely nothing wrong with doing it that way -- and it will sure make you appreciate the convenience of using string and vector. But, that said, there is a wealth of valuable learning that can be gained from walking a pointer (or couple of pointers) through each line of data to parse the id, name & hourlyPay from each line of data.
The fly-in-the-ointment of the entire parsing process is not knowing how many spaces may be contained in name (it could have none, one, two, three, ...). Though, things are not as dire as they may appear. You know you have an int that begins the line of data and a double at the end -- everything left in between the whitespace after the int and before the double is your name.
The key here is really to read each line of data into a buffer (character array) as a cstring. Then you can use the standard tool strtol to read the id and advance a pointer to 1-past the last digit in id. You can then simply iterate forward on a character-by-character basis checking if (isspace(*p)) and continuing to advance until a non-whitespace character is found (or you hit the nul-terminating character at the end). Once you find your non-whitespace character -- you have a pointer set to the beginning of name.
Now you have to work on the other end and back up until you find the space before hourlyPay. Not that difficult. You will need strlen (buf), but at least using masterFile.getline(..) you are saved the need of trimming the '\n' from the end of the buffer by overwriting with a nul-terminating character. Simply set your end-pointer to buf + len - 1 and you are sitting on the last digit of hourlyPay. Then in similar manner, it is just a matter up backing up while (ep > sp && !isspace (*p)) (you know if your end-pointer ever reaches your start-pointer sitting at the beginning of name the parse has failed)
Now remember, here you are 1-character before the beginning of hourlyPay, so when you go to convert hourlyPay using strtod, you must remember to use p + 1 as the start of the cstring-segment for converting hourlyPay. As with any strtoX conversion, you have two primary tests (1) that following the conversion the start-pointer is not equal to the endptr parameter indicating that digits were actually converted to a number and (2) errno was not set during conversion -- indicating a failure in the actual conversion. (and when converting to a smaller type than the conversion -- e.g. to int using strtol -- you must check that the value converted is within the range of int before assigning to your value.
Now you have your id and hourlyPay -- all that is left is backing up from the beginning of hourlyPay to the end of name. You do this in like manner checking isspace() until it is no longer true, and that your pointer to the end of name is still greater than your pointer to the start of name. Now you can use strncpy to copy the name to your variable for newName by copying p - sp + 1 characters (remember you are sitting on the last character with p so you will need to add 1 to get all characters in name.
Putting it altogether and providing comments inline below, you could do something like the following (noting that your original class and member functions were left unchanged -- only the parse of the id, name & hourlyPay in main() was dramatically affected) As always -- the key is to validate each step -- then you can have confidence in the data you are processing.
#include <iostream>
#include <iomanip>
#include <fstream>
#include <cstring>
#include <cctype>
#include <limits>
#define MAXLIST 16 /* if you need constants, define one (or more) */
#define MAXNM 64
#define MAXBUF 1024
#define FNAME "dat/master10.txt"
using namespace std;
//Class decleration
class Employee
{
private:
int id; //Employee ID.
char name[MAXNM]; //Employee name.
double hourlyPay; //Pay per hour.
public:
Employee (int initId=0, char [] =0, double initHourlyPay=0.0);
bool set (int newId, char [], double newHourlyPay);
int getId() { return id; }
const char *getName() { return name;}
double getHourlyPay() { return hourlyPay;}
};
Employee::Employee (int initId, char initName[], double initHourlyPay)
{
bool status = set(initId, initName, initHourlyPay);
if (!status)
{
id = 0;
strcpy(name, "");
hourlyPay = 0.0;
}
}
bool Employee::set (int newId, char newName[], double newHourlyPay)
{
bool status = false;
if (newId > 0) {
status = true;
id = newId;
strcpy(name, newName);
hourlyPay = newHourlyPay;
}
return status;
}
int main (void) {
int id, //Employee ID.
count = 0;
long tmp; /* tmp for strtol conversion */
char newName[MAXNM] = "",
buf[MAXBUF] = ""; /* line buffer */
double hourlyPay; //Pay per hour.
Employee list[MAXLIST]; //Array to store
ifstream masterFile (FNAME); //Opens master file.
if (!masterFile.is_open()) { /* validate file open for reading */
cerr << "error: file open failed '" << FNAME << "'\n";
return 1;
}
/* read each line in masterFile into buf */
while (count < MAXLIST && masterFile.getline (buf, sizeof buf))
{
size_t len = strlen (buf); /* get length */
char *sp = buf, /* start pointer */
*p = buf + len - 1, /* working pointer */
*ep = NULL; /* end pointer for strtod conversion */
/* parse and convert id, leaving sp 1-past last digit */
errno = 0; /* zero errno before strtol conversion */
tmp = strtol (buf, &sp, 0); /* store conversion in tmp */
if (buf == sp) { /* validate characters were converted */
cerr << "error: no digits converted in id.\n";
return 1;
}
if (errno != 0) { /* validation errno not set */
cerr << "error: failed converstion for id.\n";
return 1;
}
/* validate tmp within range of integer */
if (tmp < numeric_limits<int>::min() ||
numeric_limits<int>::max() < tmp) {
cerr << "error: id not within integer range.\n";
return 1;
}
id = (int)tmp; /* assign tmp to id */
/* advance sp to 1st char in name */
while (*sp && isspace (*sp))
sp++;
/* parse hourlyPay */
/* work backward with p until space before hourlyPay found
* always validate p > sp so you don't back up beyond the start of
* name (or the beginning of buf).
*/
while (p > sp && !isspace (*p))
p--;
if (p > sp && !isdigit(*(p + 1))) { /* validate next char is digit */
cerr << "error: failed to parse hourlyPay.\n";
return 1;
}
errno = 0; /* zero errno before strtol conversion */
hourlyPay = strtod (p+1, &ep); /* convert hourlyPay to double */
if (p + 1 == ep) { /* validate characters were converted */
cerr << "error: no digits converted in hourlyPay.\n";
return 1;
}
if (errno != 0) { /* validation errno not set */
cerr << "error: failed converstion for hourlyPay.\n";
return 1;
}
/* continue working backwards to end of name */
while (p > sp && isspace (*p))
p--;
if (p <= sp) { /* validate chars between sp & p */
cerr << "error: failed to find end of name.\n";
return 1;
}
len = p - sp + 1; /* get number of chars in name */
if (len > MAXNM - 1) { /* validate it will fit in newName */
cerr << "error: name exceeds" << len << "characters.\n";
return 1;
}
strncpy (newName, sp, len); /* copy name to newName */
/* set values in list[count], on success, increment count */
if (list[count].set (id, newName, hourlyPay))
count++;
}
masterFile.close(); //Close master file.
/* outoput all employee id and hourlyPay information */
for (int i = 0; i < count; i++)
cout << list[i].getId() << " " << list[i].getName() <<
" " << list[i].getHourlyPay() << '\n';
}
(now you see why string and the other C++ tools make things much nicer?)
Example Input File
The only data you posted was used as the input file, e.g.
$ cat dat/master10.txt
5 Christine Kim 30.00
Example Use/Output
$ ./bin/employeepay
5 Christine Kim 30
Look things over and let me know if you have further questions.

Sorting strings with qsort does not work

I have a program, that asks several strings and should sort them.
My code is:
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#define MAX_STR_LEN 256
int myStrCmp (const void * a, const void * b)
{
return strcmp((const char *)a, (const char *)b);
}
int main(void)
{
int strNum; // expected number of input strings
int strCnt; // counter of strings
char ** storage; // pointr to the memory when strings are stored
char strBuf[ MAX_STR_LEN]; // buffer for strings
char * strPtr;
// input of strings number
do{
printf("How many strings will be entered: ");
while( scanf("%d", &strNum) != 1)
{
printf("ERROR: Not number was entered!\n");
while( getchar() != '\n' );
printf("Please enter a number: ");
}
if( strNum < 2 )
{
printf("ERROR: Number less than 2 was entered!\n");
}
while( getchar() != '\n' );
}
while(strNum < 2);
// allocation of memory for pointers
storage = (char **) calloc(strNum, sizeof(char*) );
if( storage == NULL )
{
printf("ERROR: Unexpected problems with memory allocation!\n");
return 1;
}
// input of strings
for( strCnt = 0; strCnt < strNum; strCnt++)
{
printf("Enter string #%d:\n", strCnt + 1);
fgets(strBuf, MAX_STR_LEN, stdin);
strPtr = strchr(strBuf, '\n');
if( strPtr )
{
*strPtr = '\0';
}
else
{
strBuf[ MAX_STR_LEN - 1] = '\0';
}
// allocation memory for particular string
storage[strCnt] = (char *) malloc(strlen(strBuf) + 1);
if(storage[strCnt] == NULL)
{
printf("ERROR: Unexpected problems with memory allocation!\n");
return 2;
}
// move string to dynamic memory
strcpy(storage[strCnt], strBuf);
}
// sort the strings
qsort(storage, strNum, sizeof(char**), myStrCmp);
// output the result
printf("\nSorted strings:\n");
for( strCnt = 0; strCnt < strNum; strCnt++)
{
printf("%s\n", storage[strCnt]);
}
return 0;
}
The simplest test shows the trouble:
How many strings will be entered: 3
Enter string #1:
ddd
Enter string #2:
aaa
Enter string #3:
ccc
Sorted strings:
ddd
aaa
ccc
I have tryed Visual C++ and gcc, but the result is the same. Please, say me what is wrong in the code?
The problem is in myStrCmp function.
Because a and b are elements in not a simple array, but in array of pointers, their type must be char ** and function that compares two elements have to be as follows:
int myStrCmp (const void * a, const void * b)
{
return strcmp(*(const char **)a, *(const char **)b);
}

Printing input string words in reverse order

Using if and while/do-while, my job is to print following user's inputs (string value) in reverse order.
For example:
input string value : "You are American"
output in reverse order : "American are You"
Is there any way to do this?
I have tried
string a;
cout << "enter a string: ";
getline(cin, a);
a = string ( a.rbegin(), a.rend() );
cout << a << endl;
return 0;
...but this would reverse the order of the words and spelling while spelling is not what I'm going for.
I also should be adding in if and while statements but do not have a clue how.
The algorithm is:
Reverse the whole string
Reverse the individual words
#include<iostream>
#include<algorithm>
using namespace std;
string reverseWords(string a)
{
reverse(a.begin(), a.end());
int s = 0;
int i = 0;
while(i < a.length())
{
if(a[i] == ' ')
{
reverse(a.begin() + s, a.begin() + i);
s = i + 1;
}
i++;
}
if(a[a.length() - 1] != ' ')
{
reverse(a.begin() + s, a.end());
}
return a;
}
Here is a C-based approach that will compile with a C++ compiler, which uses the stack to minimize creation of char * strings. With minimal work, this can be adapted to use C++ classes, as well as trivially replacing the various for loops with a do-while or while block.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAX_LINE_LENGTH 1000
#define MAX_WORD_LENGTH 80
void rev(char *str)
{
size_t str_length = strlen(str);
int str_idx;
char word_buffer[MAX_WORD_LENGTH] = {0};
int word_buffer_idx = 0;
for (str_idx = str_length - 1; str_idx >= 0; str_idx--)
word_buffer[word_buffer_idx++] = str[str_idx];
memcpy(str, word_buffer, word_buffer_idx);
str[word_buffer_idx] = '\0';
}
int main(int argc, char **argv)
{
char *line = NULL;
size_t line_length;
int line_idx;
char word_buffer[MAX_WORD_LENGTH] = {0};
int word_buffer_idx;
/* set up line buffer - we cast the result of malloc() because we're using C++ */
line = (char *) malloc (MAX_LINE_LENGTH + 1);
if (!line) {
fprintf(stderr, "ERROR: Could not allocate space for line buffer!\n");
return EXIT_FAILURE;
}
/* read in a line of characters from standard input */
getline(&line, &line_length, stdin);
/* replace newline with NUL character to correctly terminate 'line' */
for (line_idx = 0; line_idx < (int) line_length; line_idx++) {
if (line[line_idx] == '\n') {
line[line_idx] = '\0';
line_length = line_idx;
break;
}
}
/* put the reverse of a word into a buffer, else print the reverse of the word buffer if we encounter a space */
for (line_idx = line_length - 1, word_buffer_idx = 0; line_idx >= -1; line_idx--) {
if (line_idx == -1)
word_buffer[word_buffer_idx] = '\0', rev(word_buffer), fprintf(stdout, "%s\n", word_buffer);
else if (line[line_idx] == ' ')
word_buffer[word_buffer_idx] = '\0', rev(word_buffer), fprintf(stdout, "%s ", word_buffer), word_buffer_idx = 0;
else
word_buffer[word_buffer_idx++] = line[line_idx];
}
/* cleanup memory, to avoid leaks */
free(line);
return EXIT_SUCCESS;
}
To compile with a C++ compiler, and then use:
$ g++ -Wall test.c -o test
$ ./test
foo bar baz
baz bar foo
This example unpacks the input string one word at a time,
and builds an output string by concatenating in reverse order.
`
#include <iostream>
#include <sstream>
using namespace std;
int main()
{
string inp_str("I am British");
string out_str("");
string word_str;
istringstream iss( inp_str );
while (iss >> word_str) {
out_str = word_str + " " + out_str;
} // while (my_iss >> my_word)
cout << out_str << endl;
return 0;
} // main
`
This uses exactly one each of if and while.
#include <string>
#include <iostream>
#include <sstream>
void backwards(std::istream& in, std::ostream& out)
{
std::string word;
if (in >> word) // Read the frontmost word
{
backwards(in, out); // Output the rest of the input backwards...
out << word << " "; // ... and output the frontmost word at the back
}
}
int main()
{
std::string line;
while (getline(std::cin, line))
{
std::istringstream input(line);
backwards(input, std::cout);
std::cout << std::endl;
}
}
You might try this solution in getting a vector of string's using the ' ' (single space) character as a delimiter.
The next step would be to iterate over this vector backwards to generate the reverse string.
Here's what it might look like (split is the string splitting function from that post):
Edit 2: If you don't like vectors for whatever reason, you can use arrays (note that pointers can act as arrays). This example allocates a fixed size array on the heap, you may want to change this to say, double the size when the current word amount has reached a certain value.
Solution using an array instead of a vector:
#include <iostream>
#include <string>
using namespace std;
int getWords(string input, string ** output)
{
*output = new string[256]; // Assumes there will be a max of 256 words (can make this more dynamic if you want)
string currentWord;
int currentWordIndex = 0;
for(int i = 0; i <= input.length(); i++)
{
if(i == input.length() || input[i] == ' ') // We've found a space, so we've reached a new word
{
if(currentWord.length() > 0)
{
(*output)[currentWordIndex] = currentWord;
currentWordIndex++;
}
currentWord.clear();
}
else
{
currentWord.push_back(input[i]); // Add this character to the current word
}
}
return currentWordIndex; // returns the number of words
}
int main ()
{
std::string original, reverse;
std::getline(std::cin, original); // Get the input string
string * arrWords;
int size = getWords(original, &arrWords); // pass in the address of the arrWords array
int index = size - 1;
while(index >= 0)
{
reverse.append(arrWords[index]);
reverse.append(" ");
index--;
}
std::cout << reverse << std::endl;
return 0;
}
Edit: Added includes, main function, while loop format
#include <vector>
#include <string>
#include <iostream>
#include <sstream>
// From the post
std::vector<std::string> &split(const std::string &s, char delim, std::vector<std::string> &elems)
{
std::stringstream ss(s);
std::string item;
while(std::getline(ss, item, delim)) {
elems.push_back(item);
}
return elems;
}
std::vector<std::string> split(const std::string &s, char delim) {
std::vector<std::string> elems;
return split(s, delim, elems);
}
int main ()
{
std::string original, reverse;
std::cout << "Input a string: " << std::endl;
std::getline(std::cin, original); // Get the input string
std::vector<std::string> words = split(original, ' ');
std::vector<std::string>::reverse_iterator rit = words.rbegin();
while(rit != words.rend())
{
reverse.append(*rit);
reverse.append(" "); // add a space
rit++;
}
std::cout << reverse << std::endl;
return 0;
}
This code here uses string libraries to detect the blanks in the input stream and rewrite the output sentence accordingly
The algorithm is
1. Get the input stream using getline function to capture the spacecs. Initialize pos1 to zero.
2. Look for the first space in the input stream
3. If no space is found, the input stream is the output
4. Else, get the position of the first blank after pos1, i.e. pos2.
5. Save the sub-string bewteen pos1 and pos2 at the beginning of the output sentence; newSentence.
6. Pos1 is now at the first char after the blank.
7. Repeat 4, 5 and 6 untill no spaces left.
8. Add the last sub-string to at the beginning of the newSentence. –
#include <iostream>
#include <string>
using namespace std;
int main ()
{
string sentence;
string newSentence;
string::size_type pos1;
string::size_type pos2;
string::size_type len;
cout << "This sentence rewrites a sentence backward word by word\n"
"Hello world => world Hello"<<endl;
getline(cin, sentence);
pos1 = 0;
len = sentence.length();
pos2 = sentence.find(' ',pos1);
while (pos2 != string::npos)
{
newSentence = sentence.substr(pos1, pos2-pos1+1) + newSentence;
pos1 = pos2 + 1;
pos2 = sentence.find(' ',pos1);
}
newSentence = sentence.substr(pos1, len-pos1+1) + " " + newSentence;
cout << endl << newSentence <<endl;
return 0;
}