【问题标题】:C++ set: storing duplicates: confused about < operatorC++ 集:存储重复项:对 < 运算符感到困惑
【发布时间】:2016-12-09 08:58:27
【问题描述】:

我对 C++ 很陌生(但对 C 很了解),所以我可能遗漏了一些明显的东西。

TLDR:我使用存储元素两次的 std::set,这绝对不是我想要的。

长篇大论: 我已经定义了一个类 Clique,我需要将这个类的元素存储在一个集合中,所以我为 Clique 定义了

class Clique{
public :
  int b;
  int e;
  int l;
  std::set<int> X;

  bool operator <( const Clique &rhs ) const
  {
    if( b < rhs.b)
      return true;
    if( e < rhs.e)
      return true;
    if( X.size() < rhs.X.size() )
      return true;
    std::set<int>::iterator itX = X.begin();
    std::set<int>::iterator itrhs = rhs.X.begin();
    // both sets have same size, need only to check end for one of them                                                                                                                                            
    while( (*itX == *itrhs) && ( itX != X.end() ) ){
      ++itX;
      ++itrhs;
    }
    if( itX == X.end() ){
      //both sets are equal                                                                                                                                                                                        
      return false;
    }
    else
      return ( *itX < *itrhs );
  }

  void print_clique(FILE *F) const ;
};

(我不确定集合比较是如何进行的,所以我编写了一个例程,先按大小比较它们,然后逐个元素比较)。

现在我想将 Clique 元素存储在一个集合中,这就是问题出现的地方。 我的 std::set (1) 似乎没有按照我定义的顺序存储 Clique 元素; (2) 存储同一个 Clique 的多个副本

我写了一个函数来打印一组 Clique:

void print_cliqueset(std::set<Clique> mySet){
  int setsize = 0;

  std::set<Clique>::iterator it = mySet.begin();
  Clique cur_c = *it;
  Clique prev_c = *it;
  while( it != mySet.end() ){
  //  for( std::set<Clique>::iterator it = mySet.begin(); it != mySet.end(); ++it ){                                                                                                                               
    it->print_clique(stdout);
    setsize ++;
    ++it;
    if( it != mySet.end() ){
      cur_c = *it;
      assert ( prev_c < cur_c);
      gassert( prev_c.b <= cur_c.b );
    prev_c = *it;
    }
  }

  assert( setsize == mySet.size() );
}

我的功能比需要的更复杂,但我想确保我理解发生了什么。

以下是打印此类集合的典型输出: 每个 Clique 都有一行,我首先打印 b,然后是 e,然后是集合 X 中的元素。

6829 9716 1 2 3 5 8 9 10 
6792 9687 1 2 3 7 8 9 10 
606 6531 1 2 3 5 6 7 8 9 
6829 9687 1 2 3 5 7 8 9 10 
410 9951 2 6 
484 9805 1 2 4 6 
494 9805 2 4 6 10 
506 9805 1 2 5 6 
484 9821 1 2 4 
484 9871 2 3 4 6 
506 9821 1 2 5 
484 9802 1 2 3 4 6 
486 9805 1 2 4 6 9 
486 9802 1 2 3 4 6 9 
507 9802 1 2 3 4 6 9 10 
502 9802 1 2 3 4 6 10 
506 9802 1 2 3 5 6 
507 9806 1 2 4 9 10 
507 9805 1 2 5 6 9 
527 9806 1 2 5 9 10 

正如我们所见,派系根本没有按照我定义(或想要定义)的顺序排序。它们应该首先按成员 b 排序(即每行的第一个),而事实并非如此。

然后我在输出中有一些重复的行(没有出现在上面的示例中,但出现在完整的输出中)。我想我有重复的事实并不奇怪,因为它似乎对顺序感到困惑......

我想答案是相当明显的,但我看不到它。任何帮助将不胜感激!

【问题讨论】:

  • 您使用哪种 C++ 标准?解决方案的复杂性取决于此。
  • 您的比较器需要遵循例如指定的 等价关系 this std::set reference.
  • 顺便说一句,成员int l; 未进行比较。

标签: c++ set std


【解决方案1】:

您的bool operator &lt;( const Clique &amp;rhs ) const 是错误的,因为它不遵守严格的顺序。

可能只是:

bool operator <(const Clique& rhs) const
{
    return std::tie(b, e, X) < std::tie(rhs.b, rhs.e, rhs.X);
}

【讨论】:

  • 这个operator&lt; 的行为与作者定义的行为不同。 std::set::operator&lt; 按字典顺序比较集合。
  • @alexeykuzmin0:这也是 OP 尝试做的(除了大小检查)。据我了解,OP 只希望一个有效的运算符 std::set 中使用它。
  • 正是我需要的,谢谢。我只介绍了集合比较,因为我不确定
  • @chlorine 我强烈建议您将此网站添加为书签:en.cppreference.com/w/cpp/container/set/operator_cmp 如果您有兴趣编写正确的惯用代码,那么您会发现它非常宝贵。
【解决方案2】:

您的operator&lt; 已损坏。考虑两个Cliques:

c1 is {b = 0, e = 1, ...}
c2 is {b = 1, e = 0, ...}

您的代码将为c1 &lt; c2c2 &lt; c1 返回true

显然,在这种情况下std::set 表现出奇怪的行为。

我会通过以下方式修复您的operator&lt;

bool operator <( const Clique &rhs ) const
{
    if( b != rhs.b)
        return b < rhs.b;
    if( e != rhs.e)
        return e < rhs.e;
    if( X.size() != rhs.X.size() )
        return X.size() < rhs.X.size();
    std::set<int>::iterator itX = X.begin();
    std::set<int>::iterator itrhs = rhs.X.begin();
    // both sets have same size, need only to check end for one of them
    while((itX != X.end()) && (itX == *itrhs)){
        ++itX;
        ++itrhs;
    }
    if( itX == X.end() ){
    //both sets are equal
        return false;
    }
    else
        return ( *itX < *itrhs );
}

【讨论】:

  • set 比较是错误的:(取消引用 end 迭代器)。而operator &lt; (const std::set&lt;T&gt;&amp;, const std::set&lt;T&gt;&amp;) 就足够了。
  • 你说得对,将修复取消引用。我不知道作者这样定义operator&lt;的目的,所以我不想改变这种行为。唯一的目标是将Cliques 存储在std::set 中,是的,这就足够了(并且应该用作更具可读性和可维护性)。
  • 我不敢相信我的比较写得这么有缺陷!感谢建议的更正! (实际上我不需要任何特定的集合比较,我只想要一个可以为集合提供 any 排序的函数,所以我不需要 while 循环,只需使用
【解决方案3】:

operatorb < e 应该用于确定 任何 类型的关系。以下等效项在这里有效:

a &gt; b b &lt; a

a == b !(a &lt; b) &amp;&amp; !(b &lt; a)

a &gt;= b `!(a

等等。如果您使用多个字段来检查每个关系检查,那么您就有一种多维范围。只能通过这种方式制作一个平坦的范围:

  • 首先检查更重要的字段;如果此字段中的值不相等,则立即返回结果
  • 否则 - 如果它们相等 - 请检查重要性顺序中的下一个字段,依此类推。

在集合中使用这种复杂的关系定义实际上会让事情变得更加困难,因为您要做的就是说明一个元素是否小于另一个元素。因此,在您的情况下,您必须自己检查 equality。您的程序会检查“重要性链中的下一个”字段如果lhs.b &gt; rhs.b

【讨论】:

  • 是的,我为自己不了解自己而感到羞愧。感谢您的解释! :)
【解决方案4】:

运算符 x < y 然后!(y &lt; x)!(y == x)

Clique 的情况下,要求似乎是元素 b、e 和 X 按字典顺序进行比较。

表示这一点的惯用方式是根据operator&lt; 进行所有比较:

#include <set>

class Clique{
public :
    int b;
    int e;
    int l;
    std::set<int> X;

    bool operator <( const Clique &r ) const
    {
        auto const& l = *this;

        if (l.b < r.b) return true;
        if (r.b < l.b) return false;

        if (l.e < r.e) return true;
        if (r.e < l.e) return false;

        if (l.X < r.X) return true;
        if (r.X < l.X) return false;

        return false;
    }

    void print_clique(FILE *F) const ;
};

是的,std::set 确实提供了operator&lt;,当密钥类型提供它时。

另一种写法,正如 Jarod 所暗示的那样:

#include <set>
#include <tuple>

class Clique{
public :
    int b;
    int e;
    int l;
    std::set<int> X;

    bool operator <( const Clique &r ) const
    {
        auto const& l = *this;
        return std::tie(l.b, l.e, l.X) < std::tie(r.b, r.e, r.X);
    }

    void print_clique(FILE *F) const ;
};

我想你会同意的是简洁、富有表现力、正确和惯用的。

【讨论】:

  • 我同意! :) 感谢您的详细解释。 :)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-06-12
  • 2021-09-21
  • 2010-10-31
  • 1970-01-01
  • 1970-01-01
  • 2020-06-21
相关资源
最近更新 更多