I am trying to sort my file contents as double in the ascending order
My input file contains for example these lines:
105 350 4.41386e-06 4.41386e-06
115 300 4.58965e-06 4.58965e-06
15 150 1.6457e-06 1.6457e-06
255 550 5.33661e-05 5.33661e-05
25 150 3.21907e-06 3.21907e-06
35 550 2.57952e-05 2.57952e-05
45 150 1.78332e-06 1.78332e-06
And i want my output file to have them as following:
15 150 1.6457e-06 1.6457e-06
25 150 3.21907e-06 3.21907e-06
35 550 2.57952e-05 2.57952e-05
45 150 1.78332e-06 1.78332e-06
105 350 4.41386e-06 4.41386e-06
115 300 4.58965e-06 4.58965e-06
255 550 5.33661e-05 5.33661e-05
Since i am just a beginner in C coding, if have tried these line to do that task:
#include <iostream>
#include <fstream>
#include <string>
#include <vector>
int main()
{
std::ifstream textfile( "Limit.txt" );
std::string text_input;
std::vector< std::string > sort_vec;
std::ofstream outfile1;
TString outfile_name1 = "Limits.txt";
if ( textfile.is_open() )
{
outfile1.open( outfile_name1 );
while ( std::getline( textfile,text_input ) )
{
sort_vec.push_back(text_input);
}
textfile.close();
}
std::sort( std::begin( sort_vec ), std::end( sort_vec ), std::less<string>() );
for ( const auto & e : sort_vec )
{
outfile1 << e << "\n";
}
outfile1.close();
}
But I could not get the results that I want.
Could please help me through.
CodePudding user response:
Because of spaces and different length of data in each line, the lexicographic sorting of lines as strings may produce unexpected results or be totally wrong.
Therefore I would recommend to split the lines into it 4 parts, which is very simple using normal IO-extract-functionality using the >>
operator.
The data of one row can be stored in a struct. That is somehow intuitive, readable and understandable. For storing the whole list, we can use a std::vector
of such a struct.
Instead of using a struct, we can also use a std::tuple
as proposed by Jarod42. A std::tuple
has comparison operators, which allow ultra simple sorting of one row later.
Anyway. Let us start with a struct, which has additional possibilities, as shown later.
- Define the struct
- Open the file and check, if it could be opened
- Define a
std::vector
of the above struct - Create a temporary instance of a struct, te be able to read data into it
- In a loop, extract data from the file and store in struct-elements
- Push back each just read data into the
vector
(we will call it database) - Sort with a lambda expression to have full control on how things will be sorted
- Show result to the user
This can be coded in many ways. Look at on potential example below:
#include <iostream>
#include <fstream>
#include <vector>
#include <algorithm>
#include <iomanip>
struct Data {
int i1{};
int i2{};
double d1{};
double d2{};
};
int main() {
// Open the source textfile and check, if it could be opened
std::ifstream textfile("Limit.txt");
if (textfile) {
// Here we will store all data
std::vector<Data> database{};
// Temporary storage for 1 line
Data data{};
// Read all lines and add data to the database
while (textfile >> data.i1 >> data.i2 >> data.d1 >> data.d2)
database.push_back(data);
// Sort database and use a lambda to do it as wished
std::sort(database.begin(), database.end(), [](const Data& d1, const Data& d2) {
return d1.i1 == d2.i1 ? (d1.i2 == d2.i2 ? (d1.d1 == d2.d1 ? d1.d2 < d2.d1 : d1.d1 < d2.d1) : d1.i2 < d2.i2) : d1.i1 < d2.i1; });
// Debug output
for (const Data& d : database)
std::cout << std::left << std::setw(4) << d.i1 << std::setw(4) << d.i2 << std::setw(12) << d.d1 << d.d2 << '\n';
}
else std::cerr << "\n*** Error! Could not open source file\n\n";
}
But that is not all.
As you know, C is an object oriented language. And we can store data together with functions, operating on this data, in a struct (a struct is a class).
So, we can add input and output functions and the less than operator that is needed for sorting.
Then the main function will be significantly simpler.
#include <iostream>
#include <fstream>
#include <vector>
#include <algorithm>
#include <iomanip>
struct Data {
int i1{};
int i2{};
double d1{};
double d2{};
// Methods / operators
// Input
friend std::istream& operator >> (std::istream& is, Data& data) {
return is >> data.i1 >> data.i2 >> data.d1 >> data.d2;
}
// Output
friend std::ostream& operator << (std::ostream& os, const Data& d) {
return os << std::left << std::setw(4) << d.i1 << std::setw(4)
<< d.i2 << std::setw(12) << d.d1 << d.d2;
}
// Comparison
bool operator < (const Data& other) {
return i1 == other.i1 ? (i2 == other.i2 ? (d1 == other.d1 ? d2 < other.d1 : d1 < other.d1) : i2 < other.i2) : i1 < other.i1;
}
};
int main() {
// Open the source textfile and check, if it could be opened
std::ifstream textfile("Limit.txt");
if (textfile) {
// Here we will store all data
std::vector<Data> database{};
// Temporary storage for 1 line
Data data{};
// Read all lines and add data to the database
while (textfile >> data)
database.push_back(data);
// Sort database
std::sort(database.begin(), database.end());
// Debug output
for (const Data& d : database)
std::cout << d << '\n';
}
else std::cerr << "\n*** Error! Could not open source file\n\n";
}
Advantage: Understandable and simple. Good control for sorting. Disadvantage: Complex sorting criteria
CodePudding user response:
The issue here, as pointed out in comments, is that std::less<std::string>
sorts your strings lexicographically (i.e. the same order as it would appear in a dictionary).
This is not the intended behaviour since the sort will be then performed character by character and not value by value.
To achieve the correct behaviour, you need to make a custom comparator that will extract the double
value to perform the sorting based on it.
For example (using a lambda):
std::sort(sort_vec.begin(), sort_vec.end(), [](const std::string & lhs, const std::string & rhs){
double ld, rd;
std::stringstream{lhs} >> ld;
std::stringstream{rhs} >> rd;
return ld < rd;
});
And it would do the trick (live example here)
CodePudding user response:
Since you want a hierarchical sort based on the columns from left-to-right, there are multiple ways to do this using std::sort
.
One way is to read in the values into a std::tuple and apply std::sort
on a vector of these tuples. The tuple class will automatically do a lexicographical compare, from left-to-right, of the tuple's components.
The std::tuple<int, int, double, double>
basically mimics one line of your data.
#include <iostream>
#include <string>
#include <vector>
#include <tuple>
#include <sstream>
std::string test =
"105 350 4.41386e-06 4.41386e-06\n"
"115 300 4.58965e-06 4.58965e-06\n"
"15 150 1.6457e-06 1.6457e-06\n"
"255 550 5.33661e-05 5.33661e-05\n"
"25 150 3.21907e-06 3.21907e-06\n"
"35 550 2.57952e-05 2.57952e-05\n"
"45 150 1.78332e-06 1.78332e-06\n"
"105 35 4.41386e-06 4.41386e-06\n"
"115 300 4.58965e-06 4.58965e-09\n"
"15 150 1.6457e-04 1.6457e-04\n"
"255 550 5.33661e-05 5.33661e-02";
using DataLine = std::tuple<int, int, double, double>;
using VectorDataLine = std::vector<DataLine>;
int main()
{
std::istringstream textfile(test);
VectorDataLine sort_vec;
DataLine data_line;
while ( textfile >> std::get<0>(data_line) >>
std::get<1>(data_line) >>
std::get<2>(data_line) >>
std::get<3>(data_line))
{
sort_vec.push_back(data_line);
}
std::sort( std::begin( sort_vec ), std::end( sort_vec ));
for ( const auto & e : sort_vec )
{
std::cout << std::get<0>(e) << ' ' <<
std::get<1>(e) << ' ' <<
std::get<2>(e) << ' ' <<
std::get<3>(e) << "\n";
}
}
Output:
15 150 1.6457e-06 1.6457e-06
15 150 0.00016457 0.00016457
25 150 3.21907e-06 3.21907e-06
35 550 2.57952e-05 2.57952e-05
45 150 1.78332e-06 1.78332e-06
105 35 4.41386e-06 4.41386e-06
105 350 4.41386e-06 4.41386e-06
115 300 4.58965e-06 4.58965e-09
115 300 4.58965e-06 4.58965e-06
255 550 5.33661e-05 5.33661e-05
255 550 5.33661e-05 0.0533661