用任意比较器设置C ++

时间:2013-12-27 15:39:06

标签: c++

我有以下C ++代码

#include <set>
#include <string>
#include <iostream>
using namespace std;

class Pair {
  public:
    string lhs;
    string rhs;
    Pair();
    Pair( string l, string r ) {
      lhs=l;
      rhs=r;
    };
};

struct compare {
  bool operator()(const Pair& a, const Pair& b) const{
    if ( ( a.lhs == b.lhs && a.rhs == b.rhs ) || ( a.lhs == b.rhs && a.rhs == b.lhs ) ) {
      cout << "MATCH" << endl;
    }
    return ( a.lhs == b.lhs && a.rhs == b.rhs ) || ( a.lhs == b.rhs && a.rhs == b.lhs );
  }
};

int main () {
  set<Pair, compare > s;
  Pair p( string("Hello"), string("World") );
  s.insert(p);
  cout << s.size() << "\n";
  Pair q( string("World"), string("Hello") );
  s.insert(q);
  cout << s.size() << "\n";
  compare cmp;
  cout << cmp( p, q );

  return 0;
}

调用已编译的代码给出:

1
MATCH
MATCH
2
MATCH

不管怎么说,尽管比较器将它们识别为相同,但是以某种方式结束,两者对p和q结束。 为什么呢?

非常感谢任何帮助!

更新:

非常感谢您的出色答案以及您的善意和专业帮助。 正如您可能已经猜到的那样,我对C ++非常感兴趣。

无论如何,我想知道Antoine的回答是否可以用lambda表达式完成?

类似的东西:

std::set< …, [](){ my_comparator_code_here } > s;

????

4 个答案:

答案 0 :(得分:6)

std::set(有序容器)的比较运算符需要识别严格的弱排序而不是您希望的任意测试。通常,正确实施operator<可以完成工作。

如果您的比较运算符未提供严格的弱序列(因为您没有),则行为将是未定义的。没有办法解决C ++标准的这个要求。

请注意,在需要进行相等比较的某些情况下,必须使用operator<两次进行比较。

您是否还考虑过使用std::pair<std::string, std::string>而不是自己动手?

我现在已经重读了你的问题五次了,我开始想知道你想要的是set对是哪个字符串在firstsecond不在无论比较如何,都很重要。在这种情况下,@ Antoine似乎是适合您的解决方案。

答案 1 :(得分:3)

setmap或任何需要订单的lower_boundsort等算法的比较器需要实现严格的弱排序(基本上,表现得像一个<)。

这样的排序是必需的to have 3 properties

  • irreflexive:not (a < a)永远是真的
  • 非对称:a < b隐含not (b < a)
  • 及物性:a < bb < c暗示a < c

你不会<拥有。

这样的排序定义了等价类,它们是根据排序比较相等的元素组(即not (a < b) and not (b < a)被验证)。在setmap中,每个等价类只能插入一个元素,而multisetmultimap每个等价类可以包含多个元素。

现在,如果你看一下你的比较器,你就会发现你已经实现了==根本没有定义任何订单。您需要实现类似于<的内容。

一个简单但非常有效的技巧是使用元组,其中<(和==以及任何其他比较运算符)已在字典中实现订单。因此,std::tuple<std::string, std::string>完全符合您的顺序;甚至更好,std::tuple<std::string const&, std::string const&>也有它,并且可以使用std::tie轻松构建。

因此,直接比较器的实现非常简单:

struct comparator {
    bool operator()(Pair const& left, Pair const& right) const {
        return std::tie( left.a,  left.b)
             < std::tie(right.a, right.b);
    }
};

注意:尽管讨论不多,但比较器的排序必须在调用之间稳定。因此,它通常应该只依赖于元素的值,而不是外部或运行时相关的(例如它们在内存中的地址)


编辑:如上所述,您的比较器稍微复杂一些。

但是,在您的情况下,您还需要考虑ab具有对称角色。一般来说,我建议在对象的构造函数中统一表示;如果不可能,你可以先进行统一并比较第二个:

struct comparator {
    bool operator()(Pair const& left, Pair const& right) const {
        auto uleft = left.a < left.b ? std::tie(left.a, left.b)
                                     : std::tie(left.b, left.a);
        auto uright = right.a < right.b ? std::tie(right.a, right.b)
                                        : std::tie(right.b, right.a);

        assert(get<0>(uleft) <= get<1>(uleft) and "Incorrect uleft");
        assert(get<0>(uright) <= get<1>(uright) and "Incorrect uright");

        return uleft < uright;
    }
}; // struct comparator

答案 2 :(得分:2)

正如Mark B所说,compare表示排序而不是相等,默认情况下它是std::less。在您的情况下,您不希望比较取决于您的货币对中的订单,但同时,您的运营商&lt;必须满足conditions的数量。

此处的所有答案都建议更改您的规范并使比较顺序依赖。但如果您不想这样,这就是解决方案:

bool operator()(const Pair & a, const Pair & b) {
  const bool swapA = a.lhs < a.rhs;
  const std::string & al = swapA ? a.lhs : a.rhs;
  const std::string & ar = swapA ? a.rhs : a.lhs;
  const bool swapB = b.lhs < b.rhs;
  const std::string & bl = swapB ? b.lhs : b.rhs;
  const std::string & br = swapB ? b.rhs : b.lhs;
  return al < bl || (al == bl && ar < br);
}

至少,它适用于你的例子,这种关系是反身和传递的。

以下是它的工作原理:它是对的词典顺序:al < bl || (al == bl && ar < br),适用于已排序的对。

实际上,您的数据结构是(大小为N的集合)(大小为2的集合)。在内部,std::set使用比较运算符对其元素进行排序。对于“大小为2的集合”Pair,您还需要将它们视为内部排序。

如果比较代码看起来太重,您可以将对排序移动到Pair类,就像实现两个方法min()max()一样。此外,您实施operator<,然后不需要compare类:

struct Pair {
  string lhs, rhs;
  Pair();
  Pair( string l, string r ) : lhs(l), rhs(r) {}
  const std::string & min() const { return lhs < rhs ? lhs : rhs; }
  const std::string & max() const { return lhs < rhs ? rhs : lhs; }
  bool operator<(const Pair& b) const {
    return min() < b.min() || (min() == b.min() && max() < b.max());
  }
};

答案 3 :(得分:1)

from here

  

set对象使用这个表达式来确定元素在容器中遵循的顺序以及两个元素键是否相等(通过反思地比较它们:如果!comp(a,b)&amp;&amp;!comp,它们是等价的(b,A))。集合容器中没有两个元素可以等效。

抱歉所有人都跳了枪,因为我不喜欢另一个答案。我会立刻开口并纠正。 AS指出,需要实施订单。典型地,这将是一个字典顺序。但重要的是,你仍然需要确保你认为两对相等的情况在两种情况下都会返回false。

if (( a.lhs == b.lhs && a.rhs == b.rhs ) || ( a.lhs == b.rhs && a.rhs == b.lhs )) return false;
//ordinary lexicographical compare
if( a.lhs < b.lhs) return true;
else if( a.lhs == b.lhs && a.rhs < b.rhs) return true;
else return false;

注意“!”,简单。你的代码是说第一对少于第二对,小于第一对。你想要它说两者都不比另一个少。

免责声明仍然缺乏技术性,ANTOINE是正确的