C++ Need help sorting a 2D string array - c++

I'm a little stuck with sorting a string Table[X][Y]. As tagged, Im using C++ and have to use standard libraries and make it for all C++ (not only C++ 11).
The size of the Table is fixed (i get the X reading how many lines a file has and the Y is fixed because thats the different "attributes" has each line).
When i create the Table, each part of it is obtained as Table[X][Y] = stringX.data(); from things previously read from a file and stored in strings. I have numbers in the first column (the one im going to use as sorting criteria), names, address, etc in the others.
The part where the Table is created is:
Table[i][0] = string1.data();
Table[i][1] = string2.data();
Table[i][2] = string3.data();
Table[i][3] = string4.data();
Table[i][4] = string5.data();
Where "i" is the current "iteration" of a while(fgets) that reads one line at a time from a file, does some operations and stores in those strings the "final values" of each part of the line read.
I have to sort that Table using the first column as criteria in decreasing order.
Lets imagine the Table is like this: Table[4][3]
20 | Jhon | 14th July
2 | Mary | 9th June
44 | Mark | 10th December
1 | Chris | 4th Feb
And i need the output to be this:
44 | Mark | 10th December
20 | Jhon | 14th July
2 | Mary | 9th June
1 | Chris | 4th Feb
I have been reading several questions and pages and they sort int/chars arrays or convert the array into vector and then work with them. Im trying to sort the string Table i have without converting anything (dunno if possible).
I dont know if i managed to explain the issue and the situation i have clear enough. Im not putting all the code i have because apart from the declaration of the string Table and the strings that are then placed as string.data in the Table, the rest of the code has nothing to do with the Table and the sorting process. The code opens the file, reads line by line, filters the info i need from some separators and special characters and places each of the "rankings criteria" to a string, then assigns a "ranking" after evaluating each of the criterias and giving a total score (which then is stored in "string1").
After all this is done, i create the string Table[x][y] and place the filtered and processed information in that Table one row at a time (because i assing this while reading each line from the file).
The only thing that remains is the sorting of the table from the best scored to the last and then create a file with the top 10.
I appreciate and thank in advance the time you took reading this and any tip, information, code or source from where i can read this that you could provide.

First, as mentioned in the comments, a variable length array is accomplished in C++ by using std::vector. The current syntax you're using
std::string Table[X][Y]
where either X or Y are runtime variables, is not legal C++. Given your example, a standard C++ declaration would be this:
std::vector<std::array<std::string, 3>> Table;
So let's assume that this is what you are going to use.
The next step is to sort the data based on the first column. That can be accomplished by utilizing the std::sort algorithm function, along with the appropriate predicate indicating that you are using the first column as the sorting criteria.
Here is a short example, using your data, of how this is all accomplished:
#include <vector>
#include <array>
#include <iostream>
#include <algorithm>
#include <string>
int main()
{
std::vector<std::array<std::string, 3>> Table;
// Set up the test data
Table.push_back({"20", "Jhon", "14th July"});
Table.push_back({"2", "Mary", "9th June"});
Table.push_back({"44", "Mark", "10th December"});
Table.push_back({"1", "Chris", "4th Feb"});
std::cout << "Before sort:\n\n";
for (auto& s : Table)
std::cout << s[0] << " | " << s[1] << " | " << s[2] << "\n";
std::cout << "\n\nAfter sort:\n\n";
// Sort the data using the first column of each `std::array` as the criteria
std::sort(Table.begin(), Table.end(), [&](auto& a1, auto& a2)
{ return std::stoi(a1[0]) > std::stoi(a2[0]); });
// Output the results:
for (auto& s : Table)
std::cout << s[0] << " | " << s[1] << " | " << s[2] << "\n";
}
Here is the final output:
Before sort:
20 | Jhon | 14th July
2 | Mary | 9th June
44 | Mark | 10th December
1 | Chris | 4th Feb
After sort:
44 | Mark | 10th December
20 | Jhon | 14th July
2 | Mary | 9th June
1 | Chris | 4th Feb
The output needs a little bit of formatting, but that's not important.
Remember, it is not important as to where the data comes from, whether it is from a file or hardcoded as the example above shows. However you populate the Table, that's up to you. The goal is to show you once populated, how to sort the data.
The first thing we did was create the Table and fill it in with the test data. Note that the vector has a push_back function to add entries to the vector.
Then the call to std::sort has a predicate function (the lambda), where the predicate is given two items, in this case it would be two std::array's by reference. Then the goal is to return if the first std::array (in this case, a1) should be placed before the second std::array (a2).
Note that we only care about the first column, so we only need to consider array[0] of each of those arrays, and compare them.
Also note that since array[0] is a std::string, we simply can't compare it lexicographically -- we need to convert the string to an int and compare the int value. That's the reason for the std::stoi call to convert to an integer.
The final thing about the sort predicate is that we want to have a descending sort. Thus the comparing operator to use is > instead of the "traditional" < (which would have sorted in a ascending manner).
Hopefully this explains what the code is doing.
Edit:
Since you are attempting to get this code to work in C++98, the easiest way to do that is
Change to std::vector<std::vector<std::string>> instead of std::vector<std::array<std::string, 3>>
Not use the brace-initialization that C++11 offers
Use a comparison function instead of a lambda.
Given that, here is the code for C++98:
#include <vector>
#include <iostream>
#include <algorithm>
#include <string>
bool SortFirstColumn(const std::vector<std::string>& a1,
const std::vector<std::string>& a2)
{
return atoi(a1[0].c_str()) > atoi(a2[0].c_str());
}
int main()
{
std::vector<std::vector<std::string>> Table;
// Set up the test data
std::vector<std::string> vect(3);
vect[0] = "20";
vect[1] = "Jhon";
vect[2] = "14th July";
Table.push_back(vect);
vect[0] = "2";
vect[1] = "Mary";
vect[2] = "9th June";
Table.push_back(vect);
vect[0] = "44";
vect[1] = "Mark";
vect[2] = "10th December";
Table.push_back(vect);
vect[0] = "1";
vect[1] = "Chris";
vect[2] = "10th December";
Table.push_back(vect);
std::cout << "Before sort:\n\n";
for (size_t i = 0; i < Table.size(); ++i)
std::cout << Table[i][0] << " | " << Table[i][1] << " | " << Table[i][2] << "\n";
std::cout << "\n\nAfter sort:\n\n";
// Sort the data using the first column of each `std::vector<std::string>` as the criteria
std::sort(Table.begin(), Table.end(), SortFirstColumn);
// Output the results:
for (size_t i = 0; i < Table.size(); ++i)
std::cout << Table[i][0] << " | " << Table[i][1] << " | " << Table[i][2] << "\n";
}

Related

In C++ and range-v3, how to convert a string of space-separated numbers to a vector of integers?

Using C++ and range-v3 library, what's the optimal approach to converting a string with space-separated numbers to a vector of integers?
I tried the following code:
#include <iostream>
#include <range/v3/all.hpp>
using namespace std::literals;
int main() {
auto r = "1 1 2 3 5 8 13"sv
| ranges::views::split(" "sv)
| ranges::views::transform([](auto &&i){ return std::stoi(std::string{i}); })
| ranges::to<std::vector<int>>();
for (auto i: r)
std::cout << "Value: " << i << std::endl;
}
It doesn't compile however. In clang, the error is as follows:
repro-range.cpp:10:60: error: no matching constructor for initialization of 'std::string' (aka 'basic_string<char>')
| ranges::view::transform([](auto &&i){ return std::stoi(std::string{i}); })
^ ~~~
It seems that the type of i is ranges::detail::split_outer_iterator and it's not convertible to string. Actually, I don't understand how to use i, can't dereference it, can't convert it to anything useful... replacing string_views by strings also doesn't improve the situation.
What's weird, the code below works fine:
auto r = "1 1 2 3 5 8 13"sv
| ranges::views::split(" "sv)
| ranges::to<std::vector<std::string>>();
which suggest me the problem is netiher split nor to, but the transform itself.
How to make the first piece code working?
If you have a string containing space separated numbers you can first create an std::istringstream over the string and then use ranges::istream to parse the numbers (assuming ints here):
auto s = "1 1 2 3 5 8 13";
auto ss = std::istringstream{s};
auto r = ranges::istream<int>(ss)
| ranges::to<std::vector<int>>();
Here's a demo.
Digging deeper, I found out that i in my example isn't an iterator nor a wrapper over string_view (like I expected) but a range of characters (a special type with begin and end iterators).
Meaning, my code works if I first convert i to a string the range way:
auto r = "1 1 2 3 5 8 13"sv
| ranges::views::split(" "sv)
| ranges::views::transform([](auto &&i){
return std::stoi(i | ranges::to<std::string>());
})
| ranges::to<std::vector<int>>();
Although I'll be delighted if somebody posts a nicer (at least less verbose) way to do that.

Why is ranges::split_view not a Bidirectional Range?

I am using the cmcstl2 library with the C++ proposed Ranges with gcc 8
std::string text = "Let me split this into words";
std::string pattern = " ";
auto splitText = text | ranges::view::split(pattern) |
ranges::view::reverse;
But this does not work since the view is only a Forward Range not a Bidirectional Range which required by range (which is what I think is going on). Why? if
text | ranges::view::split(pattern)
outputs a view of subranges. Can't that view be reversed?
Also in cmcstl2 I must do the following to print it out.
for (auto x : splitText)
{
for (auto m : x)
std::cout << m;
std::cout << " ";
}
But in range-v3/0.4.0 version I can do:
for (auto x : splitText)
std::cout << x << '\n';
Why? What is the type of x?
The way it's been written only supports ForwardRange.
You can certainly try to make a BidirectionalRange version, although I suspect that is either hard or less general.
Consider how to specify all the options for pattern such that it can also match backwards.

Storing file data variables in a dimentional array

I have a .txt file which I'm trying to gather data from, that can then be used within variables within my code to be used in other functions.
Here's an example of my text file:
0 10 a namez 1 0
0 11 b namea 1 1
1 12 c nameb 1 1
2 13 d namec 0 1
3 14 e named 1 1
So my file will not always be the same number of lines, but always the same number of variables per line.
I currently have this, to firstly get the length of the file and then change the amount of rows within the array:
int FileLength()
{
int linecount = 0;
string line;
ifstream WorkingFile("file.txt");
while(getline(WorkingFile, line))
{
++linecount;
}
return linecount;
}
int main()
{
string FileTable [FileLength()][6];
}
Firstly I don't know if the above code is correct or how I can add the values from my file into my FileTable array.
Once I have my FileTable array with all the file data in it, I then want to be able to use this in other functions.
I've been able to do:
if(FileTable[2][0] = 1)
{
cout << "The third name is: " << FileTable[2][3] << endl;
}
I understand my code may not make sense here but I hope it demonstrates what I'm attempting to do.
I have to do this for a larger text file and all the 6 variables per line relate to be input to a function.
Hold each line in its own object, this is much clearer:
struct Entry
{
std::array<std::string, 6> items; // or a vector
};
In main:
std::vector<Entry> file_table( FileLength() );
Note that it is a waste of time to read the whole file first in order to find the number of entries. You could just start with an empty vector, and push in each entry as you read it.
Your access code:
if( file_table.size() > 2 && file_table[2].items[0] == "1" )
{
cout << "The third name is: " << FileTable[2].items[2] << endl;
}
I would actually recommend giving the members of Entry names, instead of just having an array of 6 of them. That would make your code more readable. (Unless you really need to iterate over them, in which case you can use an enum for the indices).
You could define an operator[] overload for Entry if you don't like the .items bit.
since the number of lines is dynamic I suggest to use vector instead of array. you can push back your data to the vector line by line until you read eof.
also try to study about OOP a little , it would make your code more understandable.
take look at these:
http://www.cplusplus.com/reference/vector/vector/
http://www.geeksforgeeks.org/eof-and-feof-in-c/

Check for every rugby score the recursive way without repetitions

Just for fun I created an algorithm that computes every possible combination from a given rugby score (3, 5 or 7 points). I found two methods : The first one is brute force, 3 imbricated for loops. The other one is recursion.
Problem is some combinations appear multiple times. How can I avoid that ?
My code :
#include <iostream>
using namespace std;
void computeScore( int score, int nbTryC, int nbTryNC, int nbPenalties );
int main()
{
int score = 0;
while (true)
{
cout << "Enter score : ";
cin >> score;
cout << "---------------" << endl << "SCORE = " << score << endl
<< "---------------" << endl;
// Recursive call
computeScore(score, 0, 0, 0);
}
return 0;
}
void computeScore( int score, int nbTryC, int nbTryNC, int nbPenalties )
{
const int tryC = 7;
const int tryNC = 5;
const int penalty = 3;
if (score == 0)
{
cout << "* Tries: " << nbTryC << " | Tries NT: " << nbTryNC
<< " | Penal/Drops: " << nbPenalties << endl;
cout << "---------------" << endl;
}
else if (score < penalty)
{
// Invalid combination
}
else
{
computeScore(score - tryC, nbTryC+1, nbTryNC, nbPenalties);
computeScore(score - tryNC, nbTryC, nbTryNC+1, nbPenalties);
computeScore(score - penalty, nbTryC, nbTryNC, nbPenalties+1);
}
}
One way to think about this is to realize that any time you have a sum, you can put it into some "canonical" form by sorting all the values. For example, given
20 = 5 + 7 + 3 + 5
You could also write this as
20 = 7 + 5 + 5 + 3
This gives a few different options for how to solve your problem. First, you could always sort and record all of the sums that you make, never outputting the same sum twice. This has the problem that you're going to end up repeatedly generating the same sums multiple different times, which is extremely inefficient.
The other (and much better) way to do this is to update the recursion to work in a slightly different way. Right now, your recursion works by always adding 3, 5, and 7 at each step. This is what gets everything out of order in the first place. An alternative approach would be to think about adding in all the 7s you're going to add, then all the 5's, then all the 3's. In other words, your recursion would work something like this:
Let kValues = {7, 5, 3}
function RecursivelyMakeTarget(target, values, index) {
// Here, target is the target to make, values are the number of 7's,
// 5's, and 3's you've used, and index is the index of the number you're
// allowed to add.
// Base case: If we overshot the target, we're done.
if (target < 0) return;
// Base case: If we've used each number but didn't make it, we're done.
if (index == length(kValues)) return;
// Base case: If we made the target, we're done.
if (target == 0) print values; return;
// Otherwise, we have two options:
// 1. Add the current number into the target.
// 2. Say that we're done using the current number.
// Case one
values[index]++;
RecursivelyMakeTarget(target - kValues[index], values, index);
values[index]--;
// Case two
RecursivelyMakeTarget(target, values, index + 1);
}
function MakeTarget(target) {
RecursivelyMakeTarget(target, [0, 0, 0], 0);
}
The idea here is to add in all of the 7's you're going to use before you add in any 5's, and to add in any 5's before you add in any 3's. If you look at the shape of the recursion tree that's made this way, you will find that no two paths end up trying out the same sum, because when the path branches either a different number was added in or the recursion chose to start using the next number in the series. Consequently, each sum is generated exactly once, and no duplicates will be used.
Moreover, this above approach scales to work with any number of possible values to add, so if rugby introduces a new SUPER GOAL that's worth 15 points, you could just update the kValues array and everything would work out just fine.
Hope this helps!
Each time you find a solution you could store it in a dictionary ( a set of strings for example, with strings looking like "TC-TNT-P" )
Before printing a solution you verify it was not in the dictionary.
A nested for-loop is the natural way to do this. Using recursion is just silly (as you seem to have discovered).

Initializing 2D int array in run-time

I got the code below from a C++ book, and I cannot figure out how the initialization works.
From what I can see, there is an outer for loop cycling trough the rows, and the inner loop
cycling trough the column. But its is the assignment of the values into the array that I do not understand.
#include <iostream>
using namespace std;
int main()
{
int t,i, nums[3][4];
for(t=0; t < 3; ++t) {
for(i=0; i < 4; ++i) {
nums[t][i] = (t*4)+i+1; //I don't understand this part/line
cout << nums[t][i] << ' ';
}
cout << '\n';
}
return 0;
}
so here are some questions.
I cannot understand the initialization of the 2D int array nums[3][4]. What separates the (t*4)+i+1, so that the compiler knows what to assign where?
How do I know what values will be stored in the rows and columns, based on what values have been assigned?
Why is there an asterisk?
What are the parentheses around t*4 for?
I understand that initialization two-dimensional arrays look like the following example.
#include <iostream>
using namespace std;
int main() {
char str[3][20] = {{"white" "rabbit"}, {"force"}, {"toad"}}; //initialize 2D character array
cout << str[0][0] << endl; //first letter of white
cout << str[0][5] << endl; //first letter of rabbit
cout << str[1][0] << endl; //first letter of force
cout << str[2][0] << endl; //first letter of toad
return 0;
}
And from what I know, like this in memory.
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14
0 w h i t e r a b b i t 0
1 f o r c e 0
2 t o a d 0
Thank you.
(t*4)+i+1
Is an arithmetic expression. t and i are ints, the * means multiply. So for row 1, column 2, t = 1, i = 2, and nums[1][2] = 1x4+2+1 = 7.
Oh, forgot a couple things. First, the () is to specify the order of operations. So the t*4 is done first. Note that in this case the () is unnecessary, since the multiply operator takes precedence over the plus operator anyway.
Also, I couldn't tell from your question if you knew this already or not, but the meaning of rows[t][i] is array notation for accessing rows at row t and column i.
For the first part, isn't it just assigning the a value equal to the row number * 4 plus the column number? I.E. the end result of the assignment should be:
1 2 3 4
5 6 7 8
9 10 11 12
So the expression (t*4)+i+1 means "4 multiplied by the row number plus the column number plus 1". Note that the row number and column numbers in this case start from 0.
nums[t][i] is the one spot in the array it is assigning the value of (t*4)+i+1.
So if t = 1 and i = 1 then the spot num[1][1] will equal (1*4)+1+1 which is 6.
See above.
Asterisk is for multiplying.
You do what's in the ( ) first just like in any mathematical equation.
Lets see, you have
int t,i, nums[3][4];
where we reserve space for the 2d array. The values inside the array will have random values since you only reserved space.
The line :
nums[t][i] = (t*4)+i+1; //I don't understand this part/line
Assigns the values to the array. You have t and i which are loop counters, and the line (t*4)+i+1 means, take value of t, multiply with 4, plus i and plus 1.
So for t=0, i =0, you get that nums[0][0] has value (0*4) + 0 + 1 which is 1.. etc for everything else.
And ofcourse the () are just basic math parentheses.