My name is Jose. I need help with a project. I need to handle .csv files in C++. The file contains nit, date and amount spent. The program must accumulate the purchase totals by NIT and must print on screen:
Sum NITs:
Average NITs
Min NITs
Max NITs
Count NITs
This following are links tot he csv files with nit, date, and total spent
I am trying to create output similar to:
My current codes is:
#include<iostream>
#include<fstream>
#include<string.h>
#include<stdlib.h>
#include<vector>
#include<sstream>
using namespace std;
void mostrar_csv();
int main()
{
mostrar_csv();
system("pause");
return 0;
}
void mostrar_csv()
{
ifstream archivo("archivo.csv");
string linea = "";
string escritura = "";
vector<string> vect;
while (getline(archivo, linea))
{
stringstream dato(linea);
while (getline(dato, escritura, ';'))
{
vect.push_back(escritura);
}
}
for (int i = 0; i < vect.size(); i++)
{ // EL .size literalmente es un metodo, es el tamaño que tiene el vector
cout << i + 1 << ".-- " << vect.at(i) << "\n";
}
cout << "\n\n";
cout << "the size is " << " " << vect.size() << " \n\n ";
}
See a full description below.
But first the example code (one of many possible solutions):
#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <numeric>
#include <iterator>
#include <regex>
#include <map>
#include <tuple>
#include <algorithm>
#include <iomanip>
std::regex delimiter(",");
using Data = std::tuple<unsigned long, std::string, double>;
int main() {
// Open the file and check if it could be opened
if (std::ifstream csvFileStream{ "r:\\archivo.csv" }; csvFileStream) {
// Here we will store all data
std::vector<Data> data;
// Now read every line of the file until eof
for (std::string line{}; std::getline(csvFileStream, line); ) {
// Split the line into tokens
std::vector token(std::sregex_token_iterator(line.begin(), line.end(), delimiter, -1), {});
// Add to our data vector
data.emplace_back(Data{ std::stoul(token[0]), std::string(token[1]), std::stod(token[2]) });
}
// Now we want to aggregate the data. Get the sum over all
const double sum = std::accumulate(data.begin(), data.end(), 0.0, [](double v, const Data& d) { return v + std::get<2>(d); });
// Get the average over all
const double average = sum / data.size();
// Get the min and max value over all.
const auto [min, max] = std::minmax_element(data.begin(), data.end(), [](const Data& d1, const Data& d2) { return std::get<2>(d1) < std::get<2>(d2); });
// Next, we want to group based on NIT
std::map<unsigned long, double> groups{};
for (const Data& d : data) groups[std::get<0>(d)] += std::get<2>(d);
// Generate output
std::cout << "No. NIT Total Vendido\n";
unsigned int no{ 1U };
for (const auto& [NIT, gsum] : groups)
std::cout << std::right << std::setw(3) << no++ << ' ' << std::left << std::setw(9) << NIT
<< std::right << std::fixed << std::setprecision(2) << std::setw(19) << gsum << "\n";
std::cout << " ---------------\nSumatoria NITS:" << std::setw(17) << sum
<< "\nMedia NITs :" << std::setw(17) << average << "\nMin NITS :" << std::setw(17) << std::get<2>(*min)
<< "\nMax NITS :" << std::setw(17) << std::get<2>(*max) << "\nCount NITs :" << std::setw(14) << groups.size() << "\n";
}
else {
std::cerr << "\n*** Error: Could not open csv file\n";
}
return 0;
}
One of the major topics here is, how to parse a string or, it is called like this, how to split a string into tokens.
Splitting strings into tokens is a very old task. In very early C there was the function strtok, which still exists, even in C++. Here std::strtok.
But because of the additional functionality of std::getline is has been heavily misused for tokenizing strings. If you look on the top question/answer regarding how to parse a CSV file (please see here), then you will see what I mean.
People are using std::getline to read a text line, a string, from the original stream, then stuffing it into an std::istringstream and use std::getline with delimiter again to parse the string into tokens. Weird.
But, since many many many years, we have a dedicated, special function for tokenizing strings, especially and explicitly designed for that purpose. It is the
std::sregex_token_iterator
And since we have such a dedicated function, we should simply use it.
This thing is an iterator. For iterating over a string, hence the function name is starting with an s. The begin part defines, on what range of input we shall operate, then there is a std::regex for what should be matched / or what should not be matched in the input string. The type of matching strategy is given with last parameter.
0 --> give me the stuff that I defined in the regex and (optional)
-1 --> give me that what is NOT matched based on the regex.
We can use this iterator for storing the tokens in a std::vector. The std::vector has a range constructor, which takes 2 iterators as parameter, and copies the data between the first iterator and 2nd iterator to the std::vector. The statement
std::vector tokens(std::sregex_token_iterator(s.begin(), s.end(), re, -1), {});
defines a variable “tokens” as a std::vector and uses the so called range-constructor of the std::vector. Please note: I am using C++17 and can define the std::vector without template argument. The compiler can deduce the argument from the given function parameters. This feature is called CTAD ("class template argument deduction").
Additionally, you can see that I do not use the "end()"-iterator explicitly.
This iterator will be constructed from the empty brace-enclosed default initializer list with the correct type, because it will be deduced to be the same as the type of the first argument due to the std::vector constructor requiring that.
You can read any number of tokens in a line and put it into the std::vector
But you can do even more. You can validate your input. If you use 0 as last parameter, you define a std::regex that even validates your input. And you get only valid tokens.
Additionally, it helps you to avoid the error that you made, with the last getline statement.
Overall, the usage of a dedicated functionality is superior over the misused std::getline and people should simple use it.
Some people may complain about the function overhead, but how many of them are using big data. And even then, the approach would be probably then to use string.findand string.substring or std::stringviews or whatever.
Now we should have gotten a basic understanding, how to split a string into tokens.
Next, we will explore the rest os the software.
At the beginning we open a file and check, if it has been open. We use the new existing if statement, where you can put an initializer and the condition in the (). So, we define a variable std::ifstream an use its constructor to open the file. That was the initializer. Then we put the stream as condition as the 2nd part of the if-statement. This will check, if the file could be opened or not. That works, because the std::ifstreams !-operator is overwritten and will return a boolean state of the stream.
OK, now the file is open. With a normal for-statement, we read all lines of the file, using std::getline.
Then we tokenize the line (the string). Our data per line (csv) consists of 3 values. An unsigned long, a std::string and a double. We define a Type "Data" to be a tuple of those types.
The tokens for each line will be converted and put into the std::tuple via in-place construction and the tuple will then be added to our target vector.
So, basically we need just 3 lines of code, to read and parse the complete source csv-file.
Good. Now we have all data in a std::vector "data".
We can use existing functions from the algorithm library for getting the sum, average, min and max value.
Since we want to group the data based on the NIT, we then create an associative container: std::map. The key is the NIT and the value is the sum of the doubles. With the std::map index operator [] we can access or create a new key. Meaning, when a NIT is not existing in the map, then it will be added. In any case, the index operator [] will return a reference to the value. And we simply add the double to the value of the map. This we do for all tuples in the data-vector.
After this, all group sums exist, and the number of keys in the map, the size() of the std::map is the number of groups.
The rest is just simple formatiing and output.
Related
So I got this code, I input three numbers. Now I want to display them in a for loop. From smallest to biggest, and in another loop from biggest to smallest. How can I do this?
int main()
{
int num1, num2, num3;
cout << "Enter first num" << endl;
cin >> num1;
cout << "Enter second num" << endl;
cin >> num2;
cout << "Enter third num" << endl;
cin >> num3;
}
I have done this like this but I think it's not a proper way to do it.
for(int i = 0; i <= 0; i++) {
cout << num1;
cout << num2;
cout << num3;
}
for(int i = 0; i <= 0; i++) {
cout << num3;
cout << num2;
cout << num1;
}
Edit:
is this better?
int main()
{
int numbers[3];
cout << "Enter first num" << endl;
cin >> numbers[0];
cout << "Enter second num" << endl;
cin >> numbers[1];
cout << "Enter third num" << endl;
cin >> numbers[2];
for (int i = 0; i <= 2; i++)
{
cout << numbers[i];
}
for (int i = 2; i >= 0; i--)
{
cout << numbers[i];
}
return 0;
}
What you are trying to do is very common in programming languages. I.e. enter data, process it and output it in one form or another.
A loop is what you use if you want to execute a piece of code a number of times... a number more than 1 that is. In your first example you execute the loop once, making the extra lines of code a bit redundant.
You already updated your code, showing you quickly realized how to used arrays. These types of arrays (i.e. int numbers[3];) are often referred to as C-style arrays, as they were inherited from C. In C++ we can now use std::array<int, 3> numbers;, which more adheres to the C++ style of working. (The std:: part of the type name indicates the type is define in the standard library namespace. The type is part of the C++ language standard.)
The problem with these two types is that they have a static(=fixed) size. I.e. the size has to be know at compile time. This can be quite a limitation if you don't know how many items the user wants to enter. However, the C++ standard defines other container types which can hold a variable amount of items. Of these, std::vector is the dynamic (variable) size counterpart of the array: these both store their items sequentially in memory.
So you can for instance use a loop to add (to the back = push_back()) a number of elements selected by the user to the vector.
#include <vector>
#include <iostream>
[...]
std::vector<int> numbers;
std::cout << "How many numbers do you want to enter?\n";
int N;
std::cin >> N;
numbers.reserve(N); // good practice to reserve memory if you know the number of elements
for (int i = 0; i < N; ++i) {
std::cout << "Enter a number: ";
int number;
std::cin >> number;
numbers.push_back(number);
}
[...]
Note that there is no check on the input: e.g. if the user would enter "-1" after the first question, things would break. I will not consider handling user error in my answer.
You can already see some code duplication in here: cout, type definition, cin. You can extract this in a separate function.
#include <string>
[...]
int ReadUserInput(std::string const& message) {
std::cout << message;
int value;
std::cin >> value;
return value;
}
or even better, you make a function template. I.e. a template for a function: the compiler will generate implementations of this function for you, depending on the type T inferred. I also use the std::string_view now, which can view to different types of strings (std::string, char*)
#include <string_view>
[...]
template<typename T>
T ReadUserInput(std::string_view message = "") {
if (!message.empty()) std::cout << message; //only print is there's an actual message
T value;
std::cin >> value;
return value;
}
Next, the C++ library has more to offer, including a number of algorithms that are commonly used in programming. One of these is a generator that repeatedly calls a function, of which the result is used to assign successive elements in a container. The object that points to a specific element in the container is called an iterator. The C++ standard library offers a convenient iterator type that executes a push_back: the std::back_inserter. The previous code can now be reduced to:
int const N = ReadUserInput<int>("How many numbers do you want to enter?\n");
std::vector<int> numbers;
numbers.reserve(N); // good practice to reserve memory if you know the number of elements
std::generate_n(back_inserter(numbers), N, ReadUserInputGenerator<int>("Enter a number: "));
"But wait", you might ask, "what is this ReadUserInputGenerator?". Well, to make the generate_n work, you need to pass a pointer/handle to a generator function, which is then executed for each element. If we'd just call ReadUserInput<int>("Enter a number: "), then the function would already have been evaluated. So we need to add another intermediate function object that makes this generator function. In the passed we would make a class for this
template<typename T>
class ReadUserInputGenerator {
private:
std::string_view message;
public:
ReadUserInputGenerator(std::string_view message = "") : message(message) {}
T operator()() const { return ReadUserInput(message); }
};
... but now we can do using lambda expressions
template<typename T>
auto ReadUserInputGenerator = [](std::string_view message = "") {
return [message]() { return ReadUserInput<T>(message); };
};
(note to more experienced readers: I'm not really sure about passing string_view by value. I based it on this)
SOOOOO, now we finally have our input. Next you wanted to sort it. Again, this is a common operation. There are actually many ways to sort a collection a values and it is a good excersize to implement these yourself... However, like I mentioned previously, as these kind of operations are common in programming, the C++ standard library actually offers an algorithm for sorting: std::sort.
std::sort(begin(numbers), end(numbers));
^ here begin and end refer to iterators pointing to the begin and end (or actually one past the end) of your vector. You could sort only part of your vector this way. However, the most common case is just begin to end, so in C++20 they've introduced the ranges algorithms, and the previous statement can be reduced to
std::ranges::sort(numbers);
... AAAND now its sorted... printing is next. You can print using a loop... but even there you will have a number of choices. And indexed loop:
for (int i = 0; i < numbers.size(); ++i) {
std::cout << numbers[i] << ' ';
}
An iterator based for loop:
for (auto it = cbegin(numbers); it != cend(numbers); ++it) {
std::cout << *it << ' ';
}
Note: the 'c' before begin and end denote that it is a "const" qualified iterator, i.e. it may not modify the contents of the object it points to.
Or a range based for loop:
for (int number : numbers) {
std::cout << number << ' ';
}
There is also a special convenience iterator type that can push to the cout: std::ostream_iterator. You can copy the vector to this iterator using the algorithm std::copy
std::copy(cbegin(numbers), cend(numbers), std::ostream_iterator<int>(std::cout, " "));
Note, the second argument of the ostream_iterator is the delimiter, i.e. the string appended after each element. Of course there's also a C++20 ranges version.
std::ranges::copy(numbers, std::ostream_iterator<int>(std::cout, " "));
... FINALLY reversing.
One option is just to reverse all elements in the vector and print them out again using one of the above mentioned methods. There's of course an algorithm to do so: std::reverse.
std::reverse(begin(numbers), end(numbers));
However, this operation modifies the contents of the container(vector), which might be costly. If you don't want to to this, you'll have to loop though your vector in reverse order
for (int i = numbers.size() - 1; i >= 0; --i) {
std::cout << numbers[i] << ' ';
}
This looks complex, and it's easy to make errors.
You could instead use the reverse iterators of vector, to traverse through the vector in reverse order: (you need to add an 'r' to the begin/end)
for (auto it = crbegin(numbers); it != crend(numbers); ++it) {
std::cout << *it << ' ';
}
or
std::copy(crbegin(numbers), crend(numbers), std::ostream_iterator<int>(std::cout, " "));
For C++20, there's no range operation to reverse the vector. However, they introduced "views" that are used to observe the values in the vector in a specific way. One such a way is "observe it in reverse order": std::ranges::view::reverse. So in C++20 you will be able to do:
for (int number : numbers | std::views::reverse) {
std::cout << number << ' ';
}
or
std::ranges::copy(numbers | std::views::reverse, std::ostream_iterator<int>(std::cout, " "));
which both don't modify numbers.
The end code could look a little bit something like this (pre C++20 version):
#include <vector>
#include <iostream>
#include <string_view>
#include <iterator>
#include <algorithm>
template<typename T>
T ReadUserInput(std::string_view message = "") {
if (!message.empty()) std::cout << message; //only print is there's an actual message
T value;
std::cin >> value;
return value;
}
template<typename T>
auto ReadUserInputGenerator = [](std::string_view message = "") {
return [message]() { return ReadUserInput<T>(message); };
};
int main() {
int const N = ReadUserInput<int>("How many numbers do you want to enter?\n");
std::vector<int> numbers;
numbers.reserve(N); // good practice to reserve memory if you know the number of elements
std::generate_n(back_inserter(numbers), N, ReadUserInputGenerator<int>("Enter a number: "));
std::sort(begin(numbers), end(numbers));
std::copy(cbegin(numbers), cend(numbers), std::ostream_iterator<int>(std::cout, " "));
std::cout << '\n';
std::copy(crbegin(numbers), crend(numbers), std::ostream_iterator<int>(std::cout, " "));
std::cout << '\n';
}
Given that the input doesn't seem to have any meaning other than "some numbers", you should use a container, and the obvious choice would be:
std::vector<int> nums;
From c++20, you don't needs loops at all for this problem, because you can use ranges:
#include<ranges>
namespace rs = std::ranges;
namespace rv = std::views;
and now you can read in numbers like this:
rs::copy_n(std::istream_iterator<int>(std::cin), 3,
std::back_inserter(nums));
I'm not sure if you want to use the order that the user inputs the numbers, but if you want the actual smallest to largest, you can do:
rs::sort(nums);
Now to print out the numbers:
rs::copy(nums,
std::ostream_iterator<int>(std::cout, " "));
and in reverse:
rs::copy(nums | rv::reverse,
std::ostream_iterator<int>(std::cout, " "));
Here's a demo.
You don't need a loop, the one you have it's not really a loop in the sense that it only cycles once, you can use a chained ostream:
cout << num1 << " " << num2 << " " << num3 << "\n";
And
cout << num3 << " " << num2 << " " << num1 << "\n";
But if you want print them sorted by value and you can't use some container where you can apply a sorting algorithm, you'll need some conditionals.
EDIT: Is this better?
Storing it in an array makes it easier to deal with the data, for instance, it will allow you to sort it by value using something as simple as <algorithm> library std::sort.
I am getting few problems when reading code. This is the text file.
2X^6+3X^3+4X^0=0
5X^6+X^2+X^1-4X^0=0
I am getting a proper input for the first line but in second line first I need to ignore. I searched here and found how to use it and it's work to get to next line ignoring all the left over characters of first line.
You can see in second line with X there is no integer, now problem is the second while loop is running continuously. If I add 1 in text file with the X the file reads perfectly. Also how can I put a condition to satisfy this that when there is directly X or -X, it should store 1 or -1 and goes to next character? Also you can see ^ I have to store this in a variable whereas I should ignore it but didn't how to ignore it?
Thanks in advance
int main()
{
int in;
int power;
char x;
char f;
fstream fin;
fin.open("input1.txt");
list l1,l2;
while(fin.peek() != 61)
{
fin>>in;
fin>>x;
fin>>f;
fin>>power;
cout<<in<<endl<<x<<endl<<f<<endl<<power<<endl;
l1.addtoend(in,power,x);
cout<<endl;
}
fin.ignore(2,'\n');
while(fin.peek() != 61)
{
fin>>in;
fin>>x;
fin>>f;
fin>>power;
cout<<in<<endl<<x<<endl<<f<<endl<<power<<endl;
l2.addtoend(in,power,x);
cout<<endl;
}
l1.display();
l2.display();
}
Unfortunately, this is not so simple as expected.
We need to split up the task into smaller parts.
What you want to do, is splitting your equation in terms and extract from this the coefficients and exponents.
Splitting up something in similar parts is also called tokenizing.
So, your equation consists of terms, which all follow the same pattern. First an optional sign, followed by the coefficients, then a “X^”, and, at the end the exponent (which may or may not have a sign).
And since all terms have the same pattern, we can find them with a so-called regex. C++ supports this functionality. Also for splitting up a text in smaller tokens/terms/pattern-matches, we have a special iterator std::sregex_token_iterator. Like any other iterator in C++, it iterates over the source string and extracts (and copies) all matched patterns.
OK, then we found already a solution for the first sub task. Extract all terms and put them into a std::vector. We will use the std::vectors range constructor, to do this, while defining the variable.
The next step is to get the coefficient. Here we need some special handling, because the coefficient can be omitted with an assumed 1. Using this assumption, we will read the term and convert the coefficient to an integer. And because we want to do that in one statement, we use std::transform from the STL’s algorithm library.
Getting the exponents is easier. We simply convert anything in a term following the ‘^’-sign to an integer. We again use std::transform to work on all terms in one statement.
Last but not least, we will get the right-hand-side of the equation and convert it also to an integer.
Please note:
All this can be done also with float type values
We could also allow spaces in the equation
For that, we would simple modify the std::regex-string.
Please see the complete example below:
#include <iostream>
#include <string>
#include <vector>
#include <iterator>
#include <regex>
#include <algorithm>
#include <iomanip>
int main() {
std::string equation{ "5X^6+X^2+X^1-4X^0=0" };
const std::regex re(R"(([+-]?\d?X\^[+-]?\d+))");
std::vector<std::string> terms{ std::sregex_token_iterator(equation.begin(), equation.end(), re,1),std::sregex_token_iterator() };
std::vector<int> coefficients(terms.size());
std::vector<int> exponents(terms.size());
int rightHandSite{ 0 };
// Everything in front of X is the coefficient. Handle special case, when no digit is given
std::transform(terms.begin(), terms.end(), coefficients.begin(), [](const std::string& s) {
std::string temp = s.substr(0U, s.find('X'));
if (1 == temp.size() && !std::isdigit(temp[0])) temp += '1';
return std::stoi(temp); });
// Get all exponents
std::transform(terms.begin(), terms.end(), exponents.begin(), [](const std::string & s) {
return std::stoi(s.substr(s.find('^') + 1)); });
// Get right Hand site of equation
rightHandSite = std::stoi(equation.substr(equation.find('=') + 1));
// Show result
std::cout << "\nEquation: " << equation << "\n\nFound '" << terms.size() << "' terms.\n\nCoeffient Exponent\n";
for (size_t i = 0U; i < terms.size(); ++i)
std::cout << std::right << std::setw(9) << coefficients[i] << std::setw(10) << exponents[i] << "\n";
std::cout << "\n --> " << rightHandSite << "\n";
return 0;
}
There are many other possible solutions. But maybe it will give you some idea on what you could do.
I am a beginner in C++ and I am currently working with strings.
My question is why when compiling the code I'm providing below, I can get the string's characters when I use index notation, but cannot get the string itself using cout?
This is the code:
#include <iostream>
#include <string>
using namespace std;
int main()
{
string original; // original message
string altered; // message with letter-shift
original = "abc";
cout << "Original : " << original << endl; // display the original message
for(int i = 0; i<original.size(); i++)
altered[i] = original[i] + 5;
// display altered message
cout << altered[0] << " " << altered[1] << " " << altered[2] << endl;
cout << "altered : " << altered << endl;
return 0;
}
When I run this, the characters in the string altered are displayed correctly with this line:
cout << altered[0] << " " << altered[1] << " " << altered[2] << endl;
But the string itself is not displayed with this line:
cout << "altered : " << altered << endl;
I would like to know why this happens.
You have not resized your altered string to fit the length of the original string before the loop, thus your code exhibits undefined behavior:
altered[i] = original[i] + 5; // UB - altered is empty
To fix this, resize altered before the loop:
altered.resize(original.size());
Or use std::string::operator+= or similar to append to altered:
altered += original[i] + 5;
This way, it can be empty before the loop, it will automatically resize itself to contain appended characters.
Explanation
The way UB is happening here, is that you're succeeding in writing the data in the static array, which std::string uses for short string optimization (std::string::operator[] does no checks if you're accessing this array past the std::string::size()), but std::string::size() remains 0, as well as std::string::begin() == std::string::end().
That's why you can access the data individually (again, with UB):
cout << altered[0] << " " << altered[1] << " " << altered[2] << endl;
but cout << aligned does not print anything, considering simplified operator<< definition for std::string looks functionally like this:
std::ostream &operator<<(std::ostream &os, std::string const& str)
{
for(auto it = str.begin(); it != str.end(); ++it) // this loop does not run
os << *it;
return os;
}
In one sentence, std::string is not aware of what you did to its underlying array and that you meant the string to grow in length.
To conclude, <algoritm> way of doing this transformation:
std::transform(original.begin(), original.end(),
std::back_inserter(altered), // or altered.begin() if altered was resized to original's length
[](char c)
{
return c + 5;
}
(required headers: <algorithm>, <iterator>)
In your program string altered is empty. It has no elements.
Thus you may not use the subscript operator to access non-existent elements of the string as you are doing
altered[i] = original[i] + 5;
So you can append the string with new characters. There are several ways to do this. For example
altered.push_back( original[i] + 5 );
or
altered.append( 1, original[i] + 5 );
or
altered += original[i] + 5;
As you may not apply the subscript operator for an empty string to assign a value then it is better to use the range-based for loop because the index itself in fact is not used. For example
for ( char c : original ) altered += c + 5;
The size of altered is always zero - by using indexes you are trying to copy values from original to altered at indexes altered does not have. As LogicStuff has said, this is undefined behaviour - it doesn't generate an error because when we use indexes with std::string we are in fact calling an operator on a std::string to access the data field of a string. Using [] operator is defined in the C++ Standard as having no range check - that's why no error was thrown. The safe way to access indexes is to use the at(i) method: altered.at(i) will instead throw a range error if altered.size() <= i
However, I'm going to give this as my solution because it's a "Modern C++" approach (plus shorter and complete).
This is the alternative I would do to what has been given above:
string original = "abc";
string altered = original;
for (auto& c : altered) c += 5; // ranged for-loop - for each element in original, increase its value by 5
cout << altered << endl;
Note the significant reduction in code :-)
Even if I were doing it LogicStuff's way, I would still do it like this:
string original = "abc"
string altered = ""; // this is actually what an empty string should be initialised to.
for (auto c : original) altered += (c+5);
However, I actually don't recommend this approach, because of the way push_back() and string appending / string concatenation work. It's fine in this small example, but what if original was a string holding the first 10 pages of a book to be parsed? Or what if it's a raw input of a million characters? Then every time the data field for altered reaches its limit it needs to be re-allocated via a system call and the contents of altered are copied and the prior allocation for the data field is freed. This is a significant performance impediment which grows relative to the size of original -- it's just bad practice. It would always be more efficient to do a complete copy and then iterate, making the necessary adjustments on the copied string. The same applies to std::vector.
So I'm trying to force a preceding 0 to an int so it can be processed later on. Now, all the tutorials i've seen on SO, or any other website, all use something similar to this:
cout << setfill('0') << setw(2) << x ;
Whilst this is great, i can only seem to get it to work with cout, however, I don't want to output my text, i just want the number padded, for later use.
So far, this is my code..
#include <iostream>
#include <string>
#include <iomanip>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <vector>
#include <sstream>
/*
using std::string;
using std::cout;
using std::setprecision;
using std::fixed;
using std::scientific;
using std::cin;
using std::vector;
*/
using namespace std;
void split(const string &str, vector<string> &splits, size_t length = 1)
{
size_t pos = 0;
splits.clear(); // assure vector is empty
while(pos < str.length()) // while not at the end
{
splits.push_back(str.substr(pos, length)); // append the substring
pos += length; // and goto next block
}
}
int main()
{
int int_hour;
vector<string> vec_hour;
vector<int> vec_temp;
cout << "Enter Hour: ";
cin >> int_hour;
stringstream str_hour;
str_hour << int_hour;
cout << "Hour Digits:" << endl;
split(str_hour.str(), vec_hour, 1);
for(int i = 0; i < vec_hour.size(); i++)
{
int_hour = atoi(vec_hour[i].c_str());
printf( "%02i", int_hour);
cout << "\n";
}
return 0;
}
The idea being to input an int, then cast it to a stringstream to be split into single characters, then back to an integer. However, anything less than the number 10 (<10), I need to be padded with a 0 on the left.
Thanks guys
EDIT:
The code you see above is only a snippet of my main code, this is the bit im trying to make work.
Alot of people are having trouble understanding what i mean. so, here's my idea. Okay, so the entire idea of the project is to take user input (time (hour, minute) day(numeric, month number), etc). Now, i need to break those numbers down into corresponding vectors (vec_minute, vec_hour, etc) and then use the vectors to specify filenames.. so like:
cout << vec_hour[0] << ".png";
cout << vec_hour[1] << ".png";
Now, i know i can use for loops to handle the output of vectors, i just need help breaking down the input into individual characters. Since i ask users to input all numbers as 2 digits, anything under the number 10 (numbers preceding with a 0), wont split into to digits because the program automatically removes its preceding 0 before the number gets passed to the split method (ie. you enter 10, your output will be 10, you enter 0\n9, and your output will be a single digit 9). I cant have this, i need to pad the any number less than 10 with a 0 before it gets passed to the split method, therefore it will return 2 split digits. I cast the integers into stringstreams because thats the best way for splitting data types i found (incase you were wondering).
Hope i explained everything alot better :/
If I understand correctly your question, you can just use those manipulators with a stringstream, for instance:
std::stringstream str_hour;
str_hour << setfill('0') << setw(2) << int_hour;
String streams are output streams, so I/O manipulators affect them the same way they affect the behavior of std::cout.
A complete example:
#include <sstream>
#include <iostream>
#include <iomanip>
int main()
{
std::stringstream ss;
ss << std::setfill('0') << std::setw(2) << 10; // Prints 10
ss << " - ";
ss << std::setfill('0') << std::setw(2) << 5; // Prints 05
std::cout << ss.str();
}
And the corresponding live example.
int and other numeric types store values. Sticking a 0 in front of an integer value does not change the value. It's only when you convert it to a text representation that adding a leading 0 changes what you have, because you've changed the text representation by inserting an additional character.
X-Y Problem, I think
for ( int i = 0; i < POWER_OF_TEN; i++ )
{
vector<int>.push_back(num%10);
num /= 10
}
?
Then reverse the vector if you want
yes i know this is not real code
if you really want characters, vector<char>.push_back(num%10 + '0')?
can anyone help me make this more generalised and more pro?
#include <fstream>
#include <iostream>
#include <string>
#include <vector>
using namespace std;
int main()
{
// open text file for input:
string file_name;
cout << "please enter file name: ";
cin >> file_name;
// associate the input file stream with a text file
ifstream infile(file_name.c_str());
// error checking for a valid filename
if ( !infile )
{
cerr << "Unable to open file "
<< file_name << " -- quitting!\n";
return( -1 );
}
else cout << "\n";
// some data structures to perform the function
vector<string> lines_of_text;
string textline;
// read in text file, line by
while (getline( infile, textline, '\n' ))
{
// add the new element to the vector
lines_of_text.push_back( textline );
// print the 'back' vector element - see the STL documentation
cout << lines_of_text.back() << "\n";
}
cout<<"OUTPUT BEGINS HERE: "<<endl<<endl;
cout<<"the total capacity of vector: lines_of_text is: "<<lines_of_text.capacity()<<endl;
int PLOC = (lines_of_text.size()+1);
int numbComments =0;
int numbClasses =0;
cout<<"\nThe total number of physical lines of code is: "<<PLOC<<endl;
for (int i=0; i<(PLOC-1); i++)
//reads through each part of the vector string line-by-line and triggers if the
//it registers the "//" which will output a number lower than 100 (since no line is 100 char long and if the function does not
//register that character within the string, it outputs a public status constant that is found in the class string and has a huge value
//alot more than 100.
{
string temp(lines_of_text [i]);
if (temp.find("//")<100)
numbComments +=1;
}
cout<<"The total number of comment lines is: "<<numbComments<<endl;
for (int j=0; j<(PLOC-1); j++)
{
string temp(lines_of_text [j]);
if (temp.find("};")<100)
numbClasses +=1;
}
cout<<"The total number of classes is: "<<numbClasses<<endl;
Format the code properly, use consistent style and nomenclature and throw out the utterly redundant comments and empty lines. The resulting code should be fine. Or “pro”.
Here, I’ve taken the efford (along with some stylistic things that are purely subjective):
Notice that the output is actually wrong (just run it on the program code itself to see that …).
#include <fstream>
#include <iostream>
#include <string>
#include <vector>
using namespace std;
int main()
{
string file_name;
cout << "please enter file name: ";
cin >> file_name;
ifstream infile(file_name.c_str());
if (not infile) {
cerr << "Unable to open file " << file_name << " -- quitting!" << endl;
return -1;
}
else cout << endl;
vector<string> lines_of_text;
string textline;
while (getline(infile, textline)) {
lines_of_text.push_back(textline);
cout << lines_of_text.back() << endl;
}
cout << "OUTPUT BEGINS HERE: " << endl << endl;
cout << "the total capacity of vector: lines_of_text is: "
<< lines_of_text.capacity() << endl << endl;
int ploc = lines_of_text.size() + 1;
cout << "The total number of physical lines of code is: " << ploc << endl;
// Look for comments `//` and count them.
int num_comments = 0;
for (vector<string>::iterator i = lines_of_text.begin();
i != lines_of_text.end();
++i) {
if (i->find("//") != string::npos)
++num_comments;
}
cout << "The total number of comment lines is: " << num_comments << endl;
// Same for number of classes ...
}
I'm not really sure what you're asking, but I can point out some things that can be improved in this code. I'll focus on the actual statements and leave stylistic comments to others.
cin >> file_name;
To handle file names with spaces, better write
getline(cin, file_name);
int PLOC = (lines_of_text.size()+1);
Why do you claim that there's one more line than there actually is?
if (temp.find("//")<100)
with some complicated comment explaining this. Better write
if (temp.find("//")<temp.npos)
to work correctly on all line lengths.
cout<<"The total number of comment lines is: "<<numbComments<<endl;
Actually, you counted the number of end-of-line comments. I wouldn't call a comment at the end of a statement a "comment line".
You don't count /* */ style comments.
Counting the number of classes as };? Really? How about structs, enums, and plain superfluous semicolons? Simply count the number of occurences of the class keyword. It should have no alphanumeric character or underscore on either side.
Use proper indentation, your code is very difficult to read in its current form. Here is a list of styles.
Prefer ++variable instead of variable += 1 when possible; the ++ operator exists for a reason.
Be consistent in your coding style. If you're going to leave spaces between things like cout and <<, function arguments and the function parantheses do it, otherwise don't, but be consistent. Pick one naming convention for your variables and stick to it. There is a lot about styles you can find on google, for example here and here.
Don't use the entire std namespace, only what you need. User either using std::cout; or prefix all of your cout statements with std::
Avoid needless comments. Everyone knows what ifstream infile(file_name.c_str()); does for example, what I don't know is what your program does as a whole, because I don't really care to understand what it does due to the indentation. It's a short program, so rather than explaning every statement on its own, why not explain what the program's purpose is, and how to use it?
These are all stylistic points. Your program doesn't work in its current form, assuming your goal is to count comments and classes. Doing that is a lot more difficult than you are considering. What if I have a "};" as part of a string for example? What if I have comments in strings?
Don't import the whole std namespace, only things you need from it:
using std::string;
Use a consistent naming convention: decide whether you prefer name_for_a_variable or nameforavariable or nameForAVariable. And use meaningful names: numbComments makes me associate to very different things than would numberOfComments, numComments or commentCount.
If your original code looks like this, I strongly recommend to select a single consistent indentation style: either
if ( ... )
{
...
}
or
if ( ... )
{
...
}
bot not both in the same source file.
Also remove the useless comments like
// add the new element to the vector
This is "only" about the readability of your code, not even touching its functionality... (which, as others have already pointed out, is incorrect). Note that any piece of code is likely to be read many more times than edited. I am fairly sure that you will have trouble reading (and understanding) your own code in this shape, if you need to read it even a couple of months after.
"More professional" would be not doing it at all. Use an existing SLOC counter, so you don't reinvent the wheel.
This discussion lists a few:
http://discuss.techinterview.org/default.asp?joel.3.207012.14
Another tip: Don't use "temp.find("};}) < 100)", use "temp.find("};") != temp.npos;"
Edit: s/end()/npos. Ugh.