Связывание предикатов упорядочения (например, для std:: sort)

Вы можете передать указатель функции, объект функции (или увеличить lambda) в std:: sort, чтобы определить строгий слабый порядок элементов контейнера, который вы хотите отсортировать.

Однако иногда (достаточно, чтобы я ударил это несколько раз), вы хотите иметь возможность связывать "примитивные" сравнения.

Тривиальный пример: если вы сортируете коллекцию объектов, представляющих контактные данные. Иногда вам нужно сортировать по

last name, first name, area code
. Другие времена
first name, last name
- еще раз
age, first name, area code
... и т.д.

Теперь вы можете, конечно, записать дополнительный объект функции для каждого случая, но это нарушает принцип DRY - особенно если каждое сравнение менее тривиально.

Похоже, что вы должны иметь возможность писать иерархию функций сравнения - низкоуровневые выполняют одиночные, примитивные, сопоставления (например, имя и имя), а затем более высокие уровни называет нижние уровни подряд (возможно, цепочки с использованием && использовать для оценки короткого замыкания) для создания составных функций.

Проблема с этим подходом заключается в том, что std:: sort принимает двоичный предикат - предикат может возвращать только bool. Поэтому, если вы их составляете, вы не можете сказать, означает ли "ложь" равенство или больше. Вы можете сделать предикаты нижнего уровня возвратом int с тремя состояниями - но тогда вам придется обернуть их в предикаты более высокого уровня, прежде чем их можно будет использовать с помощью std:: sort самостоятельно.

В целом, это не непреодолимые проблемы. Это просто кажется сложнее, чем должно быть - и, конечно же, приглашает реализацию вспомогательной библиотеки.

Поэтому кто-нибудь знает какую-либо ранее существовавшую библиотеку (например, если она является std или boost library), которая может помочь здесь - иметь какие-либо другие мысли по этому вопросу?

[Обновление]

Как упоминалось в некоторых комментариях - я пошел вперед и написал собственную реализацию класса для управления этим. Он довольно минимален и, вероятно, имеет некоторые проблемы с ним в целом. но на этой основе для всех, кого это интересует, класс находится здесь:

http://pastebin.com/f52a85e4f

И некоторые вспомогательные функции (чтобы избежать необходимости указывать шаблонные аргументы) здесь:

http://pastebin.com/fa03d66e

Ответ 1

Вы можете создать небольшую систему цепочек:

struct Type {
  string first, last;
  int age;
};

struct CmpFirst {
  bool operator () (const Type& lhs, const Type& rhs) { return lhs.first < rhs.first; }
};

struct CmpLast {
  bool operator () (const Type& lhs, const Type& rhs) { return lhs.last < rhs.last; }
};

struct CmpAge {
  bool operator () (const Type& lhs, const Type& rhs) { return lhs.age < rhs.age; }
};

template <typename First, typename Second>
struct Chain {
  Chain(const First& f_, const Second& s_): f(f_), s(s_) {}

  bool operator () (const Type& lhs, const Type& rhs) {
    if(f(lhs, rhs))
      return true;
    if(f(rhs, lhs))
      return false;

    return s(lhs, rhs);
  }

  template <typename Next>
  Chain <Chain, Next> chain(const Next& next) const {
     return Chain <Chain, Next> (*this, next);
  }

  First f;
  Second s;
};

struct False { bool operator() (const Type& lhs, const Type& rhs) { return false; } };

template <typename Op>
Chain <False, Op> make_chain(const Op& op) { return Chain <False, Op> (False(), op); }

Затем, чтобы использовать его:

vector <Type> v;  // fill this baby up

sort(v.begin(), v.end(), make_chain(CmpLast()).chain(CmpFirst()).chain(CmpAge()));

Последняя строка немного подробна, но я думаю, что она понятна, что предназначено.

Ответ 2

Один обычный способ справиться с этим - сортировать в несколько проходов и использовать устойчивый вид. Обратите внимание, что std::sort обычно нестабилен. Тем не менее, theres std::stable_sort.

Тем не менее, я бы написал обертку вокруг функторов, которые возвращают tristate (меньше, равно, больше).

Ответ 3

std::sort не гарантируется стабильностью, потому что стабильные сортировки обычно медленнее, чем нестабильные... поэтому использование стабильного сортирования несколько раз выглядит как рецепт проблемы с производительностью...

И да, действительно, это позор, который попросит предикат: Я не вижу другого способа, кроме создания функтора, принимающего вектор тристатных функций...

Ответ 4

Вы можете попробовать следующее:

Использование:

struct Citizen {
    std::wstring iFirstName;
    std::wstring iLastName;
};

ChainComparer<Citizen> cmp;
cmp.Chain<std::less>( boost::bind( &Citizen::iLastName, _1 ) );
cmp.Chain<std::less>( boost::bind( &Citizen::iFirstName, _1 ) );

std::vector<Citizen> vec;
std::sort( vec.begin(), vec.end(), cmp );

Реализация:

template <typename T>
class ChainComparer {
public:

    typedef boost::function<bool(const T&, const T&)> TComparator;
    typedef TComparator EqualComparator;
    typedef TComparator CustomComparator;

    template <template <typename> class TComparer, typename TValueGetter>
    void Chain( const TValueGetter& getter ) {

        iComparers.push_back( std::make_pair( 
            boost::bind( getter, _1 ) == boost::bind( getter, _2 ), 
            boost::bind( TComparer<TValueGetter::result_type>(), boost::bind( getter, _1 ), boost::bind( getter, _2 ) ) 
        ) );
    }

    bool operator()( const T& lhs, const T& rhs ) {
        BOOST_FOREACH( const auto& comparer, iComparers ) {
            if( !comparer.first( lhs, rhs ) ) {
                return comparer.second( lhs, rhs );
            }
        }

        return false;
    }

private:
    std::vector<std::pair<EqualComparator, CustomComparator>> iComparers;
};

Ответ 5

Цепочное решение является подробным. Вы также можете использовать boost:: bind в сочетании с std:: logical_and для создания вашего предиката сортировки. См. Связанную статью для получения дополнительной информации: Как библиотека связывания boost может улучшить ваши С++-программы

Ответ 6

Шаблоны Variadic на С++ 11 дают более короткую опцию:

    #include <iostream>
    using namespace std;

    struct vec { int x,y,z; };

    struct CmpX {
      bool operator() (const vec& lhs, const vec& rhs) const 
      { return lhs.x < rhs.x; }
    };

    struct CmpY {
      bool operator() (const vec& lhs, const vec& rhs) const 
      { return lhs.y < rhs.y; }
    };

    struct CmpZ {
      bool operator() (const vec& lhs, const vec& rhs) const 
      { return lhs.z < rhs.z; }
    };

    template <typename T>
    bool chained(const T &, const T &) {
      return false;
    }

    template <typename CMP, typename T, typename ...P>
    bool chained(const T &t1, const T &t2, const CMP &c, P...p) {
      if (c(t1,t2)) { return true;          }
      if (c(t2,t1)) { return false;         }
      else          { return chained(t1, t2, p...); }
    }

    int main(int argc, char **argv) {
      vec x = { 1,2,3 }, y = { 2,2,3 }, z = { 1,3,3 };
      cout << chained(x,x,CmpX(),CmpY(),CmpZ()) << endl;
      return 0;
    }