Sortowanie vectora struktur po co najmniej dwóch parametrach

0

Cześć, pisze niewielka aplikacje konsolowa i natrafiłem na pewien problem. Mianowicie mam vector zapełniony strukturami(wszystkie struktury jednakowe) i chciałbym, aby użytkownik miał możliwość posortowania go, po co najmniej dwóch różnych parametrach jednocześnie(w zależności od tego co wpisze). struktura zawiera w sobie : number, name, category, login, website oraz password. Po uruchomieniu sortowania użytkownik ma do wyboru po jakich parametrach chce sortować i tu zaczyna się mój problem. Czy jest jakaś możliwość przekazania do wyrażenia Lambda/funkcji sortującej zmiennej wskazującej na parametr, po którym użytkownik chce sortować? Dodam, że próbuje to zaimplementować za pomocą std::sort i chciałbym to zrobić bez konieczności pisania tylu funkcji sortujących ile jest kombinacji parametrów. Poniżej znajduje się przykładowa funkcja sortująca. Chciałbym w jakiś sposób w miejsce np ".name" przekazywać wartość/wskaźnik za pomocą zmiennej(do której wcześniej użytkownik wpisze wartość, która wskazywała by na dany parametr struktury).

sort(passwordVec.begin(), passwordVec.end(), [](Password const& pass1, Password const& pass2) {
			if (pass1.name < pass2.name) return true;
			else if (pass1.category < pass2.category)return true;
			else return false; });
0

Przez zmienną globalną ... (tak, wiem ... nie wątkowe, nieładne )

Yarides napisał(a):

Chciałbym w jakiś sposób w miejsce np ".name" przekazywać wartość/wskaźnik za pomocą zmiennej(do której wcześniej użytkownik wpisze wartość, która wskazywała by na dany parametr struktury).

Pola nieznanego obiektu nie wskażesz (chyba by się udało w fajnych templejtkach, ale to nie tu jesteśmy)

Zaskoczony jestem prymitywizmem tego API ... jedno z gorszych jakie widziałem w kilku językach

0
plx211 napisał(a):

Będzie krótko bo z telefonu:
poczytaj o lambda capture https://en.cppreference.com/w/cpp/language/lambda

Mam świadomość, że w tych kwadratowych nawiasach jestem w stanie przekazywać zmienne, które będą dostępne w wyrażeniu Lambda. Natomiast od niedawna uczę się c++ i od dwóch dni nie mogę dojść do tego, w jaki sposób podstawić zmienną do tego wyrażenia, aby wskazywała ona na konkretny parametr. Przypuśćmy, że użytkownik poprzez cin>> zmienna(string) wpisuje do zmiennej słowo "name". W jaki sposób wstawić ją do tego porównania pass1."zmienna" > pass2."zmienna", aby ta zmienna wskazywała na parametr w strukturze, na który jest zapisany w tej zmiennej, czyli w tym przypadku chciałbym, żeby w porównaniu znalazło się pass1.name > pass2.name. Wiem, że nie będę w stanie tego zrobić poprzez zmienną typu string, ale nie mam pojęcia czego użyć, aby osiągnąć zamierzony efekt. Mam nadzieje, że udało mi się dosyć jasno ubrać w słowa o co mi chodzi :D

0

Właśnie czytam dok ...

https://cplusplus.com/reference/algorithm/stable_sort/

Tu jest mowa, ze komparator może być OBIEKTEM funkcyjnym (choć nie mam pojęcia jak), wydaje się ze to zwiększa nadzieje.

0

Szczerze myślałem, że będzie to prostsze jeżeli użyje std::sort ale z tego co widzę tylko to utrudnia i będę musiał napisać własną funkcje sortującą. Jeżeli zrobił bym to na dwóch iteratorach to jestem w stanie zrobić coś w ten deseń?
string zmienna;

cin>>zmienna;
vector<Tstruct>::iterator i;
i->zmienna; 
0

@Yarides:

Iteratory rozumie się jako coś, co przebiega raz w jedną stronę (choć są realizacje bardziej "z gumy")

Jest:
https://www.mygreatlearning.com/blog/sort-function-in-cpp/
sekcja:
Sort using a Function Object, to dopiero zaczyna być po bożemu, jak w javie i C#

Co implementowania sortowania totalnie po swojemu, odradzam.

2

Jak rozumiem, chodzi o to by wybierać parametry do sortowania (dokładnie dwa) w czasie runtime'u. Zaklepane na szybko.

#include <vector>
#include <string>
#include <iostream>
#include <algorithm>
#include <exception>

struct Test {
    int i;
    float f;
    std::string s;
};

std::ostream& operator<< (std::ostream& o, const Test& test) {
    return o << "(" << test.i << ", " << test.f << ", " << test.s << ")";
}

std::function<bool(const Test& t1, const Test& t2)> get_comparator(std::string field) {
    if(field == std::string("i")) { return [](const Test& t1, const Test& t2){ return t1.i < t2.i; }; }
    if(field == std::string("f")) { return [](const Test& t1, const Test& t2){ return t1.f < t2.f; }; }
    if(field == std::string("s")) { return [](const Test& t1, const Test& t2){ return t1.s < t2.s; }; }
    throw std::runtime_error("Unknown comparator (use i, f, s)");
}

int main() {
    std::vector<Test> test = {
        Test{10, 32.2, "Foo"},
        Test{30, 1.f, "C"},
        Test{30, 2.f, "B"},
        Test{30, 3.f, "A"},
        Test{2, 12.2, "Bar"}
    };
    std::string field1, field2;
    std::cin >> field1 >> field2;

    auto comparator1 = get_comparator(field1);
    auto comparator2 = get_comparator(field2);

    std::sort(test.begin(), test.end(), [&](const Test& t1, const Test& t2) {
        // https://stackoverflow.com/a/3574724
        if(comparator1(t1, t2)) return true;
        if(comparator1(t2, t1)) return false;
        if(comparator2(t1, t2)) return true;
        if(comparator2(t2, t1)) return false;
        return false;
    });
    for(auto& t: test) {
        std::cout << t << "\n";
    }
}

get_comparator zwraca tu lambdę porównującą odpowiednie pole w strukturze w zależności od podanego stringa (trzeba ręcznie wypisać dla wszystkim pól, bo refleksji póki co nie ma). Mając już dwie lambdy do porównań przekazujemy je do drugiej lambdy, użytej w samej funkcji std::sort ze standardu.

$ g++ test.cpp -o test
$ ./test 
i
f
(2, 12.2, Bar)
(10, 32.2, Foo)
(30, 1, C)
(30, 2, B)
(30, 3, A)
$ ./test
i
s
(2, 12.2, Bar)
(10, 32.2, Foo)
(30, 3, A)
(30, 2, B)
(30, 1, C)

0
Spearhead napisał(a):

Jak rozumiem, chodzi o to by wybierać parametry do sortowania (dokładnie dwa) w czasie runtime'u. Zaklepane na szybko.

#include <vector>
#include <string>
#include <iostream>
#include <algorithm>
#include <exception>

struct Test {
    int i;
    float f;
    std::string s;
};

std::ostream& operator<< (std::ostream& o, const Test& test) {
    return o << "(" << test.i << ", " << test.f << ", " << test.s << ")";
}

std::function<bool(const Test& t1, const Test& t2)> get_comparator(std::string field) {
    if(field == std::string("i")) { return [](const Test& t1, const Test& t2){ return t1.i < t2.i; }; }
    if(field == std::string("f")) { return [](const Test& t1, const Test& t2){ return t1.f < t2.f; }; }
    if(field == std::string("s")) { return [](const Test& t1, const Test& t2){ return t1.s < t2.s; }; }
    throw std::runtime_error("Unknown comparator (use i, f, s)");
}

int main() {
    std::vector<Test> test = {
        Test{10, 32.2, "Foo"},
        Test{30, 1.f, "C"},
        Test{30, 2.f, "B"},
        Test{30, 3.f, "A"},
        Test{2, 12.2, "Bar"}
    };
    std::string field1, field2;
    std::cin >> field1 >> field2;

    auto comparator1 = get_comparator(field1);
    auto comparator2 = get_comparator(field2);

    std::sort(test.begin(), test.end(), [&](const Test& t1, const Test& t2) {
        // https://stackoverflow.com/a/3574724
        if(comparator1(t1, t2)) return true;
        if(comparator1(t2, t1)) return false;
        if(comparator2(t1, t2)) return true;
        if(comparator2(t2, t1)) return false;
        return false;
    });
    for(auto& t: test) {
        std::cout << t << "\n";
    }
}

get_comparator zwraca tu lambdę porównującą odpowiednie pole w strukturze w zależności od podanego stringa (trzeba ręcznie wypisać dla wszystkim pól, bo refleksji póki co nie ma). Mając już dwie lambdy do porównań przekazujemy je do drugiej lambdy, użytej w samej funkcji std::sort ze standardu.

$ g++ test.cpp -o test
$ ./test 
i
f
(2, 12.2, Bar)
(10, 32.2, Foo)
(30, 1, C)
(30, 2, B)
(30, 3, A)
$ ./test
i
s
(2, 12.2, Bar)
(10, 32.2, Foo)
(30, 3, A)
(30, 2, B)
(30, 1, C)

Bardzo pomocne dzięki, mógłbyś mi jeszcze powiedzieć jak przekształcić ta funkcje,

std::function<bool(const Test& t1, const Test& t2)> get_comparator(std::string field) {
    if(field == std::string("i")) { return [](const Test& t1, const Test& t2){ return t1.i < t2.i; }; }
    if(field == std::string("f")) { return [](const Test& t1, const Test& t2){ return t1.f < t2.f; }; }
    if(field == std::string("s")) { return [](const Test& t1, const Test& t2){ return t1.s < t2.s; }; }
    throw std::runtime_error("Unknown comparator (use i, f, s)");
}

na coś w stylu bool getComperator(arg,arg2){
}
bo nie do końca rozumiem ten zapis. :D

0
#include <iostream>
#include <vector>
#include <algorithm>
#include <iomanip>

using namespace std;

struct Avenue
{
    string name;
    int number;
};

enum SortBy { NAME, NUMBER } sortby;

bool operator<(const Avenue & lhs, const Avenue & rhs)
{
    switch( sortby )
    {
    case NAME:
    {
        if (lhs.name != rhs.name)
        {
            return lhs.name < rhs.name;
        }
        return lhs.number < rhs.number;
    }
    case NUMBER:
    {
        if (lhs.number != rhs.number)
        {
            return lhs.number < rhs.number;
        }
        return lhs.name < rhs.name;
    }
    }
    return false;
}

int main()
{
    vector<Avenue> street = {
        {"Strawberry", 31}, {"Honey", 18}, {"Raspberry", 25}, {"Oak", 18},
        {"Cherry", 20}, {"Cherry", 17}, {"Raspberry", 18}
    };
    int choise{0};

    for (const Avenue & way: street)
        cout << setw(18) << setfill(' ') << right << way.name << '\t' << way.number << '\n';

    while( (cout<<"\nyou choise sort by : 1-name, 2-number : ")&&(cin>>choise) )
    {
        if((choise<1)||(choise>2)) break;
        if(choise==1){sortby=NAME;sort(street.begin(), street.end());}
        if(choise==2){sortby=NUMBER;sort(street.begin(), street.end());}
        for (const Avenue & way: street)
            cout << setw(18) << setfill(' ') << right << way.name << '\t' << way.number << '\n';
    }

    return 0;
}

0
int CompareTo(const Test& t1, const Test& t2)
{
  int ret;
  if((ret=t1.i<=>t2.i)!=0) return ret;
  if((ret=t1.f<=>t2.f)!=0) return ret;
  if((ret=t1.s<=>t2.s)!=0) return ret;
  return ret;
}
1

Najlepiej to nie kombinować, normalny if/switch i osobny sort dla każdego przypadku.
Wersja C++20:


struct Data {
    int number;
    std::string name;
    int category;
    std::string login;
    std::string website;
    std::string password;
};

template<auto a, auto b>
auto selectTwoItems(const Data& x)
{
    return std::pair{std::invoke(a, x), std::invoke(b, x)};
}

class Model {
    std::vector<Data> mItems;

public:
    std::ostream& printTo(std::ostream& out) const
    {
        std::copy(mItems.begin(), mItems.end(), std::ostream_iterator<Data> { out, "\n" });
        return out;
    }

    std::istream& scanFrom(std::istream& in)
    {
        std::copy(std::istream_iterator<Data> { in }, {}, std::back_inserter(mItems));
        in.clear();
        return in;
    }

    template <auto By, typename Order = std::less<>>
    void sortItems()
    {
        std::ranges::stable_sort(mItems, Order {}, By);
    }

    void sortItemsBy(int input)
    {
        switch (input) {
        case 0:
            sortItems<&Data::number>();
            break;
        case 1:
            sortItems<&Data::name>();
            break;
        case 2:
            sortItems<&Data::category>();
            break;
        case 3:
            sortItems<&Data::login>();
            break;
        case 4:
            sortItems<&Data::website>();
            break;
        case 23:
            sortItems<&selectTwoItems<&Data::category, &Data::login>>();
            break;
        default:
            std::cerr << "Nieprawidłowa wartość: " << input << '\n';
        }
    }
};

https://godbolt.org/z/M513oWjaa

Jak już podmieniać komparator do sort, to potrzebny jest jeden typ. Skoro wszystkie porównania nie wymagają dodatkowego parametru, zwracanie wskaźnika na funkcje powinno być wystarczające. Wersja dla c++11:

bool(*)(const Data&, const Data&) selectCmp(int input)
{
    switch(input) {
    case 0: return +[](const Data& a, const Data& b) { return a.number < b.number ;};
    case 1: return +[](const Data& a, const Data& b) { return a.name < b.name ;};
    case 2: return +[](const Data& a, const Data& b) { return a.category < b.category ;};
    case 3: return +[](const Data& a, const Data& b) { return a.login < b.login ;};
    case 4: return +[](const Data& a, const Data& b) { return a.website < b.website ;};
    default:
        std::cerr << "Nieprawidłowa wartość: " << input << '\n'; 
    }
    return +[](const Data&, const Data&) { return true; }; 
}

https://godbolt.org/z/hEvr1MEb7

0

Piję kawę i "podziwiam" w/w ortodoksyjne propozycje, przy których grzech zmiennej globalnej (czytelnosc, konserowalność) jest totalnie nieistotny.

MarekR22 napisał(a):

Najlepiej to nie kombinować, normalny if/switch i osobny sort dla każdego przypadku.

4 pola, z których każde może być w zestawie kluczy, lub nie
To się silnią liczy czy potęgą? Jakby nie liczyć, to błyskawicznie osiąga > 20 przypadków.

Nadmienię, oczywiście wiesz, że kilkukrotne sortowanie po pojedynczych kluczach NIE JEST tożsame sortowaniu po kluczu złożonym

0

W boost jest jakieś sortowanie, nie wiem o nim nic, ale może ma szczęśliwszy design *) ?
https://www.boost.org/doc/libs/1_62_0/libs/sort/doc/html/index.html

*) dla mnie kolejny przykład jak życie znają brodacze w kraciastych koszulach

1
#include <algorithm>
#include <iostream>
using namespace std;

struct Data
{
     int number;
     string name;
     int category;
     string login;
     string password;
};

template <typename DataType> class orderByFields
{
	public:
	typedef function<bool(const DataType&,const DataType&)> CmpType;
	typedef vector<CmpType> CmpTypeList;
	private:
	CmpTypeList &order;
	public:
	orderByFields(CmpTypeList &order):order(order) {}
	bool operator()(const DataType& a,const DataType& b)
	{
		int ret=0;
		for(const CmpType &cmp:order)  if((ret=cmp(b,a)-cmp(a,b))) break;	
		return ret<0;
	}
};

auto byNumber=[](const Data& a,const Data& b) { return a.number<b.number; };
auto byName=[](const Data& a,const Data& b) { return a.name<b.name; };
auto byCategory=[](const Data& a,const Data& b) { return a.category<b.category; };

int main()
{
	vector<Data> items;
	items.push_back(Data{0,"C",2,"",""});
	items.push_back(Data{0,"B",1,"",""});
	items.push_back(Data{0,"A",2,"",""});
	items.push_back(Data{0,"A",1,"",""});
	items.push_back(Data{0,"B",2,"",""});
	items.push_back(Data{0,"C",1,"",""});
	vector<function<bool(const Data&,const Data&)>> order;
	order.push_back(byCategory);
	order.push_back(byName);
	sort(begin(items),end(items),orderByFields<Data>(order));
	for(const Data &data:items) cout<<data.category<<' '<<data.name<<endl;
	cout<<endl;

	reverse(begin(order),end(order));
	sort(begin(items),end(items),orderByFields<Data>(order));
	for(const Data &data:items) cout<<data.category<<' '<<data.name<<endl;
	return 0;
}

1 użytkowników online, w tym zalogowanych: 0, gości: 1