#c #stl
#c #stl
Вопрос:
Я хочу изменить содержимое a std::set
(не просто повторяя его в обратном порядке, а изменяя содержимое iteslf). Я обнаружил, что std::set
это использует сравнение как объект функции для своего конструктора. Поэтому я придумал следующий код, чтобы сделать то же самое:
#include <set>
using namespace std;
struct Comparator
{
Comparator(bool g) : m_g(g){}
bool operator()(int i1, int i2) const
{
if(m_g)
{
return i1>i2;
}
return i1<i2;
}
bool m_g;
};
int main(int argc, char** argv)
{
Comparator l(false);
set<int,Comparator> a(l);
a.insert(1);
a.insert(2);
a.insert(3);
Comparator g(true);
set<int,Comparator> b(g);
copy(a.begin(), a.end(), inserter(b, b.begin()));
a = b;
return 0;
}
Похоже, это работает в VC9. Но корректен ли этот код? Мои сомнения возникают из-за того, что у my Comparator
есть состояние, связанное с ним. Разрешено ли компараторам иметь состояния?
Комментарии:
1. Я не понимаю, почему нет, при условии, что вы не измените состояние в середине сравнения. Вы должны сделать bool закрытым, чтобы защитить это, и если вы беспокоитесь о стиле, то, вероятно, в первую очередь используйте класс, а не структуру — для меня компаратор больше похож на класс, чем на структуру.
2. Обычно вы используете разные типы компараторов. важно ли для нашего варианта использования, чтобы набор имел один и тот же тип в обоих случаях?
3. шаблонизация порядка сортировки в компараторе будет немного быстрее, поскольку компилятор будет знать логическую константу во время компиляции и фактически пропускать ветку во время выполнения.
4. @Jalf: да, мне нужно использовать тот же объект. Весь мой другой код обращается к этому набору, поэтому я не могу использовать разные типы компараторов.
5. Мне любопытно, как именно вам нужно это использовать. Мне трудно придумать ситуацию, когда я не мог бы просто использовать один и тот же точный набор, а только изменить свой алгоритм в соответствии с желаемым порядком.
Ответ №1:
Да, это законно. Учтите, что если бы компаратору не разрешалось иметь состояние, не было бы смысла разрешать вам передавать компаратор в качестве параметра конструктора. 🙂
Пока компаратор обеспечивает строгий слабый порядок, это нормально (что, среди прочего, означает, что он должен быть последовательным. Вы не можете изменить его состояние на полпути, чтобы оно упорядочивало элементы по-другому)
Комментарии:
1. Вот почему у вас нет возможности получить доступ к компаратору, который использует сам набор. (Вы можете получить его копию с помощью
std::set<>::key_comp()
, но это возвращает значение, поэтому вы получаете только копию для воспроизведения.)
Ответ №2:
Это нормально, но это излишне сложно.
Вы можете просто использовать std::less
(значение по умолчанию для этого параметра шаблона!) Или std::greater
из стандартной библиотеки. Они предоставляются <functional>
.
Комментарии:
1. Но тогда тип не будет одинаковым, т. Е. Для первого типа тип будет
std::set<int,lesser<int> >
, а для второго один разstd::set<int, greater<int> >
. Я хочу, чтобы типы были одинаковыми.2. «Я хочу, чтобы типы были одинаковыми» для какой цели? Вы собираетесь хранить компараторы в контейнере? o_O
Ответ №3:
Более общее решение. boost::assign и c 11 просто для удобства (и забавный автоматический реверс)
# include <iostream>
# include <set>
# include <boost/assign.hpp>
using namespace boost::assign;
template <typename CL , typename Pred>
struct revPred {
revPred (Pred pred) : pred_(pred) {}
bool operator()(const CL amp; a, const CLamp; b)
{
return pred_(b,a);
}
Pred pred_;
};
template <typename CL , typename Pred, typename alloc>
inline
std::set<CL,revPred<CL,Pred>,alloc> reverseSet(const std::set<CL,Pred,alloc> amp; set) {
std::set<CL,revPred<CL,Pred>,alloc> res(revPred<CL,Pred>(set.key_comp()));
std::copy(set.begin(), set.end(), std::inserter(res, res.begin()));
return res;
}
int main()
{
std::set<int> s; s = 0 , 1 , 2 , 3;
std::for_each(s.begin(), s.end(), [](int x) { std::cout << x << " "; });
std::cout << std::endl;
auto reverse = reverseSet(s);
std::for_each(reverse.begin(), reverse.end(), [](int x) { std::cout << x << " "; });
std::cout << std::endl;
return 0;
}
Ответ №4:
В вашем коде нет ничего плохого.
И нет ничего плохого в том, что компараторы имеют состояние.
Ответ №5:
Это нормально, поскольку ваше сравнение не изменяется динамически и обеспечивает строгий слабый порядок.
Однако, если вы делаете это так, чтобы тип набора оставался неизменным даже при изменении порядка, я мог бы предложить альтернативную идею. Вместо этого сравнения вы используете два разных типа набора с std::less
и std::greater
и используете интерфейс итератора, как это делает стандартная библиотека, а не интерфейс контейнера, который зависит от всех параметров шаблона.
И, наконец, как отмечено в ответе от @parapura rajkumar, вы должны использовать конструктор пары итераторов, а не std::copy
:
// Assuming my other comments don't apply, modify as needed if they do:
Comparator g(true);
set<int, Comparator> b(a.rbegin(), a.rend(), g);
Ответ №6:
Вам не нужно предоставлять объект функции, который поддерживает состояние. Просто используйте обычную функцию, и она должна выполнить эту работу. Пожалуйста. не возражайте против использования лямбда. Используется как сокращение для выполнения печати.
typedef bool (*Cmp)(int x, int y);
bool Compare(int x, int y)
{
return x < y;
}
bool CompareR(int x , int y)
{
return !Compare(x, y);
}
void SetReverse()
{
std::set<int, Cmp> s1(Compare);
s1.insert(1);
s1.insert(3);
s1.insert(2);
s1.insert(4);
std::for_each(s1.begin(), s1.end(), [](int x) { std::cout << x << "n"; });
std::set<int, Cmp> s2(CompareR);
s2.insert(s1.begin(), s1.end());
std::for_each(s2.begin(), s2.end(), [](int x) { std::cout << x << "n"; });
s1 = s2;
}
Комментарии:
1. Причина, по которой люди предпочитают использовать объекты функций вместо функций, заключается в более высокой производительности — вы не можете встроить косвенный вызов.
2. джагансай, я думаю, что твой ответ неверен, потому что !compare даст x >= y, что не является слабым строгим упорядочением.
Ответ №7:
Если у вас достаточно большой набор, вы уже заплатили штраф O (NlogN) за создание сбалансированного двоичного дерева. Слепая вставка в целевой набор, вам придется снова заплатить штраф.
Рассмотрите возможность использования одной из этих перегрузок вставки.
void insert ( InputIterator first, InputIterator last );
iterator insert ( iterator position, const value_typeamp; x );
Вставка диапазона имеет линейную сложность, если [первый, последний) уже отсортированы.