0

The program requires a user to search for a file with an array of any 2D dimensions (with specified rows/columns, this is outside the function and not really relevant), select one column in the array, and sort that column using a bubble sort.

My code for the function:

int sortFile(string file1, string file2)
{
    ifstream inFile;
    inFile.open(file1.c_str());

    if (inFile)
    {
        int row, col;

        inFile >> row >> col;
        long double arr[row][col];

        for (int i = 0; i < row; i++)
        {
            for (int j = 0; j < col; j++)
            {
                inFile >> arr[i][j];
            }
        }

        int columnSort;
        cout << "Enter the column number you'd like to sort between 1 and " << col << ": ";
        cin >> columnSort;
        columnSort -= 1;

        for (int i = 0; i < row - 1; i++)
        {
            for (int j = 0; j < col - 1; j++)
            {
                if (arr[i][columnSort] > arr[i+1][columnSort])
                {
                    swap(arr[i][columnSort], arr[i+1][columnSort]);
                }
            }
        }
        inFile.close();

        ofstream sortFile;
        sortFile.open(file2.c_str());

        if (sortFile)
        {
            sortFile << row << " " << col << endl;

            for (int i = 0; i < row; i++)
            {
                for (int j = 0; j < col; j++)
                {
                    sortFile << arr[i][j] << " ";
                }
                sortFile << endl;
            }
            sortFile.close();

            cout << "File named " << file2 << " sorted and saved successfully." << endl;
            cout << endl;
        }
        else
        {
            cout << "File could not be opened for writing..." << endl;
            cout << "Terminating program.";
            return 1;
        }
    }
    else
    {
        cout << "File could not be opened for sorting..." << endl;
        cout << "Terminating program.";
        return 1;
    }
}

I used the following file as a test:

5 6
5 8 4 2 3 6 
6 5 8 7 1 2 
3 9 6 3 6 4 
7 7 5 2 1 3 
4 2 0 3 8 4 

and the sorted output was this when I specified column 3:

5 6
5 8 4 2 3 6 
6 5 6 7 1 2 
3 9 5 3 6 4 
7 7 0 2 1 3 
4 2 8 3 8 4 

I'm not seeing a logical issue in what I wrote (probably due to inexperience since I am finishing an introductory course to C++) so I'm not entirely sure what's causing it to not actually sort everything. Maybe a possible error with columns that have duplicate values as well?

Note: the string variables are zero issue. There are three more functions in this program that use the same file opening/closing and such, the only problem in this seems to be the sorting algorithm. Thus I have not included anything outside this function.

OllyDraws
  • 3
  • 1
  • 1
    Suggest try to separate `sortFile` into `readFile` and `sort`. then maybe you could solve this by yourself. https://en.wikipedia.org/wiki/Modular_design – JustWe Dec 13 '19 at 05:20
  • 1
    Your sorting logic runs once through the array, which isn’t enough for bubble sort. It also loops j for cols but doesn’t use it for anything, so it just basically checks one column col times and it of course can’t be larger after it’s been possibly swapped. – Sami Kuhmonen Dec 13 '19 at 05:22
  • Would a do/while loop make it so that it runs through the array multiple times? @Sami-Kuhmonen – OllyDraws Dec 13 '19 at 05:26
  • You can do it with for or while, whatever suits better for the algorithm – Sami Kuhmonen Dec 13 '19 at 05:27
  • Awesome, this fixed it once I put it inside a do/while (with a boolean to keep the loop going until the if statement was false) and got rid of the pointless j loop. Thank you! – OllyDraws Dec 13 '19 at 05:38
  • 1
    Does this answer your question? [What is a debugger and how can it help me diagnose problems?](https://stackoverflow.com/questions/25385173/what-is-a-debugger-and-how-can-it-help-me-diagnose-problems) – Aykhan Hagverdili Dec 13 '19 at 05:43

1 Answers1

0

There is a slight problem with your bubble sort implementation. I reformulated that according to Pseudocode in Wikipedia.

// Sort one column with bubblesort
for (size_t n = csv.size(); n > 1U; --n)
    for (size_t i = 0U; i < n-1; ++i) 
        if (csv[i][SortColumn] > csv[i+1][SortColumn]) 
            std::swap(csv[i][SortColumn], csv[i+1][SortColumn]);

But additionally I would like to show you a "more" C++ solution.

Basically it seems like you want to read csv data. This is a standard task and I will give you detailed explanations. In the end all the reading will be done in a one-liner.

There is also no need to have a row and column specifier in the source text file. The algorthims will detect the array dimensions by themselves.

Still, with CSV all people talking about and are linking to [How can I read and parse CSV files in C++?][1], the questions is from 2009 and now over 10 years old. Most answers are also old and very complicated. So, maybe its time for a change.

In modern C++ you have algorithms that iterate over ranges. You will often see something like "someAlgoritm(container.begin(), container.end(), someLambda)". The idea is that we iterate over some similar elements.

In your case we iterate over tokens in your input string, and create substrings. This is called tokenizing.

And for exactly that purpose, we have the std::sregex_token_iterator. And because we have something that has been defined for such purpose, we should use it.

This thing is an iterator. For iterating over a string, hence sregex. 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.

  • 1 --> give me the stuff that I defined in the regex and
  • -1 --> give me that what is NOT matched based on the regex.

So, now that we understand the iterator, we can std::copy the tokens from the iterator to our target, a std::vector of std::string. And since we do not know, how may columns we have, we will use the std::back_inserter as a target. This will add all tokens that we get from the std::sregex_token_iterator and append it ot our std::vector<std::string>>. It does'nt matter how many columns we have.

Good. Such a statement could look like

std::copy(                          // We want to copy something
    std::sregex_token_iterator      // The iterator begin, the sregex_token_iterator. Give back first token
    (
        line.begin(),               // Evaluate the input string from the beginning
        line.end(),                 // to the end
        re,                         // Add match a comma
        -1                          // But give me back not the comma but everything else 
    ),
    std::sregex_token_iterator(),   // iterator end for sregex_token_iterator, last token + 1
    std::back_inserter(cp.columns)  // Append everything to the target container
);

Now we can understand, how this copy operation works.

Next step. We want to read from a file. The file contains also some kind of same data. The same data are rows.

And as for above, we can iterate of similar data. If it is the file input or whatever. For this purpose C++ has the std::istream_iterator. This is a template and as a template parameter it gets the type of data that it should read and, as a constructor parameter it gets a reference to an input stream. It doesnt't matter, if the input stream is a std::cin, or a std::ifstream or a std::istringstream. The behaviour is identical for all kinds of streams.

And since we do not have files an SO, I use (in the below example) a std::istringstream to store the input csv file. But of course you can open a file, by defining a std::ifstream testCsv(filename). No problem.

And with std::istream_iterator, we iterate over the input and read similar data. In our case one problem is that we want to iterate over special data and not over some build in data type.

To solve this, we define a Proxy class, which does the internal work for us (we do not want to know how, that should be encapsulated in the proxy). In the proxy we overwrite the type cast operator, to get the result to our expected type for the std::istream_iterator.

And the last important step. A std::vector has a range constructor. It has also a lot of other constructors that we can use in the definition of a variable of type std::vector. But for our purposes this constructor fits best.

So we define a variable csv and use its range constructor and give it a begin of a range and an end of a range. And, in our specific example, we use the begin and end iterator of std::istream_iterator.

If we combine all the above, reading the complete CSV file is a one-liner, it is the definition of a variable with calling its constructor.

Then we use an ultra simple bubble sort, as shown above, and the show the result.

Please see the resulting code:

#include <iostream>
#include <sstream>
#include <fstream>
#include <string>
#include <vector>
#include <iterator>
#include <regex>
#include <algorithm>
#include <utility>

std::istringstream testCsv{ R"(5 8 4 2 3 6 
6 5 8 7 1 2 
3 9 6 3 6 4 
7 7 5 2 1 3 
4 2 0 3 8 4)" };


// Define Alias for easier Reading
using Columns = std::vector<std::string>;
using CSV = std::vector<Columns>;


// Proxy for the input Iterator
struct ColumnProxy {    
    // Overload extractor. Read a complete line
    friend std::istream& operator>>(std::istream& is, ColumnProxy& cp) {

        // Read a line
        std::string line; cp.columns.clear();
        std::getline(is, line);

        // The delimiter
        const std::regex re(" ");

        // Split values and copy into resulting vector
        std::copy(std::sregex_token_iterator(line.begin(), line.end(), re, -1),
            std::sregex_token_iterator(),
            std::back_inserter(cp.columns));
        return is;
    }

    // Type cast operator overload.  Cast the type 'Columns' to std::vector<std::string>
    operator std::vector<std::string>() const { return columns; }
protected:
    // Temporary to hold the read vector
    Columns columns{};
};

constexpr size_t SortColumn = 2;

int main()
{

    // Define variable CSV with its range constructor. Read complete CSV in this statement, So, one liner
    CSV csv(std::istream_iterator<ColumnProxy>(testCsv), {});

    // Sort one column with bubblesort
    for (size_t n = csv.size(); n > 1U; --n)
        for (size_t i = 0U; i < n-1; ++i) 
            if (csv[i][SortColumn] > csv[i+1][SortColumn]) 
                std::swap(csv[i][SortColumn], csv[i+1][SortColumn]);


    // Print result. Go through all lines and then copy line elements to std::cout
    std::for_each(csv.begin(), csv.end(), [](Columns& c) {
        std::copy(c.begin(), c.end(), std::ostream_iterator<std::string>(std::cout, " ")); std::cout << "\n";   });
}

Additional hint for opening files. You are using:

 ifstream inFile;
    inFile.open(file1.c_str());
    if (inFile)

You can and should use:

if (std::ifstream inFile(file1.c_str()); inFile) {

It will keep the inFile in the inner scope. The destructor will automatically close the file at the end of the if.

A M
  • 14,694
  • 5
  • 19
  • 44