【问题标题】:A set of struct with different == and < operator semantic一组具有不同 == 和 < 运算符语义的结构
【发布时间】:2012-06-21 09:39:34
【问题描述】:

我使用的是一个非常简单的结构,映射,定义如下:

struct mapping{
    int code;
    string label;

    bool operator<(const mapping& map) const {
        return code < map.code;
    }

    bool operator==(const mapping& map) const {
        return label.compare(map.label) == 0 ;
    }
};

我想创建一组按他们的代码排序的映射。为此,我重载了

当我尝试插入具有相同代码但不同标签的映射时,问题就出现了。实际上,在该过程的第二步中,我不知道之前是否插入了具有相同标签的映射。所以,我需要调用 find() 函数来确定是否是这种情况。如果没有插入相同标签的映射,没关系,我只需要插入这个新的(但它的代码将暂时与其他映射相同)。如果存在一个具有相同标签的映射,我只需要更新它的代码。我虽然像我一样重载 == 运算符就足够了,但事实并非如此,如下面的代码所示。

mapping m = {1,"xxx"};
mapping m2 ={1, "yyy"};

this->fn[0].insert(m);

set<mapping>::iterator itTmp;
itTmp = this->fn[0].find(m2);
if (itTmp != this->fn[0].end() ) {
    cout << "m2 exists "<<endl;

    if ( !(*itTmp == m2) ){
        cout << "But it is different according to the definition of the == operator "<<endl;
    }
} 

相关的输出是:

m2 exists
But it is different according to the definition of the == operator

我该如何解决这个问题并设法处理具有非常不同语义的运算符

谢谢,

约安

【问题讨论】:

  • 顺便说一句,operatorstd::set<mapping, SpecialOrdering> specialOrderedSet
  • @stefaanv 所以std::string("that") 小于std::string("this")?包含 1 和 10 的 std::set&lt;int&gt; 小于仅包含 2 的 std::set&lt;int&gt;
  • @aschepler:是的,显然,即使对于一个没有多大意义的集合。您似乎不同意我写的内容,但我不确定您的意思。我的观点是,如果您使用 operator

标签: c++ stl set


【解决方案1】:

如果一个类可以按其多个字段合理排序,则它根本不应该有关系运算符(operator==operator!= 除外)。

是的,std::mapstd::set 默认使用 std::less 作为比较器(内部使用 operator&lt;),但这只是为了方便,所以您不需要编写函数对象来创建 @ 987654327@,其中double 有一个规范的排序顺序(因此定义了一个(内置)operator&lt;)。

它们还提供了一个比较器模板参数,您可以而且应该使用它来准确地提供该容器(如关系数据库)所需的索引。还有一些容器(在 boost 中)为一组元素维护多个索引。

例如,考虑一个point2d 类:

struct point2d { int x, y; };

人们可以合理地希望通过xy 来索引point2ds 的容器,因此按照上述说明,point2d 根本不应该定义operator&lt;。为了避免类的每个用户都发明自己的排序函数对象,可以将它们与point2d 一起提供:

struct less_point2d_by_x {
    typedef bool result_value;
    // the mixed overloads are for use in std::lower_bound and similar
    bool operator()( int lhs, int rhs ) const { return lhs < rhs; }
    bool operator()( int lhs, point2d rhs ) const { return lhs < rhs.x; }
    bool operator()( point2d lhs, int rhs ) const { return lhs.x < rhs; }
    bool operator()( point2d lhs, point2d rhs ) const { return lhs.x < rhs.x; }
};
// same for _y

用法:

std::vector<point2d> v = ...;
std::sort(v.begin(), v.end(), less_point2d_by_x()); // sort by x
std::sort(v.begin(), v.end(), less_point2d_by_y()); // sort by y

std::set<point2d, less_point2d_by_x> orderedByX;
std::set<point2d, less_point2d_by_y> orderedByY;

如果你不怕模板,你甚至可以模板化关系运算符,像这样:

template <template <typename T> class Op=std::less>
struct point2d_by_x {
    typedef bool result_value;
    // the mixed overloads are for use in std::lower_bound and similar
    bool operator()( int lhs, int rhs )         const { return Op<int>()(lhs, rhs); }
    bool operator()( int lhs, point2d rhs )     const { return Op<int>()(lhs, rhs.x); }
    bool operator()( point2d lhs, int rhs )     const { return Op<int>()(lhs.x, rhs); }
    bool operator()( point2d lhs, point2d rhs ) const { return Op<int>()(lhs.x, rhs.x); }
};
// same for _y

用法:

std::vector<point2d> v = ...;
std::sort(v.begin(), v.end(), point2d_by_x<std::less>()); // sort ascending by x
std::sort(v.begin(), v.end(), point2d_by_x<>()); // same
std::sort(v.begin(), v.end(), point2d_by_x<std::greater>()); // sort descending by x
// find the position where a `point2d` with `x = 14` would go in the ordering:
std::lower_bound(v.begin(), v.end(), 14, point2d_by_x<std::greater>());

std::set<point2d, point2d_by_x<std::less> > increasingXOrder;
std::set<point2d, point2d_by_y<std::less> > increasingYOrder;

【讨论】:

  • 非常感谢您提供如此清晰而有帮助的回复。我从中学到了很多。
  • @YoannPitarch:StackOverflow 表达感谢的方式是对答案进行投票。不过,我很欣赏这些客气话:)
  • 关键是我不能赞成你的回复,因为我是新手并且没有 15 声望。
【解决方案2】:

正如@PiotrNycz 指出的那样,std::set 根本不使用或关心operator==,只有operator&lt;。这包括std::set&lt;X&gt;::find 成员。因此,您的 find 调用正在定位具有相同代码的元素,而不是具有相同标签的元素。

但命名空间范围函数std::find 确实使用operator==,而不是operator&lt;

itTmp = std::find(this->fn[0].begin(), this->fn[0].end(), m2);

由于您的set 是按代码而非标签排序的,因此当元素数量很大时,查找具有给定标签的元素的效率不如查找具有给定代码的元素。如果这很重要,其他答案已经给出了有关如何更改容器类型以提供帮助的建议。

【讨论】:

  • 好的,谢谢。这是非常有用的。 “无法像使用给定代码查找一样有效”,您的意思是搜索时间复杂度与记录数成线性关系吗?
  • 没错。 std::set&lt;X&gt;::find 是 O(log(N)),std::find 是 O(N),其中 N 是元素的总数。
【解决方案3】:

如果您希望 O(log n) 查找代码或映射,那么 std::set 不是正确的容器,因为它仅按一个顺序排序。您需要的是一个双映射,其值为指向另一个映射中条目的指针。这样,每个成员都是其映射中的一个键,并且可以在 O(log n) 中找到。

如果可以使用 boost,请查看boost.bimap

【讨论】:

  • 看起来是个好主意。谢谢!! :-)
【解决方案4】:

您可能应该创建多个容器(两个或三个)。 例如:

std::list<mapping> storage; // actual storage
std::multiset<mapping*, compare_by_code_functor> by_code; // sorted by code
std::set<mapping*, compare_by_label_functor> by_label; // sorted by label

std::list<mapping> storage; // actual storage
std::multiset<std::list<mapping>::iterator, compare_by_code_functor> by_code; // sorted by code
std::set<std::list<mapping>::iterator, compare_by_label_functor> by_label; // sorted by label

【讨论】:

  • 看起来很有效,但从存储的角度来看并不是最有效的解决方案。希望它存在更好,更优雅的解决方案。但无论如何,谢谢。
【解决方案5】:

std::set 使用operator&lt;,因此您需要将operator== 的语义插入operator&lt;。您可以通过例如首先检查code 来做到这一点,如果相等,则比较label


更新 标准库中的有序容器必须使用隐含严格弱序的比较器。这意味着operator&lt;需要推导出一个严格的顺序(不同的元素有不同的顺序),但是很弱,因为它对不等式一无所知。

在您的情况下,正如 cmets 中所指出的,您需要比较 string label。两种选择:

return code < map.code || (!(map.code < code) && label < map.label);

或反过来,首先检查label,然后检查code

仅检查label 的相等性会破坏“严格”的排序感。

【讨论】:

  • 不幸的是,通过“return (code
  • @YoannPitarch:比较 label,不检查不等式。您评论中的 operator&lt; 实现没有定义严格的顺序。
  • @rubenvb 我可能很愚蠢,但是......有什么区别?在我看来,比较标签意味着评估字符串是否相同。
  • 例如return code &lt; map.code || (!(map.code &lt; code) &amp;&amp; label &lt; map.label);。您的实现不是可传递的。考虑 {1, "a"}
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-09-27
  • 2012-04-19
  • 2019-12-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多