2015-05-19 9 views
27

Jest to zbiór elementów, a zwyczaj struct:C++ 11 rodzaj obiektów niestandardowych kilkoma właściwościami

struct MyStruct 
{ 
    int id; 
    std::string currencyCode; 
    int month; 
    int year; 
    int amount; 
}; 

Dane te będą wyświetlane w jakiejś tabeli, która pozwala na sortowanie według kilku kolumn (poprzez kliknięcie na kolumny tabeli przytrzymujące klawisz Ctrl).

Sortowanie kolekcji obiektów klienta przez jeden nieruchomości jest tak proste, jak:

vector<MyStruct> values; 

std::sort(values.begin(), values.end(), [ ](const MyStruct& lhs, const MyStruct& rhs) 
{ 
    return lhs.key < rhs.key; 
}); 

lub

struct MyStruct_x_Greater 
{ 
    bool operator()(const MyStruct& lMyStruct, const MyStruct& rMyStruct) const { 
     return lMyStruct.x < rMyStruct.x; 
    } 
}; 

std::sort(values.begin(), values.end(), MyStruct_x_Greater()); 

Ale jak zrobić sortowanie przez kilka właściwości jednego innego (coś jak sql ORDER BY column1 DESC, column2 ASC)?

+1

[Boost.MultiIndex] (http://www.boost.org/doc/libs/1_58_0/libs/multi_index/doc/index.html) został stworzony w tym celu. [ten przykład] (http://www.boost.org/doc/libs/1_58_0/libs/multi_index/example/basic.cpp) jest niemal dosłownie twoją sprawą. – rwols

+1

@nwols: ciągłe przechowywanie i utrzymywanie indeksów dla wszystkich obsługiwanych zamówień sortowania (ponad 20 ma sens) nie wydaje mi się szczególnie dobrym podejściem ... –

Odpowiedz

34

Wystarczy zmienić komparator. Załóżmy, że chcesz zamówić przez rok, a potem przez miesiąc, następnie przez kwotę, a następnie:

std::sort(values.begin(), values.end(), [ ](const MyStruct& lhs, const MyStruct& rhs) 
{ 
    return std::tie(lhs.year, lhs.month, lhs.amount) < 
      std::tie(rhs.year, rhs.month, rhs.amount); 
}); 

std::tuple „s operator< robi leksykograficznego porównania, więc nie ma potrzeby, aby rzucić swój własny (i ryzyko coraz to źle). Aby wykonać kolejność malejącą dla atrybutu, po prostu zamień te atrybuty na lhs i rhs.

4

Zamawianie według kilku atrybutów oznacza określenie kolejności ważności. Na przykład ważniejsze niż miesiąc jest zamówienie przez rok, miesiąc: rok:

vector<MyStruct> values; 
std::sort(values.begin(), values.end(), [ ](const MyStruct& lhs, const MyStruct& rhs) 
{ 
    return lhs.year < rhs.year || (lhs.year == rhs.year && lhs.month < rhs.month); 
}); 
5

Zastosowanie następny klucz tylko wtedy, gdy wartości bieżącego klucza są równe:

[ ](const MyStruct& lhs, const MyStruct& rhs) 
{ 
    if(lhs.key1 != rhs.key1) 
     return lhs.key1 < rhs.key1; 

    if(lhs.key2 != rhs.key2) 
     return lhs.key2 < rhs.key2; 

    ...  

    return lhs.keyN < rhs.keyN; 
} 

W tym przykładzie, wpisy będą zamawiane accroding key1, niż key2 i tak dalej, aż do keyN.

14

Twoje potrzeby, aby wybrać kolejność sortowania przy starcie różni się od większości podobnych pytań (i odpowiedzi na kilka odruchowych już odebrane). Proponuję dać każde pole numer identyfikacyjny, a także dodać funkcję porównać pole określonego przez ID:

template <typename T> 
int cmp(const T& lhs, const T& rhs) 
{ 
    return lhs < rhs ? -1 : lhs == rhs ? 0 : 1; 
} 

struct MyStruct 
{ 
    int id; // 0 
    std::string currencyCode; // 1 
    int month; // 2 
    int year; // 3 
    int amount; // 4 

    int cmp(int field_id, const MyStruct& rhs) const 
    { 
     switch (field_id) 
     { 
      case 0: return cmp(id, rhs.id); 
      case 1: return cmp(currencyCode, rhs.currencyCode); 
      case 2: return cmp(month, rhs.month); 
      case 3: return cmp(year, rhs.year); 
      case 4: return cmp(amount, rhs.amount); 
      default: throw ...cases out of sync with code...; 
     } 
    } 
}; 

// update this as your column heading are clicked... 
std::vector<int> sort_field_ids = ...; 

std::sort(begin(values), end(values), 
    [&](const MyStruct& lhs, const MyStruct& rhs) 
    { 
     for (auto fid : sort_field_ids) 
     { 
      int cmp = lhs.cmp(fid, rhs); 
      if (cmp) return cmp == -1; 
     } 
     // fall back on something stable and unique 
     return lhs.id < rhs.id; 
    }); 

do wspierania malejąco rodzaju, utrzymania dodatkową flagę (np std::vector<std::pair<int, bool>> sort_field_ids) następnie return (cmp == -1)^descending; (gdzie fid,descending są wyodrębniane z swój numer pair lub odnieś je jako .first/.second, jeśli wolisz).

Co więcej, znajdź bibliotekę graficzną z przyzwoitym widgetem siatki/tabeli, który dokonuje wewnętrznego sortowania.

+0

Dzięki, tak, runtime jest tutaj ważne. – Zelid

3

nieco kłopotliwe drogą do osiągnięcia tego, co szukasz jest to zrobić w kilku etapach (to będzie działać w C++ 03, jak również)

struct ColumnOneDESC{...}; 
struct ColumnTwoASC{...}; 
... 
std::stable_sort(values.begin(), values.end(), ColumnTwoASC()); 
std::stable_sort(values.begin(), values.end(), ColumnOneDESC()); 
... 

I cource można uczynić go bardziej ogólny przez stosując std :: lub nie, takie

8

Rozwiązanie to poprzez przypisanie identyfikatorów pól ma kilka wad:

  • przypadkach może być pokryty (na co wskazuje throw)
  • Sortowanie w kolejności odwrotnej wymaga dodatkowego flagę
  • To nie jest oczywiste z kodu, który stoi ID, dla których pole
  • Nie jest łatwo rozszerzalny (zawsze trzeba zaktualizować mapowania ID)

Alternatywnie można zdefiniować "komparatory", które zwracają wartość, w zależności od tego, czy pierwszy argument jest mniejszy, równy lub większy niż drugi. Komparatory te można następnie łączyć i odwracać dość ogólnie i używać jako predykatu sortowania.

Biorąc pod uwagę zestaw tych komparatorów dla dziedzin swojej struktury, można komponować je tak:

Comparator byYearReverseAndMonth = compose(reverse(byYear), byMonth); 
std::sort(values.begin(), values.end(), with(byYearReverseAndMonth)); 

Edycja w odpowiedzi na komentarz:

Oczywiście to nie niezbędne do zdefiniowania każdej kombinacji komparatorów i komparatorów wstecznych w czasie kompilacji. Zamiast tego można zbierać pożądane komparatory dla następnego sortowania w czasie wykonywania, na przykład w vector<Comparator>. Te przykłady porównawcze może na przykład być związany z kolumny tabeli:

vector<Comparator> comparators; 
for (each selected column) { 
    comparators.push_pack(comparatorFor(column)); 
} 

te komparatory można następnie składa się w jedną z prostą pętli na wszystkich komparatorów:

Comparator composedComparator = comparators[0]; 
for (int i=1; i<comparators.size(); ++i) { 
    composedComparator = compose(comparator, comparators[i]); 
} 

sort(v.begin(),v.end(),with(composedComparator)); 

szkic co to może wyglądać następująco:

#include <algorithm> 
#include <functional> 
#include <iostream> 
#include <vector> 

struct MyStruct 
{ 
    int id; 
    std::string currencyCode; 
    int month; 
    int year; 
    int amount; 
}; 

typedef std::function<int(const MyStruct& s0, const MyStruct& s1)> Comparator; 
typedef std::function<bool(const MyStruct& s0, const MyStruct& s1)> Predicate; 

template <typename T> 
std::function<int(const T&, const T&)> compose(
    std::function<int(const T&, const T&)> c0, 
    std::function<int(const T&, const T&)> c1) 
{ 
    return[c0, c1](const T& t0, const T& t1) -> int 
    { 
     int r0 = c0(t0, t1); 
     if (r0 != 0) 
     { 
      return r0; 
     } 
     return c1(t0, t1); 
    }; 
} 

template <typename T> 
std::function<int(const T&, const T&)> reverse(
    std::function<int(const T&, const T&)> c) 
{ 
    return[c](const T& t0, const T& t1) -> int 
    { 
     return -c(t0, t1); 
    }; 
} 

template <typename T> 
std::function<bool(const T&, const T&)> with(
    std::function<int(const T&, const T&)> comparator) 
{ 
    return[comparator](const T& t0, const T& t1) 
    { 
     return comparator(t0, t1) < 0; 
    }; 
} 


void print(std::vector<MyStruct>& values) 
{ 
    for (auto it = values.begin(); it != values.end(); ++it) 
    { 
     std::cout << (*it).month << "-" 
      << (*it).year << " id " 
      << (*it).id << std::endl; 
    } 
} 

int main(int argc, char** argv) 
{ 
    std::vector<MyStruct> values; 

    MyStruct m; 
    m.year = 1981; m.month = 1; m.id = 4; values.push_back(m); 
    m.year = 1980; m.month = 2; m.id = 5; values.push_back(m); 
    m.year = 1980; m.month = 4; m.id = 2; values.push_back(m); 
    m.year = 1980; m.month = 3; m.id = 3; values.push_back(m); 
    m.year = 1980; m.month = 4; m.id = 1; values.push_back(m); 

    std::cout << "Before sorting" << std::endl; 
    print(values); 

    Comparator byMonth = [](const MyStruct& s0, const MyStruct& s1) 
    { 
     if (s0.month < s1.month) return -1; 
     if (s0.month > s1.month) return 1; 
     return 0; 
    }; 
    Comparator byYear = [](const MyStruct& s0, const MyStruct& s1) 
    { 
     if (s0.year < s1.year) return -1; 
     if (s0.year > s1.year) return 1; 
     return 0; 
    }; 
    Comparator byId = [](const MyStruct& s0, const MyStruct& s1) 
    { 
     if (s0.id < s1.id) return -1; 
     if (s0.id > s1.id) return 1; 
     return 0; 
    }; 

    Comparator byYearAndMonth = compose(byYear, byMonth); 
    std::sort(values.begin(), values.end(), with(byYearAndMonth)); 

    std::cout << "After sorting by year and month:" << std::endl; 
    print(values); 


    Comparator byYearReverseAndMonth = compose(reverse(byYear), byMonth); 
    std::sort(values.begin(), values.end(), with(byYearReverseAndMonth)); 

    std::cout << "After sorting by year reverse and month:" << std::endl; 
    print(values); 


    Comparator byYearAndMonthAndId = compose(byYearAndMonth, byId); 
    std::sort(values.begin(), values.end(), with(byYearAndMonthAndId)); 

    std::cout << "After sorting by year and month and id:" << std::endl; 
    print(values); 

    return 0; 
} 

(przeprosiny za potencjalnych faux pas, jestem newb C++ tj)

+1

Jedyne, co trzeba zrobić, aby Komparatory dla wszystkich możliwych kombinacji tutaj będzie dużo pracy, a gdy nowa właściwość zostanie dodana do wyliczenia - trzeba ponownie zmienić wszystkie komparatory. – Zelid

+1

@Zidzieć Może źle zrozumiałem twoje wymagania. Ale myślę, że jest tylko ** jeden ** komparator potrzebny do ** każdej ** własności.(Oczywiście, gdyby konieczne było stworzenie każdej możliwej kombinacji z góry (przynosząc kombinowaną eksplozję i wykładniczą liczbę komparatorów), nie byłoby to możliwe). Ale tutaj kombinacje * mogą być następnie montowane w czasie wykonywania. Na przykład. można skomponować wszystkie komparatory, które są zawarte na przykład w "wektorze " - bez względu na to, jakie to są komparatory i czy niektóre z nich są w rzeczywistości komparatorami "wstecznymi". – Marco13

Powiązane problemy