2

I have a switch statement that based on the user's input will sort an array based on array[i].name or sort by array[i].mark

    switch(input){
        case 1:
            for(int i = 1; i < size; i++){
                structure_name choice = Array[i];
                int j = i - 1;
                while(j >= 0 && tolower(choice.name[0]) <= tolower(Array[j].name[0])){
                    Array[j + 1] = Array[j];
                    j = j - 1;
                }
                Array[j+1] = choice;
            }
            break;
        case 2:
            for(int i = 1; i < size; i++){
                structure_name choice = Array[i];
                int j = i - 1;
                while(j >= 0 && choice.mark <= Array[j].mark){
                    Array[j + 1] = Array[j];
                    j = j - 1;
                }
                Array[j+1] = choice;
            }
            break;
        default:
            break;
    }

As you can see, I'm performing a selection sort twice. Is it possible to perform something like...

    if(input == 1){
        option = .name
    }else if(input == 2){
        option = .mark
    }

So that I can use just one sort and change what variable it's taking from the array. Note: I cant use sort(), I have to use the sort algorithm I've written out. Hence the dilemma of me trying to avoid writing essentially the same code twice.

Choski
  • 23
  • 4

4 Answers4

3

I think you can use different comparator functions as "options". And give the desired comparator to a sorting function, whether you use std::sort or a custom handmade one.

Considering the following struct (which you forgot to specify):

struct MyStruct
{
    std::string name;
    int mark;
};

you can define your "options" as comparator functions:

int mark_cmp(const MyStruct & lhs, const MyStruct & rhs)
{
    return lhs.mark < rhs.mark; // Compare by mark
}
int name_cmp(const MyStruct & lhs, const MyStruct & rhs)
{
    return lhs.name.compare(rhs.name); // Compare by name
}

Then you could simply write:

int main()
{
    unsigned int input = 1;

    MyStruct arr[5] {{"foo", 5}, {"bar", 6}, {"baz", -9}, {"foobar", 0}, {"foobaz", -3}};

    switch(input)
    {
        case 1: std::sort(arr, arr+5, &name_cmp); // input == 1 ? sort by name
            break;
        case 2: std::sort(arr, arr+5, &mark_cmp); // input == 2 ? sort by mark
            break;
        default:;
    }

    return 0;
}

Note: You don't have to break after the last statement in the switch (in your case, after the default, no need to break).

Note2: I used a raw array to be closer as what you did but I would advise you to use a std::array instead. Or a std::vector if you need a dynamic container.


If you cannot use std::sort, you can create your own sorting function which takes the comparator function as parameters and it will work too.

Fareanor
  • 5,900
  • 2
  • 11
  • 37
  • In general such approach is legit, one can use lambdas instead, but in some cases it's a sub-par option, to have an isolated function for comparison. It's simplicity of reading code vs. speed issue. – Swift - Friday Pie Oct 22 '19 at 07:20
  • @Swift-FridayPie Yes, but the OP seemed to have explicit "options". I think having separated functions better illustrates that. And since the code here is only meant to illustrate the strategy, the idea, I choosed the clearer solution. But indeed, using lambdas would be perfectly fine too. The OP is completely free to adapt this code sample. Moreover, this is only one call, there is really no speed issue about using a lambda or an explicit function :) – Fareanor Oct 22 '19 at 07:26
2

It's simple if those fields have same type. Note , in C++ proper term is member variable, not field. You can use pointer to member.

struct structure_name {
    std::string name;
    std::string mark;
};

int main()
{
    structure_name S  = { "name", "test"};

    // p now points at name field
    // p can point at any std::string in structure_name 
    std::string structure_name::* p  = &structure_name::name;

    // p is dereferenced using  object S.
    std::cout << (S.*p) << std::endl;
}

In your case option must be such pointer

If fields have different types, some type erasure method should be used. There are two dereference operators, .* and ->*.

if sort parameter is know statically you can template the sort algorithm, using fields type as a template parameter. SO you'll have it written once, but instantiated twice, for each field.

Swift - Friday Pie
  • 12,777
  • 2
  • 19
  • 42
1

Instead of having a duplicate of the sorting algorithm, you could have it just once and put the input comparison inside the sorting algorithm. Something similar to what you're trying to do would be to determine the correct comparator and pass that on to a general sorting function. If there aren't any requirements that you didn't mention, I would just use std::sort for that:

#include <iostream>
#include <array>
class foo {
public:
    int mark;
    std::string name;
};
int main() {
    std::array<foo, 100> Array;
    // initialize Array here
    int input;
    std::cin >> input;
    if (input == 1) {
        std::sort(Array.begin(), Array.end(), [](foo& a, foo& b) {
            return a.mark > b.mark;
        });
    }
    else if (input == 2) {
        std::sort(Array.begin(), Array.end(), std::less<int>());
    }
    else
        std::cout << "Invalid input of " << input << ", did not sort." << std::endl;
}
Blaze
  • 16,736
  • 2
  • 25
  • 44
0

I think you could do something like this:

auto f1 = [](fix_type name1, fix_type name2) {
 return tolower(choice.name[0]) <= tolower(Array[j].name[0]);
}

auto f2 = [](fix_type name1, fix_type name2) {
 return choice.mark <= Array[j].mark;
}

std::function<bool(fix_type, fix_type)> f;

if(input == 1){
  f = f1;
}else if(input == 2){
  f = f2;
}